,
+ config?: PureHttpRequestConfig
+ ): Promise {
+ return this.request
("post", url, params, config);
+ }
+
+ /** 单独抽离的get工具函数 */
+ public get(
+ url: string,
+ params?: AxiosRequestConfig,
+ config?: PureHttpRequestConfig
+ ): Promise {
+ return this.request
("get", url, params, config);
+ }
+}
+
+export const http = new PureHttp();
diff --git a/src/utils/http/types.d.ts b/src/utils/http/types.d.ts
new file mode 100644
index 0000000..197b152
--- /dev/null
+++ b/src/utils/http/types.d.ts
@@ -0,0 +1,47 @@
+import Axios, {
+ Method,
+ AxiosError,
+ AxiosResponse,
+ AxiosRequestConfig
+} from "axios";
+
+export type resultType = {
+ accessToken?: string;
+};
+
+export type RequestMethods = Extract<
+ Method,
+ "get" | "post" | "put" | "delete" | "patch" | "option" | "head"
+>;
+
+export interface PureHttpError extends AxiosError {
+ isCancelRequest?: boolean;
+}
+
+export interface PureHttpResponse extends AxiosResponse {
+ config: PureHttpRequestConfig;
+}
+
+export interface PureHttpRequestConfig extends AxiosRequestConfig {
+ beforeRequestCallback?: (request: PureHttpRequestConfig) => void;
+ beforeResponseCallback?: (response: PureHttpResponse) => void;
+}
+
+export default class PureHttp {
+ request(
+ method: RequestMethods,
+ url: string,
+ param?: AxiosRequestConfig,
+ axiosConfig?: PureHttpRequestConfig
+ ): Promise;
+ post(
+ url: string,
+ params?: T,
+ config?: PureHttpRequestConfig
+ ): Promise;
+ get(
+ url: string,
+ params?: T,
+ config?: PureHttpRequestConfig
+ ): Promise;
+}
diff --git a/src/utils/message.ts b/src/utils/message.ts
new file mode 100644
index 0000000..54b100f
--- /dev/null
+++ b/src/utils/message.ts
@@ -0,0 +1,85 @@
+import { type VNode } from "vue";
+import { isFunction } from "@pureadmin/utils";
+import { type MessageHandler, ElMessage } from "element-plus";
+
+type messageStyle = "el" | "antd";
+type messageTypes = "info" | "success" | "warning" | "error";
+
+interface MessageParams {
+ /** 消息类型,可选 `info` 、`success` 、`warning` 、`error` ,默认 `info` */
+ type?: messageTypes;
+ /** 自定义图标,该属性会覆盖 `type` 的图标 */
+ icon?: any;
+ /** 是否将 `message` 属性作为 `HTML` 片段处理,默认 `false` */
+ dangerouslyUseHTMLString?: boolean;
+ /** 消息风格,可选 `el` 、`antd` ,默认 `antd` */
+ customClass?: messageStyle;
+ /** 显示时间,单位为毫秒。设为 `0` 则不会自动关闭,`element-plus` 默认是 `3000` ,平台改成默认 `2000` */
+ duration?: number;
+ /** 是否显示关闭按钮,默认值 `false` */
+ showClose?: boolean;
+ /** 文字是否居中,默认值 `false` */
+ center?: boolean;
+ /** `Message` 距离窗口顶部的偏移量,默认 `20` */
+ offset?: number;
+ /** 设置组件的根元素,默认 `document.body` */
+ appendTo?: string | HTMLElement;
+ /** 合并内容相同的消息,不支持 `VNode` 类型的消息,默认值 `false` */
+ grouping?: boolean;
+ /** 关闭时的回调函数, 参数为被关闭的 `message` 实例 */
+ onClose?: Function | null;
+}
+
+/** 用法非常简单,参考 src/views/components/message/index.vue 文件 */
+
+/**
+ * `Message` 消息提示函数
+ */
+const message = (
+ message: string | VNode | (() => VNode),
+ params?: MessageParams
+): MessageHandler => {
+ if (!params) {
+ return ElMessage({
+ message,
+ customClass: "pure-message"
+ });
+ } else {
+ const {
+ icon,
+ type = "info",
+ dangerouslyUseHTMLString = false,
+ customClass = "antd",
+ duration = 2000,
+ showClose = false,
+ center = false,
+ offset = 20,
+ appendTo = document.body,
+ grouping = false,
+ onClose
+ } = params;
+
+ return ElMessage({
+ message,
+ type,
+ icon,
+ dangerouslyUseHTMLString,
+ duration,
+ showClose,
+ center,
+ offset,
+ appendTo,
+ grouping,
+ // 全局搜 pure-message 即可知道该类的样式位置
+ customClass: customClass === "antd" ? "pure-message" : "",
+ onClose: () => (isFunction(onClose) ? onClose() : null)
+ });
+ }
+};
+
+/**
+ * 关闭所有 `Message` 消息提示函数
+ */
+const closeAllMessage = (): void => ElMessage.closeAll();
+
+export { message, closeAllMessage };
diff --git a/src/utils/mitt.ts b/src/utils/mitt.ts
new file mode 100644
index 0000000..63816f1
--- /dev/null
+++ b/src/utils/mitt.ts
@@ -0,0 +1,13 @@
+import type { Emitter } from "mitt";
+import mitt from "mitt";
+
+/** 全局公共事件需要在此处添加类型 */
+type Events = {
+ openPanel: string;
+ tagViewsChange: string;
+ tagViewsShowModel: string;
+ logoChange: boolean;
+ changLayoutRoute: string;
+};
+
+export const emitter: Emitter = mitt();
diff --git a/src/utils/print.ts b/src/utils/print.ts
new file mode 100644
index 0000000..333f6fe
--- /dev/null
+++ b/src/utils/print.ts
@@ -0,0 +1,214 @@
+interface PrintFunction {
+ extendOptions: Function;
+ getStyle: Function;
+ setDomHeight: Function;
+ toPrint: Function;
+}
+
+const Print = function (dom, options?: object): PrintFunction {
+ options = options || {};
+ // @ts-expect-error
+ if (!(this instanceof Print)) return new Print(dom, options);
+ this.conf = {
+ styleStr: "",
+ // Elements that need to dynamically get and set the height
+ setDomHeightArr: [],
+ // Callback before printing
+ printBeforeFn: null,
+ // Callback after printing
+ printDoneCallBack: null
+ };
+ for (const key in this.conf) {
+ // eslint-disable-next-line no-prototype-builtins
+ if (key && options.hasOwnProperty(key)) {
+ this.conf[key] = options[key];
+ }
+ }
+ if (typeof dom === "string") {
+ this.dom = document.querySelector(dom);
+ } else {
+ this.dom = this.isDOM(dom) ? dom : dom.$el;
+ }
+ if (this.conf.setDomHeightArr && this.conf.setDomHeightArr.length) {
+ this.setDomHeight(this.conf.setDomHeightArr);
+ }
+ this.init();
+};
+
+Print.prototype = {
+ /**
+ * init
+ */
+ init: function (): void {
+ const content = this.getStyle() + this.getHtml();
+ this.writeIframe(content);
+ },
+ /**
+ * Configuration property extension
+ * @param {Object} obj
+ * @param {Object} obj2
+ */
+ extendOptions: function (obj, obj2: T): T {
+ for (const k in obj2) {
+ obj[k] = obj2[k];
+ }
+ return obj;
+ },
+ /**
+ Copy all styles of the original page
+ */
+ getStyle: function (): string {
+ let str = "";
+ const styles: NodeListOf = document.querySelectorAll("style,link");
+ for (let i = 0; i < styles.length; i++) {
+ str += styles[i].outerHTML;
+ }
+ str += ``;
+ return str;
+ },
+ // form assignment
+ getHtml: function (): Element {
+ const inputs = document.querySelectorAll("input");
+ const selects = document.querySelectorAll("select");
+ const textareas = document.querySelectorAll("textarea");
+ const canvass = document.querySelectorAll("canvas");
+
+ for (let k = 0; k < inputs.length; k++) {
+ if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
+ if (inputs[k].checked == true) {
+ inputs[k].setAttribute("checked", "checked");
+ } else {
+ inputs[k].removeAttribute("checked");
+ }
+ } else if (inputs[k].type == "text") {
+ inputs[k].setAttribute("value", inputs[k].value);
+ } else {
+ inputs[k].setAttribute("value", inputs[k].value);
+ }
+ }
+
+ for (let k2 = 0; k2 < textareas.length; k2++) {
+ if (textareas[k2].type == "textarea") {
+ textareas[k2].innerHTML = textareas[k2].value;
+ }
+ }
+
+ for (let k3 = 0; k3 < selects.length; k3++) {
+ if (selects[k3].type == "select-one") {
+ const child = selects[k3].children;
+ for (const i in child) {
+ if (child[i].tagName == "OPTION") {
+ if ((child[i] as any).selected == true) {
+ child[i].setAttribute("selected", "selected");
+ } else {
+ child[i].removeAttribute("selected");
+ }
+ }
+ }
+ }
+ }
+
+ for (let k4 = 0; k4 < canvass.length; k4++) {
+ const imageURL = canvass[k4].toDataURL("image/png");
+ const img = document.createElement("img");
+ img.src = imageURL;
+ img.setAttribute("style", "max-width: 100%;");
+ img.className = "isNeedRemove";
+ canvass[k4].parentNode.insertBefore(img, canvass[k4].nextElementSibling);
+ }
+
+ return this.dom.outerHTML;
+ },
+ /**
+ create iframe
+ */
+ writeIframe: function (content) {
+ let w: Document | Window;
+ let doc: Document;
+ const iframe: HTMLIFrameElement = document.createElement("iframe");
+ const f: HTMLIFrameElement = document.body.appendChild(iframe);
+ iframe.id = "myIframe";
+ iframe.setAttribute(
+ "style",
+ "position:absolute;width:0;height:0;top:-10px;left:-10px;"
+ );
+ // eslint-disable-next-line prefer-const
+ w = f.contentWindow || f.contentDocument;
+ // eslint-disable-next-line prefer-const
+ doc = f.contentDocument || f.contentWindow.document;
+ doc.open();
+ doc.write(content);
+ doc.close();
+
+ const removes = document.querySelectorAll(".isNeedRemove");
+ for (let k = 0; k < removes.length; k++) {
+ removes[k].parentNode.removeChild(removes[k]);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const _this = this;
+ iframe.onload = function (): void {
+ // Before popping, callback
+ if (_this.conf.printBeforeFn) {
+ _this.conf.printBeforeFn({ doc });
+ }
+ _this.toPrint(w);
+ setTimeout(function () {
+ document.body.removeChild(iframe);
+ // After popup, callback
+ if (_this.conf.printDoneCallBack) {
+ _this.conf.printDoneCallBack();
+ }
+ }, 100);
+ };
+ },
+ /**
+ Print
+ */
+ toPrint: function (frameWindow): void {
+ try {
+ setTimeout(function () {
+ frameWindow.focus();
+ try {
+ if (!frameWindow.document.execCommand("print", false, null)) {
+ frameWindow.print();
+ }
+ } catch (e) {
+ frameWindow.print();
+ }
+ frameWindow.close();
+ }, 10);
+ } catch (err) {
+ console.error(err);
+ }
+ },
+ isDOM:
+ typeof HTMLElement === "object"
+ ? function (obj) {
+ return obj instanceof HTMLElement;
+ }
+ : function (obj) {
+ return (
+ obj &&
+ typeof obj === "object" &&
+ obj.nodeType === 1 &&
+ typeof obj.nodeName === "string"
+ );
+ },
+ /**
+ * Set the height of the specified dom element by getting the existing height of the dom element and setting
+ * @param {Array} arr
+ */
+ setDomHeight(arr) {
+ if (arr && arr.length) {
+ arr.forEach(name => {
+ const domArr = document.querySelectorAll(name);
+ domArr.forEach(dom => {
+ dom.style.height = dom.offsetHeight + "px";
+ });
+ });
+ }
+ }
+};
+
+export default Print;
diff --git a/src/utils/progress/index.ts b/src/utils/progress/index.ts
new file mode 100644
index 0000000..d309862
--- /dev/null
+++ b/src/utils/progress/index.ts
@@ -0,0 +1,17 @@
+import NProgress from "nprogress";
+import "nprogress/nprogress.css";
+
+NProgress.configure({
+ // 动画方式
+ easing: "ease",
+ // 递增进度条的速度
+ speed: 500,
+ // 是否显示加载ico
+ showSpinner: false,
+ // 自动递增间隔
+ trickleSpeed: 200,
+ // 初始化时的最小百分比
+ minimum: 0.3
+});
+
+export default NProgress;
diff --git a/src/utils/propTypes.ts b/src/utils/propTypes.ts
new file mode 100644
index 0000000..cc7b16f
--- /dev/null
+++ b/src/utils/propTypes.ts
@@ -0,0 +1,39 @@
+import type { CSSProperties, VNodeChild } from "vue";
+import {
+ createTypes,
+ toValidableType,
+ VueTypesInterface,
+ VueTypeValidableDef
+} from "vue-types";
+
+export type VueNode = VNodeChild | JSX.Element;
+
+type PropTypes = VueTypesInterface & {
+ readonly style: VueTypeValidableDef;
+ readonly VNodeChild: VueTypeValidableDef;
+};
+
+const newPropTypes = createTypes({
+ func: undefined,
+ bool: undefined,
+ string: undefined,
+ number: undefined,
+ object: undefined,
+ integer: undefined
+}) as PropTypes;
+
+// 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
+export default class propTypes extends newPropTypes {
+ // a native-like validator that supports the `.validable` method
+ static get style() {
+ return toValidableType("style", {
+ type: [String, Object]
+ });
+ }
+
+ static get VNodeChild() {
+ return toValidableType("VNodeChild", {
+ type: undefined
+ });
+ }
+}
diff --git a/src/utils/responsive.ts b/src/utils/responsive.ts
new file mode 100644
index 0000000..6ee6f51
--- /dev/null
+++ b/src/utils/responsive.ts
@@ -0,0 +1,37 @@
+// 响应式storage
+import { App } from "vue";
+import Storage from "responsive-storage";
+import { routerArrays } from "@/layout/types";
+import { responsiveStorageNameSpace } from "@/config";
+
+export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
+ const nameSpace = responsiveStorageNameSpace();
+ const configObj = Object.assign(
+ {
+ // layout模式以及主题
+ layout: Storage.getData("layout", nameSpace) ?? {
+ layout: config.Layout ?? "vertical",
+ theme: config.Theme ?? "default",
+ darkMode: config.DarkMode ?? false,
+ sidebarStatus: config.SidebarStatus ?? true,
+ epThemeColor: config.EpThemeColor ?? "#409EFF"
+ },
+ configure: Storage.getData("configure", nameSpace) ?? {
+ grey: config.Grey ?? false,
+ weak: config.Weak ?? false,
+ hideTabs: config.HideTabs ?? false,
+ showLogo: config.ShowLogo ?? true,
+ showModel: config.ShowModel ?? "smart",
+ multiTagsCache: config.MultiTagsCache ?? false
+ }
+ },
+ config.MultiTagsCache
+ ? {
+ // 默认显示顶级菜单tag
+ tags: Storage.getData("tags", nameSpace) ?? routerArrays
+ }
+ : {}
+ );
+
+ app.use(Storage, { nameSpace, memory: configObj });
+};
diff --git a/src/utils/sso.ts b/src/utils/sso.ts
new file mode 100644
index 0000000..69b0509
--- /dev/null
+++ b/src/utils/sso.ts
@@ -0,0 +1,59 @@
+import { removeToken, setToken, type DataInfo } from "./auth";
+import { subBefore, getQueryMap } from "@pureadmin/utils";
+
+/**
+ * 简版前端单点登录,根据实际业务自行编写
+ * 划重点:
+ * 判断是否为单点登录,不为则直接返回不再进行任何逻辑处理,下面是单点登录后的逻辑处理
+ * 1.清空本地旧信息;
+ * 2.获取url中的重要参数信息,然后通过 setToken 保存在本地;
+ * 3.删除不需要显示在 url 的参数
+ * 4.使用 window.location.replace 跳转正确页面
+ */
+(function () {
+ // 获取 url 中的参数
+ const params = getQueryMap(location.href) as DataInfo;
+ const must = ["username", "roles", "accessToken"];
+ const mustLength = must.length;
+ if (Object.keys(params).length !== mustLength) return;
+
+ // url 参数满足 must 里的全部值,才判定为单点登录,避免非单点登录时刷新页面无限循环
+ let sso = [];
+ let start = 0;
+
+ while (start < mustLength) {
+ if (Object.keys(params).includes(must[start]) && sso.length <= mustLength) {
+ sso.push(must[start]);
+ } else {
+ sso = [];
+ }
+ start++;
+ }
+
+ if (sso.length === mustLength) {
+ // 判定为单点登录
+
+ // 清空本地旧信息
+ removeToken();
+
+ // 保存新信息到本地
+ setToken(params);
+
+ // 删除不需要显示在 url 的参数
+ delete params["roles"];
+ delete params["accessToken"];
+
+ const newUrl = `${location.origin}${location.pathname}${subBefore(
+ location.hash,
+ "?"
+ )}?${JSON.stringify(params)
+ .replace(/["{}]/g, "")
+ .replace(/:/g, "=")
+ .replace(/,/g, "&")}`;
+
+ // 替换历史记录项
+ window.location.replace(newUrl);
+ } else {
+ return;
+ }
+})();
diff --git a/src/utils/tree.ts b/src/utils/tree.ts
new file mode 100644
index 0000000..f8f3783
--- /dev/null
+++ b/src/utils/tree.ts
@@ -0,0 +1,188 @@
+/**
+ * @description 提取菜单树中的每一项uniqueId
+ * @param tree 树
+ * @returns 每一项uniqueId组成的数组
+ */
+export const extractPathList = (tree: any[]): any => {
+ if (!Array.isArray(tree)) {
+ console.warn("tree must be an array");
+ return [];
+ }
+ if (!tree || tree.length === 0) return [];
+ const expandedPaths: Array = [];
+ for (const node of tree) {
+ const hasChildren = node.children && node.children.length > 0;
+ if (hasChildren) {
+ extractPathList(node.children);
+ }
+ expandedPaths.push(node.uniqueId);
+ }
+ return expandedPaths;
+};
+
+/**
+ * @description 如果父级下children的length为1,删除children并自动组建唯一uniqueId
+ * @param tree 树
+ * @param pathList 每一项的id组成的数组
+ * @returns 组件唯一uniqueId后的树
+ */
+export const deleteChildren = (tree: any[], pathList = []): any => {
+ if (!Array.isArray(tree)) {
+ console.warn("menuTree must be an array");
+ return [];
+ }
+ if (!tree || tree.length === 0) return [];
+ for (const [key, node] of tree.entries()) {
+ if (node.children && node.children.length === 1) delete node.children;
+ node.id = key;
+ node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
+ node.pathList = [...pathList, node.id];
+ node.uniqueId =
+ node.pathList.length > 1 ? node.pathList.join("-") : node.pathList[0];
+ const hasChildren = node.children && node.children.length > 0;
+ if (hasChildren) {
+ deleteChildren(node.children, node.pathList);
+ }
+ }
+ return tree;
+};
+
+/**
+ * @description 创建层级关系
+ * @param tree 树
+ * @param pathList 每一项的id组成的数组
+ * @returns 创建层级关系后的树
+ */
+export const buildHierarchyTree = (tree: any[], pathList = []): any => {
+ if (!Array.isArray(tree)) {
+ console.warn("tree must be an array");
+ return [];
+ }
+ if (!tree || tree.length === 0) return [];
+ for (const [key, node] of tree.entries()) {
+ node.id = key;
+ node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
+ node.pathList = [...pathList, node.id];
+ const hasChildren = node.children && node.children.length > 0;
+ if (hasChildren) {
+ buildHierarchyTree(node.children, node.pathList);
+ }
+ }
+ return tree;
+};
+
+/**
+ * @description 广度优先遍历,根据唯一uniqueId找当前节点信息
+ * @param tree 树
+ * @param uniqueId 唯一uniqueId
+ * @returns 当前节点信息
+ */
+export const getNodeByUniqueId = (
+ tree: any[],
+ uniqueId: number | string
+): any => {
+ if (!Array.isArray(tree)) {
+ console.warn("menuTree must be an array");
+ return [];
+ }
+ if (!tree || tree.length === 0) return [];
+ const item = tree.find(node => node.uniqueId === uniqueId);
+ if (item) return item;
+ const childrenList = tree
+ .filter(node => node.children)
+ .map(i => i.children)
+ .flat(1) as unknown;
+ return getNodeByUniqueId(childrenList as any[], uniqueId);
+};
+
+/**
+ * @description 向当前唯一uniqueId节点中追加字段
+ * @param tree 树
+ * @param uniqueId 唯一uniqueId
+ * @param fields 需要追加的字段
+ * @returns 追加字段后的树
+ */
+export const appendFieldByUniqueId = (
+ tree: any[],
+ uniqueId: number | string,
+ fields: object
+): any => {
+ if (!Array.isArray(tree)) {
+ console.warn("menuTree must be an array");
+ return [];
+ }
+ if (!tree || tree.length === 0) return [];
+ for (const node of tree) {
+ const hasChildren = node.children && node.children.length > 0;
+ if (
+ node.uniqueId === uniqueId &&
+ Object.prototype.toString.call(fields) === "[object Object]"
+ )
+ Object.assign(node, fields);
+ if (hasChildren) {
+ appendFieldByUniqueId(node.children, uniqueId, fields);
+ }
+ }
+ return tree;
+};
+
+/**
+ * @description 构造树型结构数据
+ * @param data 数据源
+ * @param id id字段 默认id
+ * @param parentId 父节点字段,默认parentId
+ * @param children 子节点字段,默认children
+ * @returns 追加字段后的树
+ */
+export const handleTree = (
+ data: any[],
+ id?: string,
+ parentId?: string,
+ children?: string
+): any => {
+ if (!Array.isArray(data)) {
+ console.warn("data must be an array");
+ return [];
+ }
+ const config = {
+ id: id || "id",
+ parentId: parentId || "parentId",
+ childrenList: children || "children"
+ };
+
+ const childrenListMap: any = {};
+ const nodeIds: any = {};
+ const tree = [];
+
+ for (const d of data) {
+ const parentId = d[config.parentId];
+ if (childrenListMap[parentId] == null) {
+ childrenListMap[parentId] = [];
+ }
+ nodeIds[d[config.id]] = d;
+ childrenListMap[parentId].push(d);
+ }
+
+ for (const d of data) {
+ const parentId = d[config.parentId];
+ if (nodeIds[parentId] == null) {
+ tree.push(d);
+ }
+ }
+
+ for (const t of tree) {
+ adaptToChildrenList(t);
+ }
+
+ function adaptToChildrenList(o: Record) {
+ if (childrenListMap[o[config.id]] !== null) {
+ o[config.childrenList] = childrenListMap[o[config.id]];
+ }
+ if (o[config.childrenList]) {
+ for (const c of o[config.childrenList]) {
+ adaptToChildrenList(c);
+ }
+ }
+ }
+ return tree;
+};
diff --git a/src/views/aiModel/index.vue b/src/views/aiModel/index.vue
new file mode 100644
index 0000000..c954f9d
--- /dev/null
+++ b/src/views/aiModel/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
aiModel
+
+
diff --git a/src/views/error/403.vue b/src/views/error/403.vue
new file mode 100644
index 0000000..83b0838
--- /dev/null
+++ b/src/views/error/403.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ 403
+
+
+ 抱歉,你无权访问该页面
+
+
+ 返回首页
+
+
+
+
diff --git a/src/views/error/404.vue b/src/views/error/404.vue
new file mode 100644
index 0000000..fbf01d0
--- /dev/null
+++ b/src/views/error/404.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ 404
+
+
+ 抱歉,你访问的页面不存在
+
+
+ 返回首页
+
+
+
+
diff --git a/src/views/error/500.vue b/src/views/error/500.vue
new file mode 100644
index 0000000..da672b4
--- /dev/null
+++ b/src/views/error/500.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ 500
+
+
+ 抱歉,服务器出错了
+
+
+ 返回首页
+
+
+
+
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 0000000..df14048
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,167 @@
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login/utils/motion.ts b/src/views/login/utils/motion.ts
new file mode 100644
index 0000000..2b1182c
--- /dev/null
+++ b/src/views/login/utils/motion.ts
@@ -0,0 +1,40 @@
+import { h, defineComponent, withDirectives, resolveDirective } from "vue";
+
+/** 封装@vueuse/motion动画库中的自定义指令v-motion */
+export default defineComponent({
+ name: "Motion",
+ props: {
+ delay: {
+ type: Number,
+ default: 50
+ }
+ },
+ render() {
+ const { delay } = this;
+ const motion = resolveDirective("motion");
+ return withDirectives(
+ h(
+ "div",
+ {},
+ {
+ default: () => [this.$slots.default()]
+ }
+ ),
+ [
+ [
+ motion,
+ {
+ initial: { opacity: 0, y: 100 },
+ enter: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ delay
+ }
+ }
+ }
+ ]
+ ]
+ );
+ }
+});
diff --git a/src/views/login/utils/rule.ts b/src/views/login/utils/rule.ts
new file mode 100644
index 0000000..6b73d5a
--- /dev/null
+++ b/src/views/login/utils/rule.ts
@@ -0,0 +1,28 @@
+import { reactive } from "vue";
+import type { FormRules } from "element-plus";
+
+/** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */
+export const REGEXP_PWD =
+ /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
+
+/** 登录校验 */
+const loginRules = reactive({
+ password: [
+ {
+ validator: (rule, value, callback) => {
+ if (value === "") {
+ callback(new Error("请输入密码"));
+ } else if (!REGEXP_PWD.test(value)) {
+ callback(
+ new Error("密码格式应为8-18位数字、字母、符号的任意两种组合")
+ );
+ } else {
+ callback();
+ }
+ },
+ trigger: "blur"
+ }
+ ]
+});
+
+export { loginRules };
diff --git a/src/views/login/utils/static.ts b/src/views/login/utils/static.ts
new file mode 100644
index 0000000..18268d8
--- /dev/null
+++ b/src/views/login/utils/static.ts
@@ -0,0 +1,5 @@
+import bg from "@/assets/login/bg.png";
+import avatar from "@/assets/login/avatar.svg?component";
+import illustration from "@/assets/login/illustration.svg?component";
+
+export { bg, avatar, illustration };
diff --git a/src/views/permission/button/index.vue b/src/views/permission/button/index.vue
new file mode 100644
index 0000000..2da55fe
--- /dev/null
+++ b/src/views/permission/button/index.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+ 当前拥有的code列表:{{ getAuths() }}
+
+
+
+
+
+
+
+ 拥有code:'btn_add' 权限可见
+
+
+ 拥有code:['btn_edit'] 权限可见
+
+
+
+ 拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
+
+
+
+
+
+
+
+
+
+ 拥有code:'btn_add' 权限可见
+
+
+ 拥有code:['btn_edit'] 权限可见
+
+
+ 拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
+
+
+
+
+
+
+
+
+ 拥有code:'btn_add' 权限可见
+
+
+ 拥有code:['btn_edit'] 权限可见
+
+
+ 拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
+
+
+
+
diff --git a/src/views/permission/page/index.vue b/src/views/permission/page/index.vue
new file mode 100644
index 0000000..f02ad5a
--- /dev/null
+++ b/src/views/permission/page/index.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+ 模拟后台根据不同角色返回对应路由(具体参考完整版pure-admin代码)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/project/details/index.vue b/src/views/project/details/index.vue
new file mode 100644
index 0000000..7cab2db
--- /dev/null
+++ b/src/views/project/details/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
算法管理
+
+
diff --git a/src/views/project/list/components/Card.vue b/src/views/project/list/components/Card.vue
new file mode 100644
index 0000000..153dd1f
--- /dev/null
+++ b/src/views/project/list/components/Card.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ product.name }}
+
+
+
+
项目负责:
+
联系方式:
+
模型应用数:
+
算力消耗:
+
+
+
+
+
+
+
+
+
+
+
+ 修改
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/project/list/components/DialogForm.vue b/src/views/project/list/components/DialogForm.vue
new file mode 100644
index 0000000..83037ec
--- /dev/null
+++ b/src/views/project/list/components/DialogForm.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+ 已停用
+ 已启用
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+ 取消
+
+ 确定
+
+
+
+
diff --git a/src/views/project/list/index.vue b/src/views/project/list/index.vue
new file mode 100644
index 0000000..8ae485d
--- /dev/null
+++ b/src/views/project/list/index.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+ 新建产品
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/screen/index.vue b/src/views/screen/index.vue
new file mode 100644
index 0000000..d666d0e
--- /dev/null
+++ b/src/views/screen/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
Sereen
+
+
diff --git a/src/views/warning/list/index.vue b/src/views/warning/list/index.vue
new file mode 100644
index 0000000..bdfb155
--- /dev/null
+++ b/src/views/warning/list/index.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
diff --git a/src/views/warning/list/utils/warningList.ts b/src/views/warning/list/utils/warningList.ts
new file mode 100644
index 0000000..9a3f9a7
--- /dev/null
+++ b/src/views/warning/list/utils/warningList.ts
@@ -0,0 +1,402 @@
+import dayjs from "dayjs";
+import { message } from "@/utils/message";
+// import { getRoleList } from "@/api/system";
+import { type PaginationProps } from "@pureadmin/table";
+import { reactive, ref, onMounted } from "vue";
+
+export function useWarnList() {
+ const form = reactive({
+ name: "",
+ code: "",
+ status: ""
+ });
+ const dataList = ref([]);
+ const loading = ref(true);
+ const pagination = reactive({
+ total: 0,
+ pageSize: 10,
+ currentPage: 1,
+ background: true
+ });
+ const columns: TableColumnList = [
+ {
+ label: "项目编号",
+ prop: "id",
+ minWidth: 100
+ },
+ {
+ label: "项目名称",
+ prop: "name",
+ minWidth: 120
+ },
+ {
+ label: "检测项",
+ prop: "detection",
+ minWidth: 150
+ },
+ {
+ label: "告警任务",
+ prop: "alarmTask",
+ minWidth: 150
+ },
+ {
+ label: "视频通道",
+ prop: "videoChannel",
+ minWidth: 150
+ },
+ {
+ label: "视频通道",
+ prop: "videoChannel",
+ minWidth: 150
+ },
+ {
+ label: "告警日期",
+ minWidth: 180,
+ prop: "createTime",
+ formatter: ({ createTime }) =>
+ dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
+ },
+ {
+ label: "上报状态",
+ prop: "reportingStatus",
+ minWidth: 150
+ },
+ {
+ label: "告警内容",
+ prop: "alarmContent",
+ minWidth: 150
+ }
+ // {
+ // label: "操作",
+ // fixed: "right",
+ // width: 240,
+ // slot: "operation"
+ // }
+ ];
+ // const buttonClass = computed(() => {
+ // return [
+ // "!h-[20px]",
+ // "reset-margin",
+ // "!text-gray-500",
+ // "dark:!text-white",
+ // "dark:hover:!text-primary"
+ // ];
+ // });
+
+ const warnList = ref({
+ list: [
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 1,
+ name: "零装线01",
+ detection: "PCB板表面检测",
+ alarmTask: "3",
+ videoChannel: "10",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 2,
+ name: "零装线02",
+ detection: "人员违章违规",
+ alarmTask: "4",
+ videoChannel: "26",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 3,
+ name: "零装线03",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "31",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 4,
+ name: "零装线04",
+ detection: "PCB板表面检测",
+ alarmTask: "3",
+ videoChannel: "34",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 5,
+ name: "零装线05",
+ detection: "人员违章违规",
+ alarmTask: "2",
+ videoChannel: "21",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 6,
+ name: "零装线06",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "26",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 7,
+ name: "零装线07",
+ detection: "PCB板表面检测",
+ alarmTask: "3",
+ videoChannel: "23",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 8,
+ name: "零装线08",
+ detection: "人员违章违规",
+ alarmTask: "5",
+ videoChannel: "20",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 9,
+ name: "零装线09",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "23",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 10,
+ name: "零装线10",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "21",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 11,
+ name: "零装线11",
+ detection: "PCB板表面检测",
+ alarmTask: "3",
+ videoChannel: "15",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 12,
+ name: "零装线12",
+ detection: "人员违章违规",
+ alarmTask: "3",
+ videoChannel: "16",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 13,
+ name: "零装线13",
+ detection: "PCB板表面检测",
+ alarmTask: "6",
+ videoChannel: "19",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 14,
+ name: "零装线14",
+ detection: "人员违章违规",
+ alarmTask: "4",
+ videoChannel: "24",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 15,
+ name: "零装线15",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "35",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 16,
+ name: "零装线16",
+ detection: "人员违章违规",
+ alarmTask: "2",
+ videoChannel: "20",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 17,
+ name: "零装线17",
+ detection: "PCB板表面检测",
+ alarmTask: "5",
+ videoChannel: "37",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 18,
+ name: "零装线18",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "28",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 19,
+ name: "零装线19",
+ detection: "人员违章违规",
+ alarmTask: "4",
+ videoChannel: "40",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ },
+ {
+ createTime: 1605456000000, // 时间戳(毫秒ms)
+ updateTime: 1684512000000,
+ creator: "admin",
+ id: 20,
+ name: "零装线20",
+ detection: "PCB板表面检测",
+ alarmTask: "4",
+ videoChannel: "20",
+ reportingStatus: "已上报",
+ alarmContent: "检测到表面缺陷"
+ }
+ ],
+ total: 20, // 总条目数
+ pageSize: 20, // 每页显示条目个数
+ currentPage: 1 // 当前页数
+ });
+
+ function handleDelete(row) {
+ message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" });
+ onSearch();
+ }
+
+ function handleSizeChange(val: number) {
+ console.log(`${val} items per page`);
+ }
+
+ function handleCurrentChange(val: number) {
+ console.log(`current page: ${val}`);
+ }
+
+ function handleSelectionChange(val) {
+ console.log("handleSelectionChange", val);
+ }
+
+ function getWarningList() {
+ // http.request("post", "/login")
+ return warnList;
+ }
+
+ async function onSearch() {
+ loading.value = true;
+ const data = getWarningList();
+ dataList.value = data.value.list;
+ pagination.total = data.value.total;
+ pagination.pageSize = data.value.pageSize;
+ pagination.currentPage = data.value.currentPage;
+ console.log(dataList.value);
+ setTimeout(() => {
+ loading.value = false;
+ }, 500);
+ }
+
+ const resetForm = formEl => {
+ if (!formEl) return;
+ formEl.resetFields();
+ onSearch();
+ };
+
+ /** 菜单权限 */
+ function handleMenu() {
+ message("等菜单管理页面开发后完善");
+ }
+
+ /** 数据权限 可自行开发 */
+ // function handleDatabase() {}
+
+ onMounted(() => {
+ onSearch();
+ });
+
+ return {
+ form,
+ loading,
+ columns,
+ dataList,
+ pagination,
+ onSearch,
+ resetForm,
+ handleMenu,
+ handleDelete,
+ // handleDatabase,
+ handleSizeChange,
+ handleCurrentChange,
+ handleSelectionChange
+ };
+}
diff --git a/src/views/welcome/index.vue b/src/views/welcome/index.vue
new file mode 100644
index 0000000..66538c9
--- /dev/null
+++ b/src/views/welcome/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
算法管理
+
+
diff --git a/stylelint.config.js b/stylelint.config.js
new file mode 100644
index 0000000..2a4d3ba
--- /dev/null
+++ b/stylelint.config.js
@@ -0,0 +1,84 @@
+module.exports = {
+ root: true,
+ extends: [
+ "stylelint-config-standard",
+ "stylelint-config-html/vue",
+ "stylelint-config-recess-order"
+ ],
+ plugins: ["stylelint-order", "stylelint-prettier", "stylelint-scss"],
+ overrides: [
+ {
+ files: ["**/*.(css|html|vue)"],
+ customSyntax: "postcss-html"
+ },
+ {
+ files: ["*.scss", "**/*.scss"],
+ customSyntax: "postcss-scss",
+ extends: [
+ "stylelint-config-standard-scss",
+ "stylelint-config-recommended-vue/scss"
+ ]
+ }
+ ],
+ rules: {
+ "selector-class-pattern": null,
+ "no-descending-specificity": null,
+ "scss/dollar-variable-pattern": null,
+ "selector-pseudo-class-no-unknown": [
+ true,
+ {
+ ignorePseudoClasses: ["deep", "global"]
+ }
+ ],
+ "selector-pseudo-element-no-unknown": [
+ true,
+ {
+ ignorePseudoElements: ["v-deep", "v-global", "v-slotted"]
+ }
+ ],
+ "at-rule-no-unknown": [
+ true,
+ {
+ ignoreAtRules: [
+ "tailwind",
+ "apply",
+ "variants",
+ "responsive",
+ "screen",
+ "function",
+ "if",
+ "each",
+ "include",
+ "mixin",
+ "use"
+ ]
+ }
+ ],
+ "rule-empty-line-before": [
+ "always",
+ {
+ ignore: ["after-comment", "first-nested"]
+ }
+ ],
+ "unit-no-unknown": [true, { ignoreUnits: ["rpx"] }],
+ "order/order": [
+ [
+ "dollar-variables",
+ "custom-properties",
+ "at-rules",
+ "declarations",
+ {
+ type: "at-rule",
+ name: "supports"
+ },
+ {
+ type: "at-rule",
+ name: "media"
+ },
+ "rules"
+ ],
+ { severity: "warning" }
+ ]
+ },
+ ignoreFiles: ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"]
+};
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..41e05b6
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,18 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: "class",
+ corePlugins: {
+ preflight: false
+ },
+ content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
+ theme: {
+ extend: {
+ colors: {
+ bg_color: "var(--el-bg-color)",
+ primary: "var(--el-color-primary)",
+ text_color_primary: "var(--el-text-color-primary)",
+ text_color_regular: "var(--el-text-color-regular)"
+ }
+ }
+ }
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..038dd01
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,42 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "moduleResolution": "Node",
+ "strict": false,
+ "jsx": "preserve",
+ "importHelpers": true,
+ "experimentalDecorators": true,
+ "strictFunctionTypes": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "isolatedModules": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "allowJs": false,
+ "resolveJsonModule": true,
+ "lib": ["dom", "esnext"],
+ "paths": {
+ "@/*": ["src/*"],
+ "@build/*": ["build/*"]
+ },
+ "types": [
+ "node",
+ "vite/client",
+ "element-plus/global",
+ "@pureadmin/table/volar",
+ "@pureadmin/descriptions/volar"
+ ]
+ },
+ "include": [
+ "mock/*.ts",
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "src/**/*.vue",
+ "types/*.d.ts",
+ "vite.config.ts"
+ ],
+ "exclude": ["dist", "**/*.js", "node_modules"]
+}
diff --git a/types/global-components.d.ts b/types/global-components.d.ts
new file mode 100644
index 0000000..bc85618
--- /dev/null
+++ b/types/global-components.d.ts
@@ -0,0 +1,124 @@
+declare module "vue" {
+ /**
+ * 自定义全局组件获得 Volar 提示(自定义的全局组件需要在这里声明下才能获得 Volar 类型提示哦)
+ */
+ export interface GlobalComponents {
+ IconifyIconOffline: typeof import("../src/components/ReIcon")["IconifyIconOffline"];
+ IconifyIconOnline: typeof import("../src/components/ReIcon")["IconifyIconOnline"];
+ FontIcon: typeof import("../src/components/ReIcon")["FontIcon"];
+ Auth: typeof import("../src/components/ReAuth")["Auth"];
+ }
+}
+
+/**
+ * TODO https://github.com/element-plus/element-plus/blob/dev/global.d.ts#L2
+ * No need to install @vue/runtime-core
+ */
+declare module "vue" {
+ export interface GlobalComponents {
+ ElAffix: typeof import("element-plus")["ElAffix"];
+ ElAlert: typeof import("element-plus")["ElAlert"];
+ ElAside: typeof import("element-plus")["ElAside"];
+ ElAutocomplete: typeof import("element-plus")["ElAutocomplete"];
+ ElAvatar: typeof import("element-plus")["ElAvatar"];
+ ElBacktop: typeof import("element-plus")["ElBacktop"];
+ ElBadge: typeof import("element-plus")["ElBadge"];
+ ElBreadcrumb: typeof import("element-plus")["ElBreadcrumb"];
+ ElBreadcrumbItem: typeof import("element-plus")["ElBreadcrumbItem"];
+ ElButton: typeof import("element-plus")["ElButton"];
+ ElButtonGroup: typeof import("element-plus")["ElButtonGroup"];
+ ElCalendar: typeof import("element-plus")["ElCalendar"];
+ ElCard: typeof import("element-plus")["ElCard"];
+ ElCarousel: typeof import("element-plus")["ElCarousel"];
+ ElCarouselItem: typeof import("element-plus")["ElCarouselItem"];
+ ElCascader: typeof import("element-plus")["ElCascader"];
+ ElCascaderPanel: typeof import("element-plus")["ElCascaderPanel"];
+ ElCheckbox: typeof import("element-plus")["ElCheckbox"];
+ ElCheckboxButton: typeof import("element-plus")["ElCheckboxButton"];
+ ElCheckboxGroup: typeof import("element-plus")["ElCheckboxGroup"];
+ ElCol: typeof import("element-plus")["ElCol"];
+ ElCollapse: typeof import("element-plus")["ElCollapse"];
+ ElCollapseItem: typeof import("element-plus")["ElCollapseItem"];
+ ElCollapseTransition: typeof import("element-plus")["ElCollapseTransition"];
+ ElColorPicker: typeof import("element-plus")["ElColorPicker"];
+ ElContainer: typeof import("element-plus")["ElContainer"];
+ ElConfigProvider: typeof import("element-plus")["ElConfigProvider"];
+ ElDatePicker: typeof import("element-plus")["ElDatePicker"];
+ ElDialog: typeof import("element-plus")["ElDialog"];
+ ElDivider: typeof import("element-plus")["ElDivider"];
+ ElDrawer: typeof import("element-plus")["ElDrawer"];
+ ElDropdown: typeof import("element-plus")["ElDropdown"];
+ ElDropdownItem: typeof import("element-plus")["ElDropdownItem"];
+ ElDropdownMenu: typeof import("element-plus")["ElDropdownMenu"];
+ ElEmpty: typeof import("element-plus")["ElEmpty"];
+ ElFooter: typeof import("element-plus")["ElFooter"];
+ ElForm: typeof import("element-plus")["ElForm"];
+ ElFormItem: typeof import("element-plus")["ElFormItem"];
+ ElHeader: typeof import("element-plus")["ElHeader"];
+ ElIcon: typeof import("element-plus")["ElIcon"];
+ ElImage: typeof import("element-plus")["ElImage"];
+ ElImageViewer: typeof import("element-plus")["ElImageViewer"];
+ ElInput: typeof import("element-plus")["ElInput"];
+ ElInputNumber: typeof import("element-plus")["ElInputNumber"];
+ ElLink: typeof import("element-plus")["ElLink"];
+ ElMain: typeof import("element-plus")["ElMain"];
+ ElMenu: typeof import("element-plus")["ElMenu"];
+ ElMenuItem: typeof import("element-plus")["ElMenuItem"];
+ ElMenuItemGroup: typeof import("element-plus")["ElMenuItemGroup"];
+ ElOption: typeof import("element-plus")["ElOption"];
+ ElOptionGroup: typeof import("element-plus")["ElOptionGroup"];
+ ElPageHeader: typeof import("element-plus")["ElPageHeader"];
+ ElPagination: typeof import("element-plus")["ElPagination"];
+ ElPopconfirm: typeof import("element-plus")["ElPopconfirm"];
+ ElPopper: typeof import("element-plus")["ElPopper"];
+ ElPopover: typeof import("element-plus")["ElPopover"];
+ ElProgress: typeof import("element-plus")["ElProgress"];
+ ElRadio: typeof import("element-plus")["ElRadio"];
+ ElRadioButton: typeof import("element-plus")["ElRadioButton"];
+ ElRadioGroup: typeof import("element-plus")["ElRadioGroup"];
+ ElRate: typeof import("element-plus")["ElRate"];
+ ElRow: typeof import("element-plus")["ElRow"];
+ ElScrollbar: typeof import("element-plus")["ElScrollbar"];
+ ElSelect: typeof import("element-plus")["ElSelect"];
+ ElSlider: typeof import("element-plus")["ElSlider"];
+ ElStep: typeof import("element-plus")["ElStep"];
+ ElSteps: typeof import("element-plus")["ElSteps"];
+ ElSubMenu: typeof import("element-plus")["ElSubMenu"];
+ ElSwitch: typeof import("element-plus")["ElSwitch"];
+ ElTabPane: typeof import("element-plus")["ElTabPane"];
+ ElTable: typeof import("element-plus")["ElTable"];
+ ElTableColumn: typeof import("element-plus")["ElTableColumn"];
+ ElTabs: typeof import("element-plus")["ElTabs"];
+ ElTag: typeof import("element-plus")["ElTag"];
+ ElTimePicker: typeof import("element-plus")["ElTimePicker"];
+ ElTimeSelect: typeof import("element-plus")["ElTimeSelect"];
+ ElTimeline: typeof import("element-plus")["ElTimeline"];
+ ElTimelineItem: typeof import("element-plus")["ElTimelineItem"];
+ ElTooltip: typeof import("element-plus")["ElTooltip"];
+ ElTransfer: typeof import("element-plus")["ElTransfer"];
+ ElTree: typeof import("element-plus")["ElTree"];
+ ElTreeV2: typeof import("element-plus")["ElTreeV2"];
+ ElUpload: typeof import("element-plus")["ElUpload"];
+ ElSpace: typeof import("element-plus")["ElSpace"];
+ ElSkeleton: typeof import("element-plus")["ElSkeleton"];
+ ElSkeletonItem: typeof import("element-plus")["ElSkeletonItem"];
+ ElCheckTag: typeof import("element-plus")["ElCheckTag"];
+ ElDescriptions: typeof import("element-plus")["ElDescriptions"];
+ ElDescriptionsItem: typeof import("element-plus")["ElDescriptionsItem"];
+ ElResult: typeof import("element-plus")["ElResult"];
+ ElSelectV2: typeof import("element-plus")["ElSelectV2"];
+ }
+
+ interface ComponentCustomProperties {
+ $message: typeof import("element-plus")["ElMessage"];
+ $notify: typeof import("element-plus")["ElNotification"];
+ $msgbox: typeof import("element-plus")["ElMessageBox"];
+ $messageBox: typeof import("element-plus")["ElMessageBox"];
+ $alert: typeof import("element-plus")["ElMessageBox"]["alert"];
+ $confirm: typeof import("element-plus")["ElMessageBox"]["confirm"];
+ $prompt: typeof import("element-plus")["ElMessageBox"]["prompt"];
+ $loading: typeof import("element-plus")["ElLoadingService"];
+ }
+}
+
+export {};
diff --git a/types/global.d.ts b/types/global.d.ts
new file mode 100644
index 0000000..3f17d2d
--- /dev/null
+++ b/types/global.d.ts
@@ -0,0 +1,160 @@
+import type {
+ VNode,
+ FunctionalComponent,
+ PropType as VuePropType,
+ ComponentPublicInstance
+} from "vue";
+import type { ECharts } from "echarts";
+import type { IconifyIcon } from "@iconify/vue";
+import type { TableColumns } from "@pureadmin/table";
+
+/**
+ * 全局类型声明,无需引入直接在 `.vue` 、`.ts` 、`.tsx` 文件使用即可获得类型提示
+ */
+declare global {
+ /**
+ * 平台的名称、版本、依赖、最后构建时间的类型提示
+ */
+ const __APP_INFO__: {
+ pkg: {
+ name: string;
+ version: string;
+ dependencies: Recordable;
+ devDependencies: Recordable;
+ };
+ lastBuildTime: string;
+ };
+
+ /**
+ * Window 的类型提示
+ */
+ interface Window {
+ // Global vue app instance
+ __APP__: App;
+ webkitCancelAnimationFrame: (handle: number) => void;
+ mozCancelAnimationFrame: (handle: number) => void;
+ oCancelAnimationFrame: (handle: number) => void;
+ msCancelAnimationFrame: (handle: number) => void;
+ webkitRequestAnimationFrame: (callback: FrameRequestCallback) => number;
+ mozRequestAnimationFrame: (callback: FrameRequestCallback) => number;
+ oRequestAnimationFrame: (callback: FrameRequestCallback) => number;
+ msRequestAnimationFrame: (callback: FrameRequestCallback) => number;
+ }
+
+ /**
+ * 打包压缩格式的类型声明
+ */
+ type ViteCompression =
+ | "none"
+ | "gzip"
+ | "brotli"
+ | "both"
+ | "gzip-clear"
+ | "brotli-clear"
+ | "both-clear";
+
+ /**
+ * 全局自定义环境变量的类型声明
+ * @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/config/#%E5%85%B7%E4%BD%93%E9%85%8D%E7%BD%AE}
+ */
+ interface ViteEnv {
+ VITE_PORT: number;
+ VITE_PUBLIC_PATH: string;
+ VITE_ROUTER_HISTORY: string;
+ VITE_CDN: boolean;
+ VITE_HIDE_HOME: string;
+ VITE_COMPRESSION: ViteCompression;
+ }
+
+ /**
+ * 继承 `@pureadmin/table` 的 `TableColumns` ,方便全局直接调用
+ */
+ interface TableColumnList extends Array {}
+
+ /**
+ * 对应 `public/serverConfig.json` 文件的类型声明
+ * @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/config/#serverconfig-json}
+ */
+ interface ServerConfigs {
+ Version?: string;
+ Title?: string;
+ FixedHeader?: boolean;
+ HiddenSideBar?: boolean;
+ MultiTagsCache?: boolean;
+ KeepAlive?: boolean;
+ Locale?: string;
+ Layout?: string;
+ Theme?: string;
+ DarkMode?: boolean;
+ Grey?: boolean;
+ Weak?: boolean;
+ HideTabs?: boolean;
+ SidebarStatus?: boolean;
+ EpThemeColor?: string;
+ ShowLogo?: boolean;
+ ShowModel?: string;
+ MenuArrowIconNoTransition?: boolean;
+ CachingAsyncRoutes?: boolean;
+ TooltipEffect?: Effect;
+ ResponsiveStorageNameSpace?: string;
+ }
+
+ /**
+ * 与 `ServerConfigs` 类型不同,这里是缓存到浏览器本地存储的类型声明
+ * @see {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/config/#serverconfig-json}
+ */
+ interface StorageConfigs {
+ version?: string;
+ title?: string;
+ fixedHeader?: boolean;
+ hiddenSideBar?: boolean;
+ multiTagsCache?: boolean;
+ keepAlive?: boolean;
+ locale?: string;
+ layout?: string;
+ theme?: string;
+ darkMode?: boolean;
+ grey?: boolean;
+ weak?: boolean;
+ hideTabs?: boolean;
+ sidebarStatus?: boolean;
+ epThemeColor?: string;
+ showLogo?: boolean;
+ showModel?: string;
+ username?: string;
+ }
+
+ /**
+ * `responsive-storage` 本地响应式 `storage` 的类型声明
+ */
+ interface ResponsiveStorage {
+ locale: {
+ locale?: string;
+ };
+ layout: {
+ layout?: string;
+ theme?: string;
+ darkMode?: boolean;
+ sidebarStatus?: boolean;
+ epThemeColor?: string;
+ };
+ configure: {
+ grey?: boolean;
+ weak?: boolean;
+ hideTabs?: boolean;
+ showLogo?: boolean;
+ showModel?: string;
+ multiTagsCache?: boolean;
+ };
+ tags?: Array;
+ }
+
+ /**
+ * 平台里所有组件实例都能访问到的全局属性对象的类型声明
+ */
+ interface GlobalPropertiesApi {
+ $echarts: ECharts;
+ $storage: ResponsiveStorage;
+ $config: ServerConfigs;
+ }
+}
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 0000000..404601a
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,80 @@
+// 此文件跟同级目录的 global.d.ts 文件一样也是全局类型声明,只不过这里存放一些零散的全局类型,无需引入直接在 .vue 、.ts 、.tsx 文件使用即可获得类型提示
+
+type RefType = T | null;
+
+type EmitType = (event: string, ...args: any[]) => void;
+
+type TargetContext = "_self" | "_blank";
+
+type ComponentRef =
+ ComponentElRef | null;
+
+type ElRef = Nullable;
+
+type ForDataType = {
+ [P in T]?: ForDataType;
+};
+
+type AnyFunction = (...args: any[]) => T;
+
+type PropType = VuePropType;
+
+type Writable = {
+ -readonly [P in keyof T]: T[P];
+};
+
+type Nullable = T | null;
+
+type NonNullable = T extends null | undefined ? never : T;
+
+type Recordable = Record;
+
+type ReadonlyRecordable = {
+ readonly [key: string]: T;
+};
+
+type Indexable = {
+ [key: string]: T;
+};
+
+type DeepPartial = {
+ [P in keyof T]?: DeepPartial;
+};
+
+type Without = { [P in Exclude]?: never };
+
+type Exclusive = (Without & U) | (Without & T);
+
+type TimeoutHandle = ReturnType;
+
+type IntervalHandle = ReturnType;
+
+type Effect = "light" | "dark";
+
+interface ChangeEvent extends Event {
+ target: HTMLInputElement;
+}
+
+interface WheelEvent {
+ path?: EventTarget[];
+}
+
+interface ImportMetaEnv extends ViteEnv {
+ __: unknown;
+}
+
+interface Fn {
+ (...arg: T[]): R;
+}
+
+interface PromiseFn {
+ (...arg: T[]): Promise;
+}
+
+interface ComponentElRef {
+ $el: T;
+}
+
+function parseInt(s: string | number, radix?: number): number;
+
+function parseFloat(string: string | number): number;
diff --git a/types/router.d.ts b/types/router.d.ts
new file mode 100644
index 0000000..6f6880a
--- /dev/null
+++ b/types/router.d.ts
@@ -0,0 +1,105 @@
+// 全局路由类型声明
+
+import { type RouteComponent, type RouteLocationNormalized } from "vue-router";
+
+declare global {
+ interface ToRouteType extends RouteLocationNormalized {
+ meta: CustomizeRouteMeta;
+ }
+
+ /**
+ * @description 完整子路由的`meta`配置表
+ */
+ interface CustomizeRouteMeta {
+ /** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加) `必填` */
+ title: string;
+ /** 菜单图标 `可选` */
+ icon?: string | FunctionalComponent | IconifyIcon;
+ /** 菜单名称右侧的额外图标 */
+ extraIcon?: string | FunctionalComponent | IconifyIcon;
+ /** 是否在菜单中显示(默认`true`)`可选` */
+ showLink?: boolean;
+ /** 是否显示父级菜单 `可选` */
+ showParent?: boolean;
+ /** 页面级别权限设置 `可选` */
+ roles?: Array;
+ /** 按钮级别权限设置 `可选` */
+ auths?: Array;
+ /** 路由组件缓存(开启 `true`、关闭 `false`)`可选` */
+ keepAlive?: boolean;
+ /** 内嵌的`iframe`链接 `可选` */
+ frameSrc?: string;
+ /** `iframe`页是否开启首次加载动画(默认`true`)`可选` */
+ frameLoading?: boolean;
+ /** 页面加载动画(有两种形式,一种直接采用vue内置的`transitions`动画,另一种是使用`animate.css`写进、离场动画)`可选` */
+ transition?: {
+ /**
+ * @description 当前路由动画效果
+ * @see {@link https://next.router.vuejs.org/guide/advanced/transitions.html#transitions}
+ * @see animate.css {@link https://animate.style}
+ */
+ name?: string;
+ /** 进场动画 */
+ enterTransition?: string;
+ /** 离场动画 */
+ leaveTransition?: string;
+ };
+ // 是否不添加信息到标签页,(默认`false`)
+ hiddenTag?: boolean;
+ /** 动态路由可打开的最大数量 `可选` */
+ dynamicLevel?: number;
+ /** 将某个菜单激活
+ * (主要用于通过`query`或`params`传参的路由,当它们通过配置`showLink: false`后不在菜单中显示,就不会有任何菜单高亮,
+ * 而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`)
+ */
+ activePath?: string;
+ }
+
+ /**
+ * @description 完整子路由配置表
+ */
+ interface RouteChildrenConfigsTable {
+ /** 子路由地址 `必填` */
+ path: string;
+ /** 路由名字(对应不要重复,和当前组件的`name`保持一致)`必填` */
+ name?: string;
+ /** 路由重定向 `可选` */
+ redirect?: string;
+ /** 按需加载组件 `可选` */
+ component?: RouteComponent;
+ meta?: CustomizeRouteMeta;
+ /** 子路由配置项 */
+ children?: Array;
+ }
+
+ /**
+ * @description 整体路由配置表(包括完整子路由)
+ */
+ interface RouteConfigsTable {
+ /** 路由地址 `必填` */
+ path: string;
+ /** 路由名字(保持唯一)`可选` */
+ name?: string;
+ /** `Layout`组件 `可选` */
+ component?: RouteComponent;
+ /** 路由重定向 `可选` */
+ redirect?: string;
+ meta?: {
+ /** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加)`必填` */
+ title: string;
+ /** 菜单图标 `可选` */
+ icon?: string | FunctionalComponent | IconifyIcon;
+ /** 是否在菜单中显示(默认`true`)`可选` */
+ showLink?: boolean;
+ /** 菜单升序排序,值越高排的越后(只针对顶级路由)`可选` */
+ rank?: number;
+ };
+ /** 子路由配置项 */
+ children?: Array;
+ }
+}
+
+// https://router.vuejs.org/zh/guide/advanced/meta.html#typescript
+declare module "vue-router" {
+ interface RouteMeta extends CustomizeRouteMeta {}
+}
diff --git a/types/shims-tsx.d.ts b/types/shims-tsx.d.ts
new file mode 100644
index 0000000..199f979
--- /dev/null
+++ b/types/shims-tsx.d.ts
@@ -0,0 +1,22 @@
+import Vue, { VNode } from "vue";
+
+declare module "*.tsx" {
+ import Vue from "compatible-vue";
+ export default Vue;
+}
+
+declare global {
+ namespace JSX {
+ interface Element extends VNode {}
+ interface ElementClass extends Vue {}
+ interface ElementAttributesProperty {
+ $props: any;
+ }
+ interface IntrinsicElements {
+ [elem: string]: any;
+ }
+ interface IntrinsicAttributes {
+ [elem: string]: any;
+ }
+ }
+}
diff --git a/types/shims-vue.d.ts b/types/shims-vue.d.ts
new file mode 100644
index 0000000..9fa8db3
--- /dev/null
+++ b/types/shims-vue.d.ts
@@ -0,0 +1,10 @@
+declare module "*.vue" {
+ import { DefineComponent } from "vue";
+ const component: DefineComponent<{}, {}, any>;
+ export default component;
+}
+
+declare module "*.scss" {
+ const scss: Record;
+ export default scss;
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..8a7a336
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,75 @@
+import dayjs from "dayjs";
+import { resolve } from "path";
+import pkg from "./package.json";
+import { warpperEnv } from "./build";
+import { getPluginsList } from "./build/plugins";
+import { include, exclude } from "./build/optimize";
+import { UserConfigExport, ConfigEnv, loadEnv } from "vite";
+
+/** 当前执行node命令时文件夹的地址(工作目录) */
+const root: string = process.cwd();
+
+/** 路径查找 */
+const pathResolve = (dir: string): string => {
+ return resolve(__dirname, ".", dir);
+};
+
+/** 设置别名 */
+const alias: Record = {
+ "@": pathResolve("src"),
+ "@build": pathResolve("build")
+};
+
+const { dependencies, devDependencies, name, version } = pkg;
+const __APP_INFO__ = {
+ pkg: { dependencies, devDependencies, name, version },
+ lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss")
+};
+
+export default ({ command, mode }: ConfigEnv): UserConfigExport => {
+ const { VITE_CDN, VITE_PORT, VITE_COMPRESSION, VITE_PUBLIC_PATH } =
+ warpperEnv(loadEnv(mode, root));
+ return {
+ base: VITE_PUBLIC_PATH,
+ root,
+ resolve: {
+ alias
+ },
+ // 服务端渲染
+ server: {
+ // 是否开启 https
+ https: false,
+ // 端口号
+ port: VITE_PORT,
+ host: "0.0.0.0",
+ // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
+ proxy: {}
+ },
+ plugins: getPluginsList(command, VITE_CDN, VITE_COMPRESSION),
+ // https://cn.vitejs.dev/config/dep-optimization-options.html#dep-optimization-options
+ optimizeDeps: {
+ include,
+ exclude
+ },
+ build: {
+ sourcemap: false,
+ // 消除打包大小超过500kb警告
+ chunkSizeWarningLimit: 4000,
+ rollupOptions: {
+ input: {
+ index: pathResolve("index.html")
+ },
+ // 静态资源分类打包
+ output: {
+ chunkFileNames: "static/js/[name]-[hash].js",
+ entryFileNames: "static/js/[name]-[hash].js",
+ assetFileNames: "static/[ext]/[name]-[hash].[ext]"
+ }
+ }
+ },
+ define: {
+ __INTLIFY_PROD_DEVTOOLS__: false,
+ __APP_INFO__: JSON.stringify(__APP_INFO__)
+ }
+ };
+};