diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..d451ff1
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..db1d3f8
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# 🎨 editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = tab
+indent_size = 4
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..3c018ee
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+vite.config.ts
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..783015b
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,64 @@
+module.exports = {
+ root: true,
+ env: {
+ browser: true,
+ node: true,
+ es6: true
+ },
+ parser: "vue-eslint-parser",
+ parserOptions: {
+ parser: "@typescript-eslint/parser",
+ ecmaVersion: 2020,
+ sourceType: "module",
+ jsxPragma: "React",
+ ecmaFeatures: {
+ jsx: true,
+ tsx: true
+ }
+ },
+ extends: [
+ "plugin:vue/vue3-recommended",
+ "plugin:@typescript-eslint/recommended",
+ "prettier",
+ "plugin:prettier/recommended"
+ ],
+ rules: {
+ "@typescript-eslint/ban-ts-ignore": "off",
+ "@typescript-eslint/explicit-function-return-type": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-var-requires": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "vue/component-name-in-template-casing": ["error", "kebab-case"],
+ "vue/component-definition-name-casing": ["error", "kebab-case"],
+ "no-use-before-define": "off",
+ "@typescript-eslint/no-use-before-define": "off",
+ "@typescript-eslint/ban-ts-comment": "off",
+ "@typescript-eslint/ban-types": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/explicit-module-boundary-types": "off",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^h$",
+ varsIgnorePattern: "^h$"
+ }
+ ],
+ "no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^h$",
+ varsIgnorePattern: "^h$"
+ }
+ ],
+ "space-before-function-paren": "off",
+ "vue/attributes-order": "off",
+ "vue/one-component-per-file": "off",
+ "vue/html-closing-bracket-newline": "off",
+ "vue/max-attributes-per-line": "off",
+ "vue/multiline-html-element-content-newline": "off",
+ "vue/singleline-html-element-content-newline": "off",
+ "vue/attribute-hyphenation": "off",
+ "vue/html-self-closing": "off",
+ "vue/require-default-prop": "off"
+ }
+};
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..e41187f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+*.js text eol=lf
+*.json text eol=lf
+*.ts text eol=lf
+*.vue text eol=lf
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d451ff1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..ee44e11
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "tabWidth": 4,
+ "useTabs": true,
+ "semi": true,
+ "singleQuote": false,
+ "printWidth": 100,
+ "trailingComma": "none"
+}
diff --git a/.vscode/crud.code-snippets b/.vscode/crud.code-snippets
new file mode 100644
index 0000000..228ae80
--- /dev/null
+++ b/.vscode/crud.code-snippets
@@ -0,0 +1,67 @@
+{
+ "cl-crud": {
+ "prefix": "cl-crud",
+ "body": [
+ "",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ "",
+ " ",
+ " ",
+ " ",
+ " ",
+ "",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ "",
+ " ",
+ " ",
+ " ",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "description": "cl-crud snippets"
+ }
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..48fc344
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "editor.cursorSmoothCaretAnimation": true
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..dc4247a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+FROM node:lts-alpine
+WORKDIR /build
+# 设置Node-Sass的镜像地址
+RUN npm config set sass_binary_site https://repo.huaweicloud.com/node-sass
+# 设置npm镜像
+RUN npm config set registry https://repo.huaweicloud.com/repository/npm/
+COPY package.json /build/package.json
+RUN npm install
+COPY ./ /build
+RUN npm run build
+
+FROM nginx
+RUN mkdir /app
+COPY --from=0 /build/dist /app
+COPY --from=0 /build/nginx.conf /etc/nginx/nginx.conf
+EXPOSE 80
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..83140ce
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 cool-team-official
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..699345f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# cool-admin [vue3 - ts - vite]
+
+
+
+
+
+cool-admin 一个很酷的后台权限管理系统,开源免费,模块化、插件化、极速开发 CRUD,方便快速构建迭代后台管理系统, 到文档 进一步了解
+
+
+
+
+
+
+
+## 地址
+
+- [⚡️ vue2.x + element-ui](https://github.com/cool-team-official/cool-admin-vue)
+
+- [⚡️ vue3.x + element-plus + ts + webpack](https://github.com/cool-team-official/cool-admin-vue/tree/vue3-ts-webpack)
+
+- [📌 vue3.x + element-plus + ts + vite](https://github.com/cool-team-official/cool-admin-vue/tree/vue3-ts-vite)
+
+- [🌐 码云仓库地址](https://gitee.com/cool-team-official/cool-admin-vue)
+
+## 演示
+
+[https://show.cool-admin.com](https://show.cool-admin.com)
+
+账户:admin,密码:123456
+
+
+
+## 项目后端
+
+[https://github.com/cool-team-official/cool-admin-midway](https://github.com/cool-team-official/cool-admin-midway)
+
+## 微信群
+
+
+
+## 微信公众号
+
+
+
+## 在线社区
+
+[https://bbs.cool-js.com/](https://bbs.cool-js.com/)
+
+## 安装项目依赖
+
+推荐使用 `yarn`:
+
+```shell
+yarn
+```
+
+解决 `node-sass` 网络慢的方法:
+
+```shell
+yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass
+```
+
+## 运行应用程序
+
+安装过程完成后,运行以下命令启动服务。您可以在浏览器中预览网站 [http://localhost:9000](http://localhost:9000)
+
+```shell
+yarn dev
+```
+
+### 服务器
+
+#### 腾讯云特供
+
+不限新老用户,注册过买过都可以享受
+
+|配置|价格|条件|备注|
+|---------|-------|-------|-------|
+|2核2g2M|一年240|个人企业限一台(不限新老用户)||
+|2核4g2M|一年260、两年380|个人企业限一台(不限新老用户)||
+|2核4g3M|一年260、三年600|企业(不限新老用户)||
+|2核4g5M|一年280、三年660|企业(不限新老用户)||
+|4核8g5M|一年320、三年720|企业(不限新老用户)||
+|4核8g10M|一年560、三年1520|企业(不限新老用户)||
+|8核16g5M|一年1800、三年3800|限企业新用户|送独立数据库|
+|8核16g10M|一年2200、三年6600|限企业新用户|送独立数据库|
+|16核32g5M|一年2600、三年6900|限企业新用户|送独立数据库|
+|16核32g10M|一年2900、三年9600|限企业新用户|送独立数据库|
+
+#### 购买咨询,数量有限!!!
+
+
+
+#### 阿里云
+
+[点击链接购买](https://www.aliyun.com/minisite/goods?userCode=pw6cig1f)
diff --git a/build/cool/index.ts b/build/cool/index.ts
new file mode 100644
index 0000000..ce29a7a
--- /dev/null
+++ b/build/cool/index.ts
@@ -0,0 +1,65 @@
+import { Plugin } from "vite";
+import { parseJson } from "./utils";
+import { getModules } from "./lib/modules";
+import { createEps, getEps } from "./lib/eps";
+import { createMenu } from "./lib/menu";
+
+export const cool = (): Plugin | null => {
+ return {
+ name: "vite-cool",
+ configureServer(server) {
+ server.middlewares.use(async (req, res, next) => {
+ function done(data: any) {
+ res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
+ res.end(JSON.stringify(data));
+ }
+
+ // 自定义
+ if (req.url.includes("__cool")) {
+ const body = await parseJson(req);
+ let next: any = null;
+
+ switch (req.url) {
+ // 快速创建菜单
+ case "/__cool_createMenu":
+ next = createMenu(body);
+ break;
+
+ // 获取模块列表
+ case "/__cool_modules":
+ next = getModules();
+ break;
+
+ // 创建描述文件
+ case "/__cool_eps":
+ next = createEps(body);
+ break;
+ }
+
+ if (next) {
+ next.then((data: any) => {
+ done({
+ code: 1000,
+ data
+ });
+ }).catch((message: string) => {
+ done({
+ code: 1001,
+ message
+ });
+ });
+ }
+ } else {
+ next();
+ }
+ });
+ },
+ config() {
+ return {
+ define: {
+ __EPS__: getEps()
+ }
+ };
+ }
+ };
+};
diff --git a/build/cool/lib/eps/index.ts b/build/cool/lib/eps/index.ts
new file mode 100644
index 0000000..9d995f9
--- /dev/null
+++ b/build/cool/lib/eps/index.ts
@@ -0,0 +1,219 @@
+import prettier from "prettier";
+import { isEmpty, last } from "lodash";
+import { createDir, firstUpperCase, readFile, toCamel } from "../../utils";
+import { createWriteStream } from "fs";
+import { join } from "path";
+
+// 临时目录路径
+const tempPath = join(__dirname, "../../temp");
+
+// 创建描述文件
+export async function createEps({ list, service }: any) {
+ const t0 = [
+ [
+ `
+ declare interface Crud {
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data: any): Promise;
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data: { ids?: number[] | string[]; [key: string]: any }): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data: { id?: number | string; [key: string]: any }): Promise;
+ /**
+ * 详情
+ * @returns Promise
+ */
+ info(data: { id?: number | string; [key: string]: any }): Promise;
+ /**
+ * 全部
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页
+ * @returns Promise
+ */
+ page(data?: { page?: number | string; size?: number | string; [key: string]: any }): Promise;
+ }
+ `,
+
+ `
+ declare interface PageResponse {
+ list: any[];
+ pagination: { size: number; page: number; total: number };
+ [key: string]: any;
+ }
+ `,
+
+ `
+ declare interface RequestOptions {
+ params?: any;
+ data?: any;
+ url: string;
+ method?: "GET" | "get" | "POST" | "post" | string;
+ [key: string]: any;
+ }
+ `
+ ]
+ ];
+
+ const t1 = [`declare type Service = {`, `request(data: RequestOptions): Promise;`];
+
+ // 处理数据
+ function deep(d: any, k?: string) {
+ if (!k) k = "";
+
+ for (const i in d) {
+ const name = k + toCamel(firstUpperCase(i.replace(/[:]/g, "")));
+
+ if (d[i].namespace) {
+ // 查找配置
+ const item = list.find((e: any) => (e.prefix || "").includes(d[i].namespace));
+
+ if (item) {
+ const t = [
+ `declare interface ${name} ${item.extendCrud ? " extends Crud" : ""} {`
+ ];
+
+ t1.push(`${i}: ${name};`);
+
+ // 插入方法
+ if (item.api) {
+ // 权限列表
+ const permission: string[] = [];
+
+ item.api.forEach((a: any) => {
+ // 方法名
+ const n = toCamel(a.name || last(a.path.split("/"))).replace(
+ /[:\/-]/g,
+ ""
+ );
+
+ if (n) {
+ // 参数类型
+ let q: string[] = [];
+
+ // 参数列表
+ const { parameters = [] } = a.dts || {};
+
+ parameters.forEach((p: any) => {
+ if (p.description) {
+ q.push(`\n/** ${p.description} */\n`);
+ }
+
+ if (p.name.includes(":")) {
+ return false;
+ }
+
+ const a = `${p.name}${p.required ? "" : "?"}`;
+ const b = `${p.schema.type || "string"}`;
+
+ q.push(`${a}: ${b},`);
+ });
+
+ if (isEmpty(q)) {
+ q = ["any"];
+ } else {
+ q.unshift("{");
+ q.push("}");
+ }
+
+ // 返回类型
+ let res = "";
+
+ switch (a.path) {
+ case "/page":
+ res = "PageResponse";
+ break;
+ default:
+ res = "any";
+ break;
+ }
+
+ // 描述
+ t.push("\n");
+ t.push("/**\n");
+ t.push(` * ${a.summary || n}\n`);
+ t.push(` * @returns Promise<${res}>\n`);
+ t.push(" */\n");
+
+ t.push(
+ `${n}(data${q.length == 1 ? "?" : ""}: ${q.join(
+ ""
+ )}): Promise<${res}>;`
+ );
+ }
+
+ permission.push(`${n}: string;`);
+ });
+
+ // 添加权限
+ t.push("\n");
+ t.push("/**\n");
+ t.push(" * 权限\n");
+ t.push(" */\n");
+ t.push(`permission: { ${permission.join("\n")} }`);
+ }
+
+ t.push("}");
+ t0.push(t);
+ }
+ } else {
+ t1.push(`${i}: {`);
+ deep(d[i], name);
+ t1.push(`},`);
+ }
+ }
+ }
+
+ deep(service);
+ t1.push("}");
+
+ // 追加
+ t0.push(t1);
+
+ // 文本内容
+ const content = prettier.format(t0.map((e) => e.join("")).join("\n\n"), {
+ parser: "typescript",
+ useTabs: true,
+ tabWidth: 4,
+ endOfLine: "lf",
+ semi: true,
+ singleQuote: false,
+ printWidth: 100,
+ trailingComma: "none"
+ });
+
+ // 创建 temp 目录
+ createDir(tempPath);
+
+ // 创建 service 描述文件
+ createWriteStream(join(tempPath, "service.d.ts"), {
+ flags: "w"
+ }).write(content);
+
+ // 创建 eps 文件
+ createWriteStream(join(tempPath, "eps.json"), {
+ flags: "w"
+ }).write(
+ JSON.stringify(
+ list.map((e: any) => {
+ return [e.prefix, e.api.map((a: any) => [a.method || "", a.path, a.name || ""])];
+ })
+ )
+ );
+}
+
+// 获取描述
+export function getEps() {
+ return JSON.stringify(readFile(join(tempPath, "eps.json")));
+}
diff --git a/build/cool/lib/menu/index.ts b/build/cool/lib/menu/index.ts
new file mode 100644
index 0000000..74a7a72
--- /dev/null
+++ b/build/cool/lib/menu/index.ts
@@ -0,0 +1,360 @@
+import { createWriteStream } from "fs";
+import prettier from "prettier";
+import { join } from "path";
+import { createDir } from "../../utils";
+import rules from "./rules";
+import { isFunction, isRegExp, isString } from "lodash";
+
+// 格式化
+function format(data: any) {
+ return {
+ label: data.label,
+ prop: data.prop,
+ ...data,
+ component: data.component
+ };
+}
+
+// 颜色
+const colors = [
+ "#409EFF",
+ "#67C23A",
+ "#E6A23C",
+ "#F56C6C",
+ "#909399",
+ "#B0CFEB",
+ "#FF9B91",
+ "#E6A23C",
+ "#BFAD6F",
+ "#FB78F2"
+];
+
+// 组件处理器
+const handler = {
+ // 单选
+ dict({ comment }) {
+ const [label, ...arr] = comment.split(" ");
+
+ // 选择列表
+ const list = arr.map((e: string, i: number) => {
+ const [value, label] = e.split("-");
+ const d: any = {
+ label,
+ value: isNaN(Number(value)) ? value : Number(value)
+ };
+
+ if (i > 0 && colors[i]) {
+ d.color = colors[i];
+ }
+
+ return d;
+ });
+
+ const d: any = {
+ table: {
+ label,
+ dict: list
+ },
+ form: {
+ label,
+ component: {
+ name: "",
+ options: list
+ }
+ }
+ };
+
+ // 默认值
+ if (list[0]) {
+ d.form.value = list[0].value;
+ }
+
+ // 匹配组件
+ d.form.component.name = arr.length > 4 ? "el-select" : "el-radio-group";
+
+ return d;
+ },
+
+ // 多选
+ dict_multiple({ comment }) {
+ const { table, form }: any = handler.dict({ comment });
+
+ if (!form.component.props) {
+ form.component.props = {};
+ }
+
+ if (!form.value) {
+ form.value = [];
+ }
+
+ switch (form.component.name) {
+ case "el-select":
+ form.component.props.multiple = true;
+ form.component.props.filterable = true;
+ break;
+ case "el-radio-group":
+ form.component.name = "el-checkbox-group";
+ break;
+ }
+
+ return {
+ table,
+ form
+ };
+ }
+};
+
+// 创建组件
+function createComponent(item: any) {
+ const { propertyName: prop, comment: label } = item;
+
+ let d = null;
+
+ rules.forEach((r: any) => {
+ const s = r.test.find((e: any) => {
+ if (isRegExp(e)) {
+ return e.test(prop);
+ }
+
+ if (isFunction(e)) {
+ return e(prop);
+ }
+
+ if (isString(e)) {
+ const re = new RegExp(`${e}$`);
+ return re.test(prop.toLocaleLowerCase());
+ }
+
+ return false;
+ });
+
+ if (s) {
+ if (r.handler) {
+ const fn = isString(r.handler) ? handler[r.handler] : r.handler;
+
+ if (isFunction(fn)) {
+ d = fn(item);
+ }
+ } else {
+ d = {
+ ...r,
+ test: undefined
+ };
+ }
+ }
+ });
+
+ function parse(v: any) {
+ if (v?.name) {
+ return {
+ prop,
+ label,
+ component: v
+ };
+ } else {
+ return {
+ prop,
+ label,
+ ...v
+ };
+ }
+ }
+
+ return {
+ column: parse(d?.table),
+ item: parse(d?.form)
+ };
+}
+
+// 获取页面标识
+function getPageName(router: string) {
+ if (router.indexOf("/") === 0) {
+ router = router.substr(1, router.length);
+ }
+
+ return router ? router.replace("/", "-") : "";
+}
+
+// 时间合并
+function datetimeMerge({ columns, item }: any) {
+ if (["startTime", "startDate"].includes(item.prop)) {
+ const key = item.prop.replace("start", "");
+
+ if (columns.find((e: any) => e.propertyName == "end" + key)) {
+ item.prop = key.toLocaleLowerCase();
+ const isTime = item.prop == "time";
+ item.label = isTime ? "时间范围" : "日期范围";
+ item.hook = "datetimeRange";
+ item.component = {
+ name: "el-date-picker",
+ props: {
+ type: isTime ? "datetimerange" : "daterange",
+ valueFormat: isTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD 00:00:00",
+ defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]
+ }
+ };
+ }
+ }
+}
+
+// 创建文件
+export async function createMenu({ router, columns, prefix, api, module, filename }: any): void {
+ const upsert: any = {
+ items: []
+ };
+
+ const table: any = {
+ columns: []
+ };
+
+ // 遍历
+ columns.forEach((e: any) => {
+ // 组件
+ const { item, column }: any = createComponent(e);
+
+ // 验证规则
+ if (!e.nullable) {
+ item.required = true;
+ }
+
+ // 忽略部分字段
+ if (!["createTime", "updateTime", "id", "endTime", "endDate"].includes(item.prop)) {
+ datetimeMerge({ columns, item });
+
+ if (!item.component) {
+ item.component = {
+ name: "el-input"
+ };
+ }
+
+ upsert.items.push(format(item));
+ }
+
+ if (!["cl-codemirror", "cl-editor-quill"].includes(column.component?.name)) {
+ table.columns.push(format(column));
+ }
+ });
+
+ // 服务
+ const service = prefix.replace("/admin", "service").replace(/\//g, ".");
+
+ // 请求路径
+ const paths = api.map((e: any) => e.path);
+
+ // 权限
+ const permission: any = {
+ add: paths.includes("/add"),
+ del: paths.includes("/delete"),
+ update: paths.includes("/info") && paths.includes("/update"),
+ page: paths.includes("/page"),
+ upsert: true
+ };
+ permission.upsert = permission.add || permission.update;
+
+ // 是否有操作栏
+ if (permission.del || permission.upsert) {
+ const d: any = {
+ type: "op",
+ buttons: []
+ };
+
+ if (permission.upsert) {
+ d.buttons.push("edit");
+ }
+
+ if (permission.del) {
+ d.buttons.push("delete");
+ }
+
+ table.columns.push(d);
+ }
+
+ // 是否多选、序号
+ if (permission.del) {
+ table.columns.unshift({
+ type: "selection"
+ });
+ } else {
+ table.columns.unshift({
+ label: "#",
+ type: "index"
+ });
+ }
+
+ // 代码模板
+ const temp = `
+
+
+
+
+ ${permission.add ? "\n" : ""}
+ ${permission.del ? "\n" : ""}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+ const content = prettier.format(temp, {
+ parser: "vue",
+ useTabs: true,
+ tabWidth: 4,
+ endOfLine: "lf",
+ semi: true,
+ jsxBracketSameLine: true,
+ singleQuote: false,
+ printWidth: 100,
+ trailingComma: "none"
+ });
+
+ // views 目录是否存在
+ const dir = join(__dirname, `../../../../src/modules/${module}/views`);
+
+ // 创建目录
+ createDir(dir);
+
+ // 创建文件
+ createWriteStream(join(dir, `${filename}.vue`), {
+ flags: "w"
+ }).write(content);
+}
diff --git a/build/cool/lib/menu/rules.ts b/build/cool/lib/menu/rules.ts
new file mode 100644
index 0000000..0541686
--- /dev/null
+++ b/build/cool/lib/menu/rules.ts
@@ -0,0 +1,214 @@
+export default [
+ {
+ test: ["avatar", "img", "image", "pic", "photo", "picture", "head", "icon"],
+ table: {
+ name: "cl-image",
+ props: {
+ size: 60
+ }
+ },
+ form: {
+ name: "cl-upload"
+ }
+ },
+ {
+ test: ["avatars", "imgs", "images", "pics", "photos", "pictures", "heads", "icons"],
+ table: {
+ name: "cl-image",
+ props: {
+ size: 60
+ }
+ },
+ form: {
+ name: "cl-upload",
+ props: {
+ listType: "picture-card",
+ multiple: true
+ }
+ }
+ },
+ {
+ test: ["file", "attachment", "attach", "url", "video", "music"],
+ table: {
+ name: "cl-link"
+ },
+ form: {
+ name: "cl-upload",
+ props: {
+ listType: "text",
+ limit: 1
+ }
+ }
+ },
+ {
+ test: ["files", "attachments", "attachs", "urls", "videos", "musics"],
+ table: {
+ name: "cl-link"
+ },
+ form: {
+ name: "cl-upload",
+ props: {
+ listType: "text",
+ multiple: true
+ }
+ }
+ },
+ {
+ test: ["enable", "status"],
+ table: {
+ name: "cl-switch"
+ },
+ form: {
+ name: "el-switch"
+ }
+ },
+ {
+ test: ["type", "classify", "category"],
+ handler: "dict"
+ },
+ {
+ test: ["types", "classifys", "categorys"],
+ handler: "dict_multiple"
+ },
+ {
+ test: ["date"],
+ table: {
+ name: "cl-date-text",
+ props: {
+ format: "YYYY-MM-DD"
+ }
+ },
+ form: {
+ name: "el-date-picker",
+ props: {
+ type: "date",
+ valueFormat: "YYYY-MM-DD"
+ }
+ }
+ },
+ {
+ test: ["dates", "dateRange", "dateScope"],
+ table: {
+ name: "cl-date-text",
+ props: {
+ format: "YYYY-MM-DD"
+ }
+ },
+ form: {
+ component: {
+ name: "el-date-picker",
+ props: {
+ type: "daterange",
+ valueFormat: "YYYY-MM-DD"
+ }
+ }
+ }
+ },
+ {
+ test: ["time"],
+ form: {
+ name: "el-date-picker",
+ props: {
+ type: "datetime",
+ valueFormat: "YYYY-MM-DD HH:mm:ss"
+ }
+ }
+ },
+ {
+ test: ["times", "timeRange", "timeScope"],
+ form: {
+ component: {
+ name: "el-date-picker",
+ props: {
+ type: "datetimerange",
+ valueFormat: "YYYY-MM-DD HH:mm:ss",
+ defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]
+ }
+ }
+ }
+ },
+ {
+ test: ["star", "stars"],
+ table: {
+ name: "el-rate",
+ props: {
+ disabled: true
+ }
+ },
+ form: {
+ name: "el-rate"
+ }
+ },
+ {
+ test: ["progress", "rate", "ratio"],
+ table: {
+ name: "el-progress"
+ },
+ form: {
+ name: "el-slider",
+ props: {
+ style: {
+ width: "200px"
+ }
+ }
+ }
+ },
+ {
+ test: ["num", "price", "age", "amount"],
+ form: {
+ name: "el-input-number",
+ props: {
+ min: 0
+ }
+ }
+ },
+ {
+ test: ["remark", "desc"],
+ table: {
+ showOverflowTooltip: true
+ },
+ form: {
+ name: "el-input",
+ props: {
+ type: "textarea",
+ rows: 4
+ }
+ }
+ },
+ {
+ test: ["rich", "text", "html", "content", "introduce", "description", "desc"],
+ table: {
+ name: "cl-editor-quill"
+ },
+ form: {
+ name: "cl-editor-quill",
+ props: {
+ height: 400
+ }
+ }
+ },
+ {
+ test: ["code", "codes"],
+ table: {
+ name: "cl-codemirror"
+ },
+ form: {
+ name: "cl-codemirror",
+ props: {
+ height: 400
+ }
+ }
+ },
+ {
+ test: ["createTime"],
+ table: {
+ sortable: "desc"
+ }
+ },
+ {
+ test: ["updateTime"],
+ table: {
+ sortable: "custom"
+ }
+ }
+];
diff --git a/build/cool/lib/modules/index.ts b/build/cool/lib/modules/index.ts
new file mode 100644
index 0000000..be30628
--- /dev/null
+++ b/build/cool/lib/modules/index.ts
@@ -0,0 +1,11 @@
+import fs from "fs";
+import { join } from "path";
+
+export function getModules() {
+ try {
+ const dirs = fs.readdirSync(join(__dirname, "../../../../src/modules"));
+ return Promise.resolve(dirs.filter((e) => !e.includes(".")));
+ } catch (e) {
+ return Promise.reject(e);
+ }
+}
diff --git a/build/cool/temp/eps.json b/build/cool/temp/eps.json
new file mode 100644
index 0000000..e6c9392
--- /dev/null
+++ b/build/cool/temp/eps.json
@@ -0,0 +1 @@
+[["/admin/base/comm",[["post","/personUpdate",""],["get","/uploadMode",""],["get","/permmenu",""],["get","/person",""],["post","/upload",""],["post","/logout",""],["","/list",""],["","/page",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]],["/admin/base/open",[["get","/refreshToken",""],["get","/captcha",""],["post","/login",""],["get","/html",""],["get","/eps",""],["","/list",""],["","/page",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]],["/admin/base/sys/department",[["post","/delete",""],["post","/update",""],["post","/order",""],["post","/list",""],["post","/add",""],["","/page",""],["","/info",""]]],["/admin/base/sys/log",[["post","/setKeep",""],["get","/getKeep",""],["post","/clear",""],["post","/page",""],["","/list",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]],["/admin/base/sys/menu",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/base/sys/param",[["post","/delete",""],["post","/update",""],["get","/html",""],["get","/info",""],["post","/page",""],["post","/add",""],["","/list",""]]],["/admin/base/sys/role",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/base/sys/user",[["post","/delete",""],["post","/update",""],["post","/move",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/demo/goods",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/page",""],["post","/list",""],["post","/add",""]]],["/admin/space/info",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/space/type",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/task/info",[["post","/delete",""],["post","/update",""],["post","/start",""],["post","/once",""],["post","/stop",""],["get","/info",""],["post","/page",""],["get","/log",""],["post","/add",""],["","/list",""]]],["/test",[["","/list",""],["","/page",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]]]
\ No newline at end of file
diff --git a/build/cool/temp/service.d.ts b/build/cool/temp/service.d.ts
new file mode 100644
index 0000000..3733eff
--- /dev/null
+++ b/build/cool/temp/service.d.ts
@@ -0,0 +1,768 @@
+declare interface Crud {
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data: any): Promise;
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data: { ids?: number[] | string[]; [key: string]: any }): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data: { id?: number | string; [key: string]: any }): Promise;
+ /**
+ * 详情
+ * @returns Promise
+ */
+ info(data: { id?: number | string; [key: string]: any }): Promise;
+ /**
+ * 全部
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页
+ * @returns Promise
+ */
+ page(data?: {
+ page?: number | string;
+ size?: number | string;
+ [key: string]: any;
+ }): Promise;
+}
+
+declare interface PageResponse {
+ list: any[];
+ pagination: { size: number; page: number; total: number };
+ [key: string]: any;
+}
+
+declare interface RequestOptions {
+ params?: any;
+ data?: any;
+ url: string;
+ method?: "GET" | "get" | "POST" | "post" | string;
+ [key: string]: any;
+}
+
+declare interface BaseComm {
+ /**
+ * 修改个人信息
+ * @returns Promise
+ */
+ personUpdate(data?: any): Promise;
+ /**
+ * 文件上传模式
+ * @returns Promise
+ */
+ uploadMode(data?: any): Promise;
+ /**
+ * 权限与菜单
+ * @returns Promise
+ */
+ permmenu(data?: any): Promise;
+ /**
+ * 个人信息
+ * @returns Promise
+ */
+ person(data?: any): Promise;
+ /**
+ * 文件上传
+ * @returns Promise
+ */
+ upload(data?: any): Promise;
+ /**
+ * 退出
+ * @returns Promise
+ */
+ logout(data?: any): Promise;
+ /**
+ * list
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * page
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * info
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * update
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * delete
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * add
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ personUpdate: string;
+ uploadMode: string;
+ permmenu: string;
+ person: string;
+ upload: string;
+ logout: string;
+ list: string;
+ page: string;
+ info: string;
+ update: string;
+ delete: string;
+ add: string;
+ };
+}
+
+declare interface BaseOpen {
+ /**
+ * 刷新token
+ * @returns Promise
+ */
+ refreshToken(data?: any): Promise;
+ /**
+ * 验证码
+ * @returns Promise
+ */
+ captcha(data?: any): Promise;
+ /**
+ * 登录
+ * @returns Promise
+ */
+ login(data?: any): Promise;
+ /**
+ * 获得网页内容的参数值
+ * @returns Promise
+ */
+ html(data?: any): Promise;
+ /**
+ * 实体信息与路径
+ * @returns Promise
+ */
+ eps(data?: any): Promise;
+ /**
+ * list
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * page
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * info
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * update
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * delete
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * add
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ refreshToken: string;
+ captcha: string;
+ login: string;
+ html: string;
+ eps: string;
+ list: string;
+ page: string;
+ info: string;
+ update: string;
+ delete: string;
+ add: string;
+ };
+}
+
+declare interface BaseSysDepartment {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 排序
+ * @returns Promise
+ */
+ order(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * page
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * info
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ order: string;
+ list: string;
+ add: string;
+ page: string;
+ info: string;
+ };
+}
+
+declare interface BaseSysLog {
+ /**
+ * 日志保存时间
+ * @returns Promise
+ */
+ setKeep(data?: any): Promise;
+ /**
+ * 获得日志保存时间
+ * @returns Promise
+ */
+ getKeep(data?: any): Promise;
+ /**
+ * 清理
+ * @returns Promise
+ */
+ clear(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * list
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * info
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * update
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * delete
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * add
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ setKeep: string;
+ getKeep: string;
+ clear: string;
+ page: string;
+ list: string;
+ info: string;
+ update: string;
+ delete: string;
+ add: string;
+ };
+}
+
+declare interface BaseSysMenu {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ info: string;
+ list: string;
+ page: string;
+ add: string;
+ };
+}
+
+declare interface BaseSysParam {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 获得网页内容的参数值
+ * @returns Promise
+ */
+ html(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * list
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ html: string;
+ info: string;
+ page: string;
+ add: string;
+ list: string;
+ };
+}
+
+declare interface BaseSysRole {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ info: string;
+ list: string;
+ page: string;
+ add: string;
+ };
+}
+
+declare interface BaseSysUser {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 移动部门
+ * @returns Promise
+ */
+ move(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ move: string;
+ info: string;
+ list: string;
+ page: string;
+ add: string;
+ };
+}
+
+declare interface DemoGoods {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ info: string;
+ page: string;
+ list: string;
+ add: string;
+ };
+}
+
+declare interface SpaceInfo {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ info: string;
+ list: string;
+ page: string;
+ add: string;
+ };
+}
+
+declare interface SpaceType {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 列表查询
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ info: string;
+ list: string;
+ page: string;
+ add: string;
+ };
+}
+
+declare interface TaskInfo {
+ /**
+ * 删除
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * 修改
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * 开始
+ * @returns Promise
+ */
+ start(data?: any): Promise;
+ /**
+ * 执行一次
+ * @returns Promise
+ */
+ once(data?: any): Promise;
+ /**
+ * 停止
+ * @returns Promise
+ */
+ stop(data?: any): Promise;
+ /**
+ * 单个信息
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * 分页查询
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * 日志
+ * @returns Promise
+ */
+ log(data?: any): Promise;
+ /**
+ * 新增
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * list
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ delete: string;
+ update: string;
+ start: string;
+ once: string;
+ stop: string;
+ info: string;
+ page: string;
+ log: string;
+ add: string;
+ list: string;
+ };
+}
+
+declare interface Test {
+ /**
+ * list
+ * @returns Promise
+ */
+ list(data?: any): Promise;
+ /**
+ * page
+ * @returns Promise
+ */
+ page(data?: any): Promise;
+ /**
+ * info
+ * @returns Promise
+ */
+ info(data?: any): Promise;
+ /**
+ * update
+ * @returns Promise
+ */
+ update(data?: any): Promise;
+ /**
+ * delete
+ * @returns Promise
+ */
+ delete(data?: any): Promise;
+ /**
+ * add
+ * @returns Promise
+ */
+ add(data?: any): Promise;
+ /**
+ * 权限
+ */
+ permission: {
+ list: string;
+ page: string;
+ info: string;
+ update: string;
+ delete: string;
+ add: string;
+ };
+}
+
+declare type Service = {
+ request(data: RequestOptions): Promise;
+ base: {
+ comm: BaseComm;
+ open: BaseOpen;
+ sys: {
+ department: BaseSysDepartment;
+ log: BaseSysLog;
+ menu: BaseSysMenu;
+ param: BaseSysParam;
+ role: BaseSysRole;
+ user: BaseSysUser;
+ };
+ };
+ demo: { goods: DemoGoods };
+ space: { info: SpaceInfo; type: SpaceType };
+ task: { info: TaskInfo };
+ test: Test;
+};
diff --git a/build/cool/utils/index.ts b/build/cool/utils/index.ts
new file mode 100644
index 0000000..9c1184e
--- /dev/null
+++ b/build/cool/utils/index.ts
@@ -0,0 +1,46 @@
+import fs from "fs";
+
+// 首字母大写
+export function firstUpperCase(value: string): string {
+ return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
+ return $1.toUpperCase() + $2;
+ });
+}
+
+// 横杠转驼峰
+export function toCamel(str: string): string {
+ return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
+ return $1 + $2.toUpperCase();
+ });
+}
+
+// 创建目录
+export function createDir(path: string) {
+ if (!fs.existsSync(path)) fs.mkdirSync(path);
+}
+
+// 读取文件
+export function readFile(name: string) {
+ try {
+ return fs.readFileSync(name, "utf8");
+ } catch (e) {}
+
+ return "";
+}
+
+// 解析body
+export function parseJson(req: any) {
+ return new Promise((resolve) => {
+ let d = "";
+ req.on("data", function (chunk: Buffer) {
+ d += chunk;
+ });
+ req.on("end", function () {
+ try {
+ resolve(JSON.parse(d));
+ } catch {
+ resolve({});
+ }
+ });
+ });
+}
diff --git a/build/svg/index.ts b/build/svg/index.ts
new file mode 100644
index 0000000..9640753
--- /dev/null
+++ b/build/svg/index.ts
@@ -0,0 +1,71 @@
+import { Plugin } from "vite";
+import { readFileSync, readdirSync } from "fs";
+
+let idPerfix = "";
+const svgTitle = /", "");
+ svgRes.push(svg);
+ }
+ }
+ return svgRes;
+}
+
+export const svgBuilder = (path: string, perfix = "icon"): Plugin | null => {
+ if (path !== "") {
+ idPerfix = perfix;
+ const res = findSvgFile(path);
+ return {
+ name: "svg-transform",
+ transformIndexHtml(html): string {
+ return html.replace(
+ "",
+ `
+
+
+ `
+ );
+ }
+ };
+ } else {
+ return null;
+ }
+};
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f74d157
--- /dev/null
+++ b/index.html
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+ COOL-ADMIN
+
+
+
+
+
+
+
+
COOL-ADMIN
+
+
正在加载资源...
+
初次加载资源可能需要较多时间 请耐心等待
+
+
+
+
+
+
+
+
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..dbc3612
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,123 @@
+user nginx;
+worker_processes 1;
+error_log /var/log/nginx/error.log warn;
+pid /var/run/nginx.pid;
+events {
+ worker_connections 1024;
+}
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+ access_log /var/log/nginx/access.log main;
+ sendfile on;
+ keepalive_timeout 65;
+ upstream backend {
+ server midway:7001;
+ }
+
+ server {
+ listen 80;
+ server_name localhost;
+ location / {
+ root /app;
+ index index.html;
+ try_files $uri $uri/ /index.html;
+ }
+ location /api/
+ {
+ proxy_pass http://backend/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header REMOTE-HOST $remote_addr;
+
+ #缓存相关配置
+ #proxy_cache cache_one;
+ #proxy_cache_key $host$request_uri$is_args$args;
+ #proxy_cache_valid 200 304 301 302 1h;
+
+ #持久化连接相关配置
+ proxy_connect_timeout 3000s;
+ proxy_read_timeout 86400s;
+ proxy_send_timeout 3000s;
+ #proxy_http_version 1.1;
+ #proxy_set_header Upgrade $http_upgrade;
+ #proxy_set_header Connection "upgrade";
+
+ add_header X-Cache $upstream_cache_status;
+
+ #expires 12h;
+ }
+ location /im {
+ proxy_pass http://backend/im;
+ proxy_connect_timeout 3600s; #配置点1
+ proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
+ proxy_send_timeout 3600s; #配置点3
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header REMOTE-HOST $remote_addr;
+ #proxy_bind $remote_addr transparent;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ # rewrite /socket/(.*) /$1 break;
+ proxy_redirect off;
+
+ }
+
+ location /socket {
+ proxy_pass http://backend/socket;
+ proxy_connect_timeout 3600s; #配置点1
+ proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
+ proxy_send_timeout 3600s; #配置点3
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header REMOTE-HOST $remote_addr;
+ #proxy_bind $remote_addr transparent;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ rewrite /socket/(.*) /$1 break;
+ proxy_redirect off;
+
+ }
+
+
+ location /adminer/
+ {
+ proxy_pass http://adminer:8080/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header REMOTE-HOST $remote_addr;
+
+ #缓存相关配置
+ #proxy_cache cache_one;
+ #proxy_cache_key $host$request_uri$is_args$args;
+ #proxy_cache_valid 200 304 301 302 1h;
+
+ #持久化连接相关配置
+ proxy_connect_timeout 3000s;
+ proxy_read_timeout 86400s;
+ proxy_send_timeout 3000s;
+ #proxy_http_version 1.1;
+ #proxy_set_header Upgrade $http_upgrade;
+ #proxy_set_header Connection "upgrade";
+
+ add_header X-Cache $upstream_cache_status;
+
+ #expires 12h;
+ }
+
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b898c8b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "front-next",
+ "version": "5.2.1",
+ "scripts": {
+ "dev": "vite --host",
+ "build": "vite build",
+ "serve": "vite preview",
+ "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
+ "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix"
+ },
+ "dependencies": {
+ "@cool-vue/crud": "^5.0.7",
+ "@element-plus/icons-vue": "^1.1.3",
+ "@vueuse/core": "^8.2.5",
+ "axios": "^0.27.2",
+ "codemirror": "^5.62.0",
+ "core-js": "^3.6.5",
+ "echarts": "^5.0.2",
+ "element-plus": "^2.2.0",
+ "file-saver": "^2.0.5",
+ "js-beautify": "^1.13.5",
+ "lodash": "^4.17.21",
+ "mitt": "^3.0.0",
+ "mockjs": "^1.1.0",
+ "nprogress": "^0.2.0",
+ "pinia": "^2.0.12",
+ "quill": "^1.3.7",
+ "store": "^2.0.12",
+ "unocss": "^0.31.0",
+ "vue": "^3.2.32",
+ "vue-echarts": "^6.0.2",
+ "vue-router": "^4.0.14",
+ "vuedraggable": "^4.1.0",
+ "xlsx": "^0.16.9"
+ },
+ "devDependencies": {
+ "@types/lodash": "^4.14.168",
+ "@types/node": "^16.10.2",
+ "@types/nprogress": "^0.2.0",
+ "@types/quill": "^2.0.9",
+ "@types/store": "^2.0.2",
+ "@types/uuid": "^8.3.4",
+ "@typescript-eslint/eslint-plugin": "^4.20.0",
+ "@typescript-eslint/parser": "^4.20.0",
+ "@unocss/preset-uno": "^0.31.0",
+ "@vitejs/plugin-vue": "^2.3.1",
+ "@vitejs/plugin-vue-jsx": "^1.3.9",
+ "@vue/cli-plugin-babel": "^5.0.1",
+ "@vue/cli-plugin-typescript": "^5.0.1",
+ "@vue/compiler-sfc": "^3.2.31",
+ "@vue/composition-api": "^1.4.9",
+ "eslint": "^7.23.0",
+ "eslint-config-prettier": "^8.1.0",
+ "eslint-plugin-prettier": "^3.3.1",
+ "eslint-plugin-vue": "^7.13.0",
+ "iconv-lite": "^0.6.3",
+ "prettier": "^2.4.1",
+ "sass": "^1.49.9",
+ "sass-loader": "^11.1.1",
+ "svg-sprite-loader": "^6.0.2",
+ "typescript": "^4.6.2",
+ "unplugin-vue-components": "^0.17.21",
+ "vite": "^2.9.8",
+ "vite-plugin-compression": "^0.5.1",
+ "vite-plugin-dts": "^0.9.9",
+ "vite-plugin-mock": "^2.9.6",
+ "vite-plugin-style-import": "^1.0.1",
+ "vite-svg-loader": "^2.1.0"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..0b823a3
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..d4a7cb7
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
{{ app.info.name }}
+
+
正在加载菜单...
+
初次加载资源可能需要较多时间 请耐心等待
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/css/index.scss b/src/assets/css/index.scss
new file mode 100644
index 0000000..e44a756
--- /dev/null
+++ b/src/assets/css/index.scss
@@ -0,0 +1,44 @@
+* {
+ padding: 0;
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
+ "微软雅黑", Arial, sans-serif;
+}
+
+*::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+}
+
+*::-webkit-scrollbar-thumb {
+ background-color: rgba(144, 147, 153, 0.3);
+}
+
+*::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+#app {
+ height: 100vh;
+ width: 100vw;
+ overflow: hidden;
+}
+
+:root {
+ --view-bg-color: #f7f7f7;
+}
+
+a {
+ text-decoration: none;
+}
+
+input,
+button {
+ outline: none;
+}
+
+input {
+ &:-webkit-autofill {
+ box-shadow: 0 0 0px 1000px white inset;
+ }
+}
diff --git a/src/assets/logo-text.png b/src/assets/logo-text.png
new file mode 100644
index 0000000..035b79c
Binary files /dev/null and b/src/assets/logo-text.png differ
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..a75c618
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/cool/bootstrap.ts b/src/cool/bootstrap.ts
new file mode 100644
index 0000000..5168261
--- /dev/null
+++ b/src/cool/bootstrap.ts
@@ -0,0 +1,49 @@
+import { createPinia } from "pinia";
+import { App } from "vue";
+import { useModule } from "./module";
+import { router, viewer } from "./router";
+import { useBaseStore } from "/$/base";
+import mitt from "mitt";
+import VueECharts from "vue-echarts";
+import ElementPlus from "element-plus";
+import "element-plus/theme-chalk/src/index.scss";
+import "uno.css";
+
+export async function bootstrap(Vue: App) {
+ // 缓存
+ Vue.use(createPinia());
+
+ // ui库
+ Vue.use(ElementPlus);
+
+ // 事件通讯
+ Vue.provide("mitt", mitt());
+
+ // 可视图表
+ Vue.component("v-chart", VueECharts);
+
+ // 基础
+ const { app, user, menu } = useBaseStore();
+
+ // 加载模块
+ useModule(Vue);
+
+ // 取缓存视图
+ viewer.add(menu.routes);
+
+ // 路由
+ Vue.use(router);
+
+ // 开启
+ app.showLoading();
+
+ if (user.token) {
+ // 获取用户信息
+ user.get();
+
+ // 获取菜单权限
+ await menu.get();
+ }
+
+ app.hideLoading();
+}
diff --git a/src/cool/config/dev.ts b/src/cool/config/dev.ts
new file mode 100644
index 0000000..aa098b3
--- /dev/null
+++ b/src/cool/config/dev.ts
@@ -0,0 +1,20 @@
+import { getUrlParam, storage } from "../utils";
+import { proxy } from "./proxy";
+
+export default {
+ // 根地址
+ host: proxy["/dev"].target,
+
+ // 请求地址
+ get baseUrl() {
+ let proxy = getUrlParam("proxy");
+
+ if (proxy) {
+ storage.set("proxy", proxy);
+ } else {
+ proxy = storage.get("proxy") || "dev";
+ }
+
+ return `/${proxy}`;
+ }
+};
diff --git a/src/cool/config/index.ts b/src/cool/config/index.ts
new file mode 100644
index 0000000..a4f4b0a
--- /dev/null
+++ b/src/cool/config/index.ts
@@ -0,0 +1,61 @@
+import dev from "./dev";
+import prod from "./prod";
+
+// 是否开发模式
+export const isDev = import.meta.env.MODE === "development";
+
+// 配置
+export const config = {
+ // 项目信息
+ app: {
+ name: "COOL-ADMIN",
+
+ // 菜单
+ menu: {
+ list: []
+ },
+
+ // 路由
+ router: {
+ // 模式
+ mode: "history",
+ // 页面
+ pages: [],
+ // 视图 / 路由下的 children
+ views: []
+ },
+
+ // 主题
+ theme: {
+ // 主色
+ color: "",
+ // 样式地址
+ url: "",
+ // 显示一级菜单
+ showAMenu: false
+ },
+
+ // 字体图标库
+ iconfont: []
+ },
+
+ // 忽略规则
+ ignore: {
+ // 不显示请求进度条
+ NProgress: ["/sys/info/record"],
+ // 页面不需要登录验证
+ token: ["/login", "/401", "/403", "/404", "/500", "/502"]
+ },
+
+ // 调试
+ test: {
+ token: "",
+ mock: false,
+ eps: true
+ },
+
+ // 当前环境
+ ...(isDev ? dev : prod)
+};
+
+export * from "./proxy";
diff --git a/src/cool/config/prod.ts b/src/cool/config/prod.ts
new file mode 100644
index 0000000..70af2d1
--- /dev/null
+++ b/src/cool/config/prod.ts
@@ -0,0 +1,9 @@
+import { proxy } from "./proxy";
+
+export default {
+ // 根地址
+ host: proxy["/prod"].target,
+
+ // 请求地址
+ baseUrl: "/api"
+};
diff --git a/src/cool/config/proxy.ts b/src/cool/config/proxy.ts
new file mode 100644
index 0000000..962ff9a
--- /dev/null
+++ b/src/cool/config/proxy.ts
@@ -0,0 +1,13 @@
+export const proxy = {
+ "/dev": {
+ target: "http://127.0.0.1:8001",
+ changeOrigin: true,
+ rewrite: (path: string) => path.replace(/^\/dev/, "")
+ },
+
+ "/prod": {
+ target: "https://show.cool-admin.com",
+ changeOrigin: true,
+ rewrite: (path: string) => path.replace(/^\/prod/, "/api")
+ }
+};
diff --git a/src/cool/hook/index.ts b/src/cool/hook/index.ts
new file mode 100644
index 0000000..4c32087
--- /dev/null
+++ b/src/cool/hook/index.ts
@@ -0,0 +1,49 @@
+import { onBeforeUpdate, ref, inject, getCurrentInstance } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { useService } from "../service";
+
+export function useRefs() {
+ const refs: any = ref([]);
+
+ onBeforeUpdate(() => {
+ refs.value = [];
+ });
+
+ const setRefs = (index: string) => (el: any) => {
+ refs.value[index] = el;
+ };
+
+ return { refs, setRefs };
+}
+
+// 服务
+const service = useService();
+
+// 组件命名
+function named(name: string) {
+ const { proxy }: any = getCurrentInstance();
+ proxy.$.type.name = name;
+}
+
+export function useCool() {
+ const { refs, setRefs } = useRefs();
+
+ // 通信
+ const mitt = inject("mitt");
+
+ // 路由
+ const route = useRoute();
+
+ // 路由器
+ const router = useRouter();
+
+ return {
+ route,
+ router,
+ refs,
+ setRefs,
+ service,
+ mitt,
+ named
+ };
+}
diff --git a/src/cool/index.ts b/src/cool/index.ts
new file mode 100644
index 0000000..d415dad
--- /dev/null
+++ b/src/cool/index.ts
@@ -0,0 +1,6 @@
+export * from "./service";
+export * from "./bootstrap";
+export * from "./hook";
+export * from "./router";
+export * from "./config";
+export { storage } from "./utils";
diff --git a/src/cool/module/index.ts b/src/cool/module/index.ts
new file mode 100644
index 0000000..f1a2722
--- /dev/null
+++ b/src/cool/module/index.ts
@@ -0,0 +1,162 @@
+import { App } from "vue";
+import modules from "/@/modules";
+import { router, viewer } from "../router";
+import { filename, module } from "../utils";
+import { isFunction, isObject } from "lodash";
+
+// 扫描文件
+const files = import.meta.globEager("/src/modules/**/*");
+
+// 模块列表
+const list: any[] = [...modules];
+
+function main() {
+ for (const i in files) {
+ // 模块名
+ const [, , , name, action] = i.split("/");
+
+ // 文件内容
+ let value: any = null;
+
+ try {
+ value = files[i].default;
+ } catch (err) {
+ console.error(err, i);
+ value = files[i];
+ }
+
+ if (!value) {
+ continue;
+ }
+
+ // 文件名
+ const fname: string = filename(i);
+
+ // 配置参数
+ function next(d: any) {
+ // 配置参数入口
+ if (action == "config.ts") {
+ d.options = value || {};
+ }
+
+ // 模块入口
+ if (action == "index.ts") {
+ d.value = value || {};
+ }
+
+ // 其他功能
+ switch (action) {
+ case "service":
+ const s = new value();
+
+ d.service.push({
+ path: s.namespace,
+ value: s
+ });
+ break;
+
+ case "pages":
+ case "views":
+ if (value.cool) {
+ d[action].push({
+ ...value.cool.route,
+ component: value
+ });
+ }
+ break;
+
+ case "components":
+ d.components[value.name] = value;
+ break;
+
+ case "directives":
+ d.directives[fname] = value;
+ break;
+ }
+
+ return d;
+ }
+
+ // 是否存在
+ const item: any = list.find((e) => e.name === name);
+
+ if (item) {
+ if (!item.isLoaded) {
+ next(item);
+ }
+ } else {
+ list.push(
+ next({
+ name,
+ options: {},
+ directives: {},
+ components: {},
+ pages: [],
+ views: [],
+ service: []
+ })
+ );
+ }
+ }
+
+ module.set(list);
+}
+
+main();
+
+export function useModule(app: App) {
+ // 模块安装
+ list.forEach((e: any) => {
+ if (isObject(e.value)) {
+ if (isFunction(e.value.install)) {
+ Object.assign(e, e.value.install(app, e.options));
+ } else {
+ Object.assign(e, e.value);
+ }
+ }
+
+ try {
+ // 注册组件
+ if (e.components) {
+ for (const i in e.components) {
+ if (e.components[i]) {
+ if (e.components[i].cool?.global || i.indexOf("cl-") === 0) {
+ app.component(e.components[i].name, e.components[i]);
+ }
+ }
+ }
+ }
+
+ // 注册指令
+ if (e.directives) {
+ for (const i in e.directives) {
+ app.directive(i, e.directives[i]);
+ }
+ }
+
+ // 注册页面
+ if (e.pages) {
+ e.pages.forEach((e: any) => {
+ router.addRoute(e);
+ });
+ }
+
+ // 注册视图
+ if (e.views) {
+ e.views.forEach((e: any) => {
+ if (!e.meta) {
+ e.meta = {};
+ }
+
+ if (e.path) {
+ viewer.add([e]);
+ } else {
+ console.error(`[${name}-views]:缺少 path 参数`);
+ }
+ });
+ }
+ } catch (err) {
+ console.error(`模块 ${name} 异常`, err);
+ }
+ });
+}
diff --git a/src/cool/router/index.ts b/src/cool/router/index.ts
new file mode 100644
index 0000000..102b998
--- /dev/null
+++ b/src/cool/router/index.ts
@@ -0,0 +1,144 @@
+// @ts-nocheck
+import { ElMessage } from "element-plus";
+import {
+ createRouter,
+ createWebHashHistory,
+ createWebHistory,
+ NavigationGuardNext,
+ RouteRecordRaw
+} from "vue-router";
+import { storage, config } from "/@/cool";
+import { useBaseStore } from "/$/base";
+import { cloneDeep, isArray } from "lodash";
+
+// 视图文件
+const views = import.meta.globEager("/src/**/views/**/*.vue");
+
+for (const i in views) {
+ views[i.slice(5)] = views[i];
+ delete views[i];
+}
+
+// 默认路由
+const routes: RouteRecordRaw[] = [
+ {
+ path: "/",
+ name: "index",
+ component: () => import("/$/base/pages/layout/index.vue"),
+ children: [
+ {
+ path: "/",
+ name: "数据统计",
+ component: () => import("/@/views/home/index.vue")
+ },
+ ...config.app.router.views
+ ]
+ },
+ ...config.app.router.pages,
+ {
+ path: "/:catchAll(.*)",
+ name: "404",
+ redirect: "/404"
+ }
+];
+
+// 创建
+const router = createRouter({
+ history: config.app.router.mode == "history" ? createWebHistory() : createWebHashHistory(),
+ routes
+}) as CoolRouter;
+
+// 路由守卫
+router.beforeEach((to: any, _: any, next: NavigationGuardNext) => {
+ const { user, process } = useBaseStore();
+
+ if (user.token) {
+ if (to.path.includes("/login")) {
+ // 登录成功且 token 未过期,回到首页
+ if (!storage.isExpired("token")) {
+ return next("/");
+ }
+ } else {
+ // 添加路由进程
+ process.add({
+ keepAlive: to.meta?.keepAlive,
+ label: to.meta?.label || to.name,
+ value: to.fullPath
+ });
+ }
+ } else {
+ if (!config.ignore.token.find((e: string) => to.path == e)) {
+ return next("/login");
+ }
+ }
+
+ next();
+});
+
+// 自定义
+router.href = function (path: string) {
+ const url = import.meta.env.BASE_URL + path;
+
+ if (url != location.pathname) {
+ location.href = url;
+ }
+};
+
+let lock = false;
+
+// 错误监听
+router.onError((err: any) => {
+ if (!lock) {
+ lock = true;
+
+ ElMessage.error("页面不存在或者未配置!");
+ console.error(err);
+
+ setTimeout(() => {
+ lock = false;
+ }, 0);
+ }
+});
+
+// 视图
+const viewer = {
+ add(data: any[] | any) {
+ // 列表
+ const list = isArray(data) ? data : [data];
+
+ list.forEach((e: any) => {
+ const d: any = cloneDeep(e);
+
+ // 命名
+ d.name = d.router;
+
+ if (!d.component) {
+ const url = d.viewPath;
+
+ if (url) {
+ if (
+ /^(http[s]?:\/\/)([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i.test(
+ url
+ )
+ ) {
+ d.meta.iframeUrl = url;
+ d.component = () => import(`/$/base/pages/iframe/index.vue`);
+ } else {
+ d.component = () => Promise.resolve(views[url.replace("cool/", "")]);
+ }
+ } else {
+ d.redirect = "/404";
+ }
+ }
+
+ // 批量添加
+ router.addRoute("index", d);
+ });
+ },
+
+ get() {
+ return router.getRoutes().find((e) => e.name == "index")?.children;
+ }
+};
+
+export { router, viewer };
diff --git a/src/cool/service/base.ts b/src/cool/service/base.ts
new file mode 100644
index 0000000..7fd151a
--- /dev/null
+++ b/src/cool/service/base.ts
@@ -0,0 +1,132 @@
+// @ts-nocheck
+import { isDev, config, proxy } from "../config";
+import { isObject } from "lodash";
+import request from "./request";
+
+export function Service(
+ value:
+ | string
+ | {
+ namespace?: string;
+ url?: string;
+ mock?: boolean;
+ }
+) {
+ return function (target: any) {
+ // 命名
+ if (typeof value == "string") {
+ target.prototype.namespace = value;
+ }
+
+ // 复杂项
+ if (isObject(value)) {
+ target.prototype.namespace = value.namespace;
+ target.prototype.mock = value.mock;
+
+ // 代理
+ if (value.proxy) {
+ target.prototype.url = proxy[value.proxy].target;
+ } else {
+ if (value.url) {
+ target.prototype.url = value.url;
+ }
+ }
+ }
+ };
+}
+
+export class BaseService {
+ constructor(
+ options = {} as {
+ namespace?: string;
+ }
+ ) {
+ if (options?.namespace) {
+ this.namespace = options.namespace;
+ }
+ }
+
+ request(
+ options = {} as {
+ params?: any;
+ data?: any;
+ url: string;
+ method?: "GET" | "get" | "POST" | "post" | string;
+ [key: string]: any;
+ }
+ ) {
+ if (!options.params) options.params = {};
+
+ let ns = "";
+
+ // 是否 mock 模式
+ if (this.mock || config.test.mock) {
+ // 测试
+ } else {
+ if (isDev) {
+ ns = this.proxy || config.baseUrl;
+ } else {
+ ns = this.proxy ? this.url : config.baseUrl;
+ }
+ }
+
+ // 拼接前缀
+ if (this.namespace) {
+ ns += "/" + this.namespace;
+ }
+
+ // 处理地址
+ if (options.proxy === undefined || options.proxy) {
+ options.url = ns + options.url;
+ }
+
+ return request(options);
+ }
+
+ list(data: any) {
+ return this.request({
+ url: "/list",
+ method: "POST",
+ data
+ });
+ }
+
+ page(data: { page?: number; size?: number; [key: string]: any }) {
+ return this.request({
+ url: "/page",
+ method: "POST",
+ data
+ });
+ }
+
+ info(params: { id?: number | string; [key: string]: any }) {
+ return this.request({
+ url: "/info",
+ params
+ });
+ }
+
+ update(data: { id?: number | string; [key: string]: any }) {
+ return this.request({
+ url: "/update",
+ method: "POST",
+ data
+ });
+ }
+
+ delete(data: { ids?: number[] | string[]; [key: string]: any }) {
+ return this.request({
+ url: "/delete",
+ method: "POST",
+ data
+ });
+ }
+
+ add(data: any) {
+ return this.request({
+ url: "/add",
+ method: "POST",
+ data
+ });
+ }
+}
diff --git a/src/cool/service/eps.ts b/src/cool/service/eps.ts
new file mode 100644
index 0000000..0816282
--- /dev/null
+++ b/src/cool/service/eps.ts
@@ -0,0 +1,213 @@
+import { isDev, config } from "../config";
+import { BaseService } from "./base";
+import { storage, toCamel } from "../utils";
+import { isArray, isEmpty } from "lodash";
+
+// 获取标签名
+function getNames(v: any) {
+ return [...Object.getOwnPropertyNames(v.constructor.prototype), ...Object.keys(v)].filter(
+ (e) => !["namespace", "constructor", "request", "permission"].includes(e)
+ );
+}
+
+// 标签名
+const names = getNames(new BaseService());
+
+export function useEps(service: Service) {
+ // 创建描述文件
+ function createDts(list: any[]) {
+ function deep(v: any) {
+ for (const i in v) {
+ if (v[i].namespace) {
+ v[i].namespace = v[i].namespace;
+
+ // 模块
+ const item: any = list.find((e: any) => e.prefix.includes(v[i].namespace));
+
+ // 接口
+ const api: any[] = item ? item.api : [];
+
+ // 获取方法集合
+ [...names, ...getNames(v[i])].forEach((e) => {
+ if (!api.find((a) => a.path.includes(e))) {
+ api.push({
+ path: `/${e}`
+ });
+ }
+ });
+
+ if (item) {
+ item.api = api;
+ } else {
+ list.push({
+ prefix: `/${v[i].namespace}`,
+ api
+ });
+ }
+ } else {
+ deep(v[i]);
+ }
+ }
+ }
+
+ deep(service);
+
+ // 本地服务
+ return service.request({
+ url: "/__cool_eps",
+ method: "POST",
+ proxy: false,
+ data: {
+ service,
+ list
+ }
+ });
+ }
+
+ // 获取 eps
+ function getEps() {
+ if (isDev && config.test.eps) {
+ service
+ .request({
+ url: "/admin/base/open/eps"
+ })
+ .then(async (res) => {
+ if (!isEmpty(res)) {
+ const isLoaded: boolean = storage.get("eps");
+ storage.set("eps", res);
+
+ if (!isLoaded) {
+ location.reload();
+ } else {
+ set(res, true);
+ console.log("[Eps] 初始化成功。");
+ }
+ }
+ })
+ .catch((err) => {
+ console.error("[Eps] 获取失败!", err.message);
+ });
+ }
+ }
+
+ // 设置
+ async function set(d: any, c?: boolean) {
+ const list: any[] = [];
+
+ if (!d) {
+ return false;
+ }
+
+ if (isArray(d)) {
+ d = { d };
+ }
+
+ for (const i in d) {
+ if (isArray(d[i])) {
+ d[i].forEach((e: any) => {
+ // 分隔路径
+ const arr = e.prefix
+ .replace(/\//, "")
+ .replace("admin", "")
+ .split("/")
+ .filter(Boolean)
+ .map(toCamel);
+
+ // 遍历
+ function deep(d: any, i: number) {
+ const k = arr[i];
+
+ if (k) {
+ // 是否最后一个
+ if (arr[i + 1]) {
+ if (!d[k]) {
+ d[k] = {};
+ }
+
+ deep(d[k], i + 1);
+ } else {
+ // 本地不存在则创建实例
+ if (!d[k]) {
+ d[k] = new BaseService({
+ namespace: e.prefix.substr(1, e.prefix.length - 1)
+ });
+ }
+
+ // 创建方法
+ e.api.forEach((a: any) => {
+ // 方法名
+ const n = (a.name || a.path).replace("/", "");
+
+ // 过滤
+ if (!names.includes(n)) {
+ // 本地不存在则创建
+ if (!d[k][n]) {
+ if (n && !/[-:]/g.test(n)) {
+ d[k][n] = function (data: any) {
+ return this.request({
+ url: a.path,
+ method: a.method,
+ [a.method.toLocaleLowerCase() == "post"
+ ? "data"
+ : "params"]: data
+ });
+ };
+ }
+ }
+ }
+ });
+
+ // 创建权限
+ if (!d[k].permission) {
+ d[k].permission = {};
+
+ const ks = Array.from(new Set([...names, ...getNames(d[k])]));
+
+ ks.forEach((e) => {
+ d[k].permission[e] = `${d[k].namespace.replace(
+ "admin/",
+ ""
+ )}/${e}`.replace(/\//g, ":");
+ });
+ }
+
+ list.push(e);
+ }
+ }
+ }
+
+ deep(service, 0);
+ });
+ }
+ }
+
+ if (isDev && c) {
+ await createDts(list);
+ }
+ }
+
+ // 解析
+ try {
+ const eps =
+ storage.get("eps") ||
+ JSON.parse(__EPS__ || "[]").map(([prefix, api]: any[]) => {
+ return {
+ prefix,
+ api: api.map(([method, path, name]: string[]) => {
+ return {
+ method,
+ path,
+ name
+ };
+ })
+ };
+ });
+
+ set(eps);
+ } catch (err) {
+ console.error("[Eps] 解析失败!", err);
+ }
+
+ // 获取
+ getEps();
+}
diff --git a/src/cool/service/index.ts b/src/cool/service/index.ts
new file mode 100644
index 0000000..438ef81
--- /dev/null
+++ b/src/cool/service/index.ts
@@ -0,0 +1,23 @@
+import { deepFiles, deepMerge, module } from "../utils";
+import { BaseService } from "./base";
+import { useEps } from "./eps";
+
+// 基础服务
+export const service: Service = {
+ request: new BaseService().request
+};
+
+export function useService() {
+ // 接口内容
+ useEps(service);
+
+ // 模块内容
+ module.list.forEach((e) => {
+ deepMerge(service, deepFiles(e.service || []));
+ });
+
+ return service;
+}
+
+export * from "./base";
+export * from "./request";
diff --git a/src/cool/service/request.ts b/src/cool/service/request.ts
new file mode 100644
index 0000000..8051862
--- /dev/null
+++ b/src/cool/service/request.ts
@@ -0,0 +1,146 @@
+import axios from "axios";
+import NProgress from "nprogress";
+import "nprogress/nprogress.css";
+import { ElMessage } from "element-plus";
+import { isDev, config } from "/@/cool";
+import { storage } from "/@/cool/utils";
+import { useBaseStore } from "/$/base";
+import { router } from "../router";
+
+axios.defaults.timeout = 30000;
+axios.defaults.withCredentials = false;
+
+NProgress.configure({
+ showSpinner: true
+});
+
+// 请求队列
+let requests: Array = [];
+
+// Token 是否刷新中
+let isRefreshing = false;
+
+// @ts-ignore
+axios.interceptors.request.eject(axios._req);
+
+// @ts-ignore
+axios._req = axios.interceptors.request.use(
+ (req: any) => {
+ const { user } = useBaseStore();
+
+ if (req.url) {
+ // 请求进度条
+ if (!config.ignore.NProgress.some((e: string) => req.url.includes(e))) {
+ NProgress.start();
+ }
+ }
+
+ // 请求信息
+ if (isDev) {
+ console.group(req.url);
+ console.log("method:", req.method);
+ console.table("data:", req.method == "get" ? req.params : req.data);
+ console.groupEnd();
+ }
+
+ // 验证 token
+ if (user.token) {
+ // 请求标识
+ req.headers["Authorization"] = user.token;
+
+ if (req.url.includes("refreshToken")) {
+ return req;
+ }
+
+ // 判断 token 是否过期
+ if (storage.isExpired("token")) {
+ // 判断 refreshToken 是否过期
+ if (storage.isExpired("refreshToken")) {
+ return user.logout();
+ }
+
+ // 是否在刷新中
+ if (!isRefreshing) {
+ isRefreshing = true;
+
+ user.refreshToken()
+ .then((token: string) => {
+ requests.forEach((cb) => cb(token));
+ requests = [];
+ isRefreshing = false;
+ })
+ .catch(() => {
+ user.clear();
+ });
+ }
+
+ return new Promise((resolve) => {
+ // 继续请求
+ requests.push((token: string) => {
+ // 重新设置 token
+ req.headers["Authorization"] = token;
+ resolve(req);
+ });
+ });
+ }
+ }
+
+ return req;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+);
+
+// 响应
+axios.interceptors.response.use(
+ (res) => {
+ NProgress.done();
+
+ if (!res?.data) {
+ return res;
+ }
+
+ const { code, data, message } = res.data;
+
+ switch (code) {
+ case 1000:
+ return data;
+ default:
+ return Promise.reject({ code, message });
+ }
+ },
+ async (error) => {
+ NProgress.done();
+
+ if (error.response) {
+ const { status, config } = error.response;
+
+ if (isDev) {
+ ElMessage.error(`${config.url} ${status}`);
+ } else {
+ switch (status) {
+ case 401:
+ router.href("401");
+ break;
+
+ case 403:
+ router.href("403");
+ break;
+
+ case 500:
+ router.href("500");
+ break;
+
+ case 502:
+ router.href("502");
+ break;
+ }
+ }
+ }
+
+ return Promise.reject({ message: error.message });
+ }
+);
+
+export default axios;
diff --git a/src/cool/utils/index.ts b/src/cool/utils/index.ts
new file mode 100644
index 0000000..1d7be11
--- /dev/null
+++ b/src/cool/utils/index.ts
@@ -0,0 +1,272 @@
+import { isArray, orderBy } from "lodash";
+import storage from "./storage";
+import module from "./module";
+
+// 首字母大写
+export function firstUpperCase(value: string): string {
+ return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
+ return $1.toUpperCase() + $2;
+ });
+}
+
+// 获取方法名
+export function getNames(value: any) {
+ return Object.getOwnPropertyNames(value.constructor.prototype);
+}
+
+// 深度合并
+export function deepMerge(a: any, b: any) {
+ let k;
+ for (k in b) {
+ a[k] =
+ a[k] && a[k].toString() === "[object Object]" ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
+ }
+ return a;
+}
+
+// 获取地址栏参数
+export function getUrlParam(name: string): string | null {
+ const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+ const r = window.location.search.substr(1).match(reg);
+ if (r != null) return decodeURIComponent(r[2]);
+ return null;
+}
+
+// 文件路径转对象
+export function deepFiles(list: any[]) {
+ const modules: any = {};
+
+ list.forEach((e) => {
+ const arr = e.path.split("/");
+ const parents = arr.slice(0, arr.length - 1);
+ const name = basename(e.path).replace(".ts", "");
+
+ let curr: any = modules;
+ let prev: any = null;
+ let key: any = null;
+
+ parents.forEach((k: string) => {
+ if (!curr[k]) {
+ curr[k] = {};
+ }
+
+ prev = curr;
+ curr = curr[k];
+ key = k;
+ });
+
+ if (name == "index") {
+ prev[key] = e.value;
+ } else {
+ curr[name] = e.value;
+ }
+ });
+
+ return modules;
+}
+
+// 文件名
+export function filename(path: string): string {
+ return basename(path.substring(0, path.lastIndexOf(".")));
+}
+
+// 路径名称
+export function basename(path: string): string {
+ let index = path.lastIndexOf("/");
+ index = index > -1 ? index : path.lastIndexOf("\\");
+ if (index < 0) {
+ return path;
+ }
+ return path.substring(index + 1);
+}
+
+// 文件扩展名
+export function extname(path: string): string {
+ return path.substring(path.lastIndexOf(".") + 1);
+}
+
+// 横杠转驼峰
+export function toCamel(str: string): string {
+ return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
+ return $1 + $2.toUpperCase();
+ });
+}
+
+// uuid
+export function uuid(): string {
+ const s: any[] = [];
+ const hexDigits = "0123456789abcdef";
+ for (let i = 0; i < 36; i++) {
+ s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+ }
+ s[14] = "4";
+ s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+ s[8] = s[13] = s[18] = s[23] = "-";
+
+ return s.join("");
+}
+
+// 浏览器信息
+export function getBrowser() {
+ const { clientHeight, clientWidth } = document.documentElement;
+
+ // 浏览器信息
+ const ua = navigator.userAgent.toLowerCase();
+
+ // 浏览器类型
+ let type = (ua.match(/firefox|chrome|safari|opera/g) || "other")[0];
+
+ if ((ua.match(/msie|trident/g) || [])[0]) {
+ type = "msie";
+ }
+
+ // 平台标签
+ let tag = "";
+
+ const isTocuh =
+ "ontouchstart" in window || ua.indexOf("touch") !== -1 || ua.indexOf("mobile") !== -1;
+ if (isTocuh) {
+ if (ua.indexOf("ipad") !== -1) {
+ tag = "pad";
+ } else if (ua.indexOf("mobile") !== -1) {
+ tag = "mobile";
+ } else if (ua.indexOf("android") !== -1) {
+ tag = "androidPad";
+ } else {
+ tag = "pc";
+ }
+ } else {
+ tag = "pc";
+ }
+
+ // 浏览器内核
+ let prefix = "";
+
+ switch (type) {
+ case "chrome":
+ case "safari":
+ case "mobile":
+ prefix = "webkit";
+ break;
+ case "msie":
+ prefix = "ms";
+ break;
+ case "firefox":
+ prefix = "Moz";
+ break;
+ case "opera":
+ prefix = "O";
+ break;
+ default:
+ prefix = "webkit";
+ break;
+ }
+
+ // 操作平台
+ const plat = ua.indexOf("android") > 0 ? "android" : navigator.platform.toLowerCase();
+
+ // 屏幕信息
+ let screen = "full";
+
+ if (clientWidth < 768) {
+ screen = "xs";
+ } else if (clientWidth < 992) {
+ screen = "sm";
+ } else if (clientWidth < 1200) {
+ screen = "md";
+ } else if (clientWidth < 1920) {
+ screen = "xl";
+ } else {
+ screen = "full";
+ }
+
+ // 是否 ios
+ const isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
+
+ // 浏览器版本
+ const version = (ua.match(/[\s\S]+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1];
+
+ // 是否 PC 端
+ const isPC = tag === "pc";
+
+ // 是否移动端
+ const isMobile = isPC ? false : true;
+
+ // 是否移动端 + 屏幕宽过小
+ const isMini = screen === "xs" || isMobile;
+
+ return {
+ height: clientHeight,
+ width: clientWidth,
+ version,
+ type,
+ plat,
+ tag,
+ prefix,
+ isMobile,
+ isIOS,
+ isPC,
+ isMini,
+ screen
+ };
+}
+
+// 列表转树形
+export function deepTree(list: any[]): any[] {
+ const newList: Array = [];
+ const map: any = {};
+
+ list.forEach((e) => (map[e.id] = e));
+
+ list.forEach((e) => {
+ const parent = map[e.parentId];
+
+ if (parent) {
+ (parent.children || (parent.children = [])).push(e);
+ } else {
+ newList.push(e);
+ }
+ });
+
+ const fn = (list: Array) => {
+ list.map((e) => {
+ if (e.children instanceof Array) {
+ e.children = orderBy(e.children, "orderNum");
+
+ fn(e.children);
+ }
+ });
+ };
+
+ fn(newList);
+
+ return orderBy(newList, "orderNum");
+}
+
+// 树形转列表
+export function revDeepTree(list: Array = []) {
+ const d: Array = [];
+ let id = 0;
+
+ const deep = (list: Array, parentId: any) => {
+ list.forEach((e) => {
+ if (!e.id) {
+ e.id = id++;
+ }
+
+ e.parentId = parentId;
+
+ d.push(e);
+
+ if (e.children && isArray(e.children)) {
+ deep(e.children, e.id);
+ }
+ });
+ };
+
+ deep(list || [], null);
+
+ return d;
+}
+
+export { storage, module };
diff --git a/src/cool/utils/module.ts b/src/cool/utils/module.ts
new file mode 100644
index 0000000..4a290e0
--- /dev/null
+++ b/src/cool/utils/module.ts
@@ -0,0 +1,31 @@
+// @ts-nocheck
+
+interface Item {
+ name: string;
+ options: {
+ [key: string]: any;
+ };
+ value: any;
+ service?: any[];
+ pages?: any[];
+ views?: any[];
+ components?: {
+ [key: string]: any;
+ };
+}
+
+const module = {
+ get list(): Item[] {
+ return window.__modules__ || [];
+ },
+
+ set(list: Item[]) {
+ window.__modules__ = list;
+ },
+
+ get(name: string) {
+ return name ? window.__modules__.find((e) => e.name == name) : window.__modules__;
+ }
+};
+
+export default module;
diff --git a/src/cool/utils/storage.ts b/src/cool/utils/storage.ts
new file mode 100644
index 0000000..d200aca
--- /dev/null
+++ b/src/cool/utils/storage.ts
@@ -0,0 +1,81 @@
+import store from "store";
+
+export default {
+ // 后缀标识
+ suffix: "_deadtime",
+
+ /**
+ * 获取
+ * @param {string} key 关键字
+ */
+ get(key: string) {
+ return store.get(key);
+ },
+
+ /**
+ * 获取全部
+ */
+ info() {
+ const d: any = {};
+
+ store.each(function (value: any, key: any) {
+ d[key] = value;
+ });
+
+ return d;
+ },
+
+ /**
+ * 设置
+ * @param {string} key 关键字
+ * @param {*} value 值
+ * @param {number} expires 过期时间
+ */
+ set(key: string, value: any, expires?: any) {
+ store.set(key, value);
+
+ if (expires) {
+ store.set(`${key}${this.suffix}`, Date.parse(String(new Date())) + expires * 1000);
+ }
+ },
+
+ /**
+ * 是否过期
+ * @param {string} key 关键字
+ */
+ isExpired(key: string) {
+ return (this.getExpiration(key) || 0) - Date.parse(String(new Date())) <= 2000;
+ },
+
+ /**
+ * 获取到期时间
+ * @param {string} key 关键字
+ */
+ getExpiration(key: string) {
+ return this.get(key + this.suffix);
+ },
+
+ /**
+ * 移除
+ * @param {string} key 关键字
+ */
+ remove(key: string) {
+ store.remove(key);
+ this.removeExpiration(key);
+ },
+
+ /**
+ * 移除到期时间
+ * @param {string} key 关键字
+ */
+ removeExpiration(key: string) {
+ store.remove(key + this.suffix);
+ },
+
+ /**
+ * 清理
+ */
+ clearAll() {
+ store.clearAll();
+ }
+};
diff --git a/src/env.d.ts b/src/env.d.ts
new file mode 100644
index 0000000..191a0fe
--- /dev/null
+++ b/src/env.d.ts
@@ -0,0 +1,4 @@
+///
+///
+
+declare const __EPS__: string;
diff --git a/src/icons/svg/icon-activity.svg b/src/icons/svg/icon-activity.svg
new file mode 100644
index 0000000..be00ee9
--- /dev/null
+++ b/src/icons/svg/icon-activity.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-app.svg b/src/icons/svg/icon-app.svg
new file mode 100644
index 0000000..1d82a1c
--- /dev/null
+++ b/src/icons/svg/icon-app.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-approve.svg b/src/icons/svg/icon-approve.svg
new file mode 100644
index 0000000..e77593c
--- /dev/null
+++ b/src/icons/svg/icon-approve.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-auth.svg b/src/icons/svg/icon-auth.svg
new file mode 100644
index 0000000..b299f59
--- /dev/null
+++ b/src/icons/svg/icon-auth.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-ban.svg b/src/icons/svg/icon-ban.svg
new file mode 100644
index 0000000..0376a77
--- /dev/null
+++ b/src/icons/svg/icon-ban.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-camera.svg b/src/icons/svg/icon-camera.svg
new file mode 100644
index 0000000..c277464
--- /dev/null
+++ b/src/icons/svg/icon-camera.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-card.svg b/src/icons/svg/icon-card.svg
new file mode 100644
index 0000000..f6ae0e8
--- /dev/null
+++ b/src/icons/svg/icon-card.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-cart.svg b/src/icons/svg/icon-cart.svg
new file mode 100644
index 0000000..f6833eb
--- /dev/null
+++ b/src/icons/svg/icon-cart.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-command.svg b/src/icons/svg/icon-command.svg
new file mode 100644
index 0000000..172c809
--- /dev/null
+++ b/src/icons/svg/icon-command.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-common.svg b/src/icons/svg/icon-common.svg
new file mode 100644
index 0000000..7fe6060
--- /dev/null
+++ b/src/icons/svg/icon-common.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-count.svg b/src/icons/svg/icon-count.svg
new file mode 100644
index 0000000..daf26ba
--- /dev/null
+++ b/src/icons/svg/icon-count.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-crown.svg b/src/icons/svg/icon-crown.svg
new file mode 100644
index 0000000..71429de
--- /dev/null
+++ b/src/icons/svg/icon-crown.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-dept.svg b/src/icons/svg/icon-dept.svg
new file mode 100644
index 0000000..1182824
--- /dev/null
+++ b/src/icons/svg/icon-dept.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-discover.svg b/src/icons/svg/icon-discover.svg
new file mode 100644
index 0000000..3747d7e
--- /dev/null
+++ b/src/icons/svg/icon-discover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-emoji.svg b/src/icons/svg/icon-emoji.svg
new file mode 100644
index 0000000..2809858
--- /dev/null
+++ b/src/icons/svg/icon-emoji.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-favor.svg b/src/icons/svg/icon-favor.svg
new file mode 100644
index 0000000..98ad58e
--- /dev/null
+++ b/src/icons/svg/icon-favor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-goods.svg b/src/icons/svg/icon-goods.svg
new file mode 100644
index 0000000..391d0ea
--- /dev/null
+++ b/src/icons/svg/icon-goods.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-home.svg b/src/icons/svg/icon-home.svg
new file mode 100644
index 0000000..94cf445
--- /dev/null
+++ b/src/icons/svg/icon-home.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-hot.svg b/src/icons/svg/icon-hot.svg
new file mode 100644
index 0000000..b907eeb
--- /dev/null
+++ b/src/icons/svg/icon-hot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-info.svg b/src/icons/svg/icon-info.svg
new file mode 100644
index 0000000..797ed3a
--- /dev/null
+++ b/src/icons/svg/icon-info.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-like.svg b/src/icons/svg/icon-like.svg
new file mode 100644
index 0000000..c49af81
--- /dev/null
+++ b/src/icons/svg/icon-like.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-living.svg b/src/icons/svg/icon-living.svg
new file mode 100644
index 0000000..47fd717
--- /dev/null
+++ b/src/icons/svg/icon-living.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-log.svg b/src/icons/svg/icon-log.svg
new file mode 100644
index 0000000..190b185
--- /dev/null
+++ b/src/icons/svg/icon-log.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-menu.svg b/src/icons/svg/icon-menu.svg
new file mode 100644
index 0000000..7b16097
--- /dev/null
+++ b/src/icons/svg/icon-menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-message.svg b/src/icons/svg/icon-message.svg
new file mode 100644
index 0000000..0110fc8
--- /dev/null
+++ b/src/icons/svg/icon-message.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-monitor.svg b/src/icons/svg/icon-monitor.svg
new file mode 100644
index 0000000..29f7eee
--- /dev/null
+++ b/src/icons/svg/icon-monitor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-new.svg b/src/icons/svg/icon-new.svg
new file mode 100644
index 0000000..3ebc4bf
--- /dev/null
+++ b/src/icons/svg/icon-new.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-news.svg b/src/icons/svg/icon-news.svg
new file mode 100644
index 0000000..5cec609
--- /dev/null
+++ b/src/icons/svg/icon-news.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-pending.svg b/src/icons/svg/icon-pending.svg
new file mode 100644
index 0000000..90be6b0
--- /dev/null
+++ b/src/icons/svg/icon-pending.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-pic.svg b/src/icons/svg/icon-pic.svg
new file mode 100644
index 0000000..0c68a0d
--- /dev/null
+++ b/src/icons/svg/icon-pic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-question.svg b/src/icons/svg/icon-question.svg
new file mode 100644
index 0000000..8fbe696
--- /dev/null
+++ b/src/icons/svg/icon-question.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-rank.svg b/src/icons/svg/icon-rank.svg
new file mode 100644
index 0000000..60916c5
--- /dev/null
+++ b/src/icons/svg/icon-rank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-scan.svg b/src/icons/svg/icon-scan.svg
new file mode 100644
index 0000000..6e1e2c7
--- /dev/null
+++ b/src/icons/svg/icon-scan.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-search.svg b/src/icons/svg/icon-search.svg
new file mode 100644
index 0000000..166aebf
--- /dev/null
+++ b/src/icons/svg/icon-search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-system.svg b/src/icons/svg/icon-system.svg
new file mode 100644
index 0000000..a370d13
--- /dev/null
+++ b/src/icons/svg/icon-system.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-tag.svg b/src/icons/svg/icon-tag.svg
new file mode 100644
index 0000000..84adbce
--- /dev/null
+++ b/src/icons/svg/icon-tag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-task.svg b/src/icons/svg/icon-task.svg
new file mode 100644
index 0000000..1a3ff3b
--- /dev/null
+++ b/src/icons/svg/icon-task.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-theme.svg b/src/icons/svg/icon-theme.svg
new file mode 100644
index 0000000..7833dde
--- /dev/null
+++ b/src/icons/svg/icon-theme.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-time.svg b/src/icons/svg/icon-time.svg
new file mode 100644
index 0000000..7ac6296
--- /dev/null
+++ b/src/icons/svg/icon-time.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-user.svg b/src/icons/svg/icon-user.svg
new file mode 100644
index 0000000..66d8df7
--- /dev/null
+++ b/src/icons/svg/icon-user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-video.svg b/src/icons/svg/icon-video.svg
new file mode 100644
index 0000000..1eee5b9
--- /dev/null
+++ b/src/icons/svg/icon-video.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-wallet.svg b/src/icons/svg/icon-wallet.svg
new file mode 100644
index 0000000..d9a1412
--- /dev/null
+++ b/src/icons/svg/icon-wallet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-warn.svg b/src/icons/svg/icon-warn.svg
new file mode 100644
index 0000000..3eab18d
--- /dev/null
+++ b/src/icons/svg/icon-warn.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/icon-workbench.svg b/src/icons/svg/icon-workbench.svg
new file mode 100644
index 0000000..e67c8ed
--- /dev/null
+++ b/src/icons/svg/icon-workbench.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..f6dc18f
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,17 @@
+import { createApp } from "vue";
+import App from "./App.vue";
+import { bootstrap } from "./cool";
+
+// mock
+// import "./mock";
+
+const app = createApp(App);
+
+// 启动
+bootstrap(app)
+ .then(() => {
+ app.mount("#app");
+ })
+ .catch((err) => {
+ console.error("COOL-ADMIN 启动失败", err);
+ });
diff --git a/src/mock/index.ts b/src/mock/index.ts
new file mode 100644
index 0000000..e9ad5d8
--- /dev/null
+++ b/src/mock/index.ts
@@ -0,0 +1,3 @@
+// @ts-nocheck
+const xhr = new window._XMLHttpRequest();
+window.XMLHttpRequest.prototype.upload = xhr.upload;
diff --git a/src/modules/base/common/index.ts b/src/modules/base/common/index.ts
new file mode 100644
index 0000000..e4a6999
--- /dev/null
+++ b/src/modules/base/common/index.ts
@@ -0,0 +1,4 @@
+import "./resize";
+
+export * from "./theme";
+export * from "./permission";
diff --git a/src/modules/base/common/permission.ts b/src/modules/base/common/permission.ts
new file mode 100644
index 0000000..899c8cb
--- /dev/null
+++ b/src/modules/base/common/permission.ts
@@ -0,0 +1,30 @@
+import { useBaseStore } from "../store";
+import { isObject } from "lodash";
+
+function parse(value: any) {
+ const { menu } = useBaseStore();
+
+ if (typeof value == "string") {
+ return value ? menu.perms.some((e: any) => e.includes(value.replace(/\s/g, ""))) : false;
+ } else {
+ return Boolean(value);
+ }
+}
+
+export function checkPerm(value: any) {
+ if (!value) {
+ return false;
+ }
+
+ if (isObject(value)) {
+ if (value.or) {
+ return value.or.some(parse);
+ }
+
+ if (value.and) {
+ return value.and.some((e: any) => !parse(e)) ? false : true;
+ }
+ }
+
+ return parse(value);
+}
diff --git a/src/modules/base/common/resize.ts b/src/modules/base/common/resize.ts
new file mode 100644
index 0000000..fb865a3
--- /dev/null
+++ b/src/modules/base/common/resize.ts
@@ -0,0 +1,13 @@
+import { useEventListener } from "@vueuse/core";
+import { useBaseStore } from "../store";
+
+function resize() {
+ const { app } = useBaseStore();
+ app.setBrowser();
+ app.isFold = app.browser.isMini;
+}
+
+window.onload = function () {
+ useEventListener(window, "resize", resize);
+ resize();
+};
diff --git a/src/modules/base/common/theme.ts b/src/modules/base/common/theme.ts
new file mode 100644
index 0000000..756651c
--- /dev/null
+++ b/src/modules/base/common/theme.ts
@@ -0,0 +1,39 @@
+import { config } from "/@/cool";
+import { basename } from "/@/cool/utils";
+import { createLink } from "../utils";
+
+// 主题初始化
+if (config.app.theme) {
+ const { url, color } = config.app.theme;
+
+ if (url) {
+ createLink(url, "theme-style");
+ }
+
+ document.getElementsByTagName("body")[0].style.setProperty("--color-primary", color);
+}
+
+// 字体图标库加载
+if (config.app.iconfont) {
+ config.app.iconfont.forEach((e: string) => {
+ createLink(e);
+ });
+}
+
+// 默认
+createLink("//at.alicdn.com/t/font_3254019_60a2xxj8uus.css");
+
+// svg 图标加载
+const svgFiles = import.meta.globEager("/src/icons/svg/**/*.svg");
+
+function iconList() {
+ const list: string[] = [];
+
+ for (const i in svgFiles) {
+ list.push(basename(i).replace(".svg", ""));
+ }
+
+ return list;
+}
+
+export { iconList };
diff --git a/src/modules/base/components/avatar/index.vue b/src/modules/base/components/avatar/index.vue
new file mode 100644
index 0000000..4541f8f
--- /dev/null
+++ b/src/modules/base/components/avatar/index.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/components/codemirror/index.vue b/src/modules/base/components/codemirror/index.vue
new file mode 100644
index 0000000..fd4b15a
--- /dev/null
+++ b/src/modules/base/components/codemirror/index.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/components/date/text.vue b/src/modules/base/components/date/text.vue
new file mode 100644
index 0000000..550ca75
--- /dev/null
+++ b/src/modules/base/components/date/text.vue
@@ -0,0 +1,30 @@
+
+ {{ value }}
+
+
+
diff --git a/src/modules/base/components/editor-quill/index.vue b/src/modules/base/components/editor-quill/index.vue
new file mode 100644
index 0000000..f904969
--- /dev/null
+++ b/src/modules/base/components/editor-quill/index.vue
@@ -0,0 +1,256 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/components/icon/svg.vue b/src/modules/base/components/icon/svg.vue
new file mode 100644
index 0000000..cfc76e9
--- /dev/null
+++ b/src/modules/base/components/icon/svg.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/components/image/index.vue b/src/modules/base/components/image/index.vue
new file mode 100644
index 0000000..ed21f60
--- /dev/null
+++ b/src/modules/base/components/image/index.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/components/link/index.vue b/src/modules/base/components/link/index.vue
new file mode 100644
index 0000000..33dd984
--- /dev/null
+++ b/src/modules/base/components/link/index.vue
@@ -0,0 +1,79 @@
+
+
+ {{ filename(item) }}
+
+
+
+
+
+
diff --git a/src/modules/base/components/scrollbar/index.vue b/src/modules/base/components/scrollbar/index.vue
new file mode 100644
index 0000000..2b0ad79
--- /dev/null
+++ b/src/modules/base/components/scrollbar/index.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/components/switch/index.tsx b/src/modules/base/components/switch/index.tsx
new file mode 100644
index 0000000..f331fe3
--- /dev/null
+++ b/src/modules/base/components/switch/index.tsx
@@ -0,0 +1,86 @@
+import { useCrud } from "@cool-vue/crud";
+import { ElMessage } from "element-plus";
+import { defineComponent, ref, watch } from "vue";
+import { isBoolean } from "lodash";
+
+export default defineComponent({
+ name: "cl-switch",
+
+ props: {
+ scope: null,
+ column: null,
+ modelValue: [Number, String, Boolean],
+ activeValue: {
+ type: [Number, String, Boolean],
+ default: true
+ },
+ inactiveValue: {
+ type: [Number, String, Boolean],
+ default: false
+ }
+ },
+
+ emits: ["update:modelValue", "change"],
+
+ setup(props, { emit }) {
+ // cl-crud
+ const Crud = useCrud();
+
+ // 状态
+ const status = ref();
+
+ watch(
+ () => props.modelValue,
+ (val: any) => {
+ if (isBoolean(props.activeValue)) {
+ status.value = Boolean(val);
+ } else {
+ status.value = val;
+ }
+ },
+ {
+ immediate: true
+ }
+ );
+
+ // 监听改变
+ function onChange(val: boolean | string | number) {
+ if (props.column && props.scope) {
+ if (Crud.value?.service.update) {
+ Crud.value?.service
+ ?.update({
+ ...props.scope,
+ [props.column.property]: val
+ })
+ .then(() => {
+ ElMessage.success("更新成功");
+ emit("update:modelValue", val);
+ emit("change", val);
+ })
+ .catch((err) => {
+ ElMessage.error(err.message);
+ });
+ }
+ } else {
+ emit("update:modelValue", val);
+ emit("change", val);
+ }
+ }
+
+ return {
+ status,
+ onChange
+ };
+ },
+
+ render(ctx: any) {
+ return (
+
+ );
+ }
+});
diff --git a/src/modules/base/directives/permission.ts b/src/modules/base/directives/permission.ts
new file mode 100644
index 0000000..84596b5
--- /dev/null
+++ b/src/modules/base/directives/permission.ts
@@ -0,0 +1,13 @@
+import { checkPerm } from "../common/permission";
+
+function change(el: any, binding: any) {
+ el.style.display = checkPerm(binding.value) ? el.getAttribute("_display") : "none";
+}
+
+export default {
+ beforeMount(el: any, binding: any) {
+ el.setAttribute("_display", el.style.display || "");
+ change(el, binding);
+ },
+ updated: change
+};
diff --git a/src/modules/base/index.ts b/src/modules/base/index.ts
new file mode 100644
index 0000000..91b0151
--- /dev/null
+++ b/src/modules/base/index.ts
@@ -0,0 +1,4 @@
+import "./static/css/index.scss";
+
+export * from "./store";
+export * from "./common";
diff --git a/src/modules/base/pages/error-page/401.vue b/src/modules/base/pages/error-page/401.vue
new file mode 100644
index 0000000..cbb6ddd
--- /dev/null
+++ b/src/modules/base/pages/error-page/401.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/src/modules/base/pages/error-page/403.vue b/src/modules/base/pages/error-page/403.vue
new file mode 100644
index 0000000..e2c415a
--- /dev/null
+++ b/src/modules/base/pages/error-page/403.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/src/modules/base/pages/error-page/404.vue b/src/modules/base/pages/error-page/404.vue
new file mode 100644
index 0000000..421718f
--- /dev/null
+++ b/src/modules/base/pages/error-page/404.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/src/modules/base/pages/error-page/500.vue b/src/modules/base/pages/error-page/500.vue
new file mode 100644
index 0000000..b5353c0
--- /dev/null
+++ b/src/modules/base/pages/error-page/500.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/src/modules/base/pages/error-page/502.vue b/src/modules/base/pages/error-page/502.vue
new file mode 100644
index 0000000..c09cac8
--- /dev/null
+++ b/src/modules/base/pages/error-page/502.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/src/modules/base/pages/error-page/components/error-page.vue b/src/modules/base/pages/error-page/components/error-page.vue
new file mode 100644
index 0000000..2c1ccb8
--- /dev/null
+++ b/src/modules/base/pages/error-page/components/error-page.vue
@@ -0,0 +1,165 @@
+
+
+
{{ code }}
+
{{ desc }}
+
+
+
+
+
+ {{ item.name }}
+ {{ item.path }}
+
+
+
+ 跳转
+
+
+
+
+
+
+
+ 返回登录页
+
+
+
+
Copyright © cool-admin-next 2023
+
+
+
+
+
+
diff --git a/src/modules/base/pages/iframe/index.vue b/src/modules/base/pages/iframe/index.vue
new file mode 100644
index 0000000..21fcbec
--- /dev/null
+++ b/src/modules/base/pages/iframe/index.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/layout/components/process.vue b/src/modules/base/pages/layout/components/process.vue
new file mode 100644
index 0000000..513fced
--- /dev/null
+++ b/src/modules/base/pages/layout/components/process.vue
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/layout/components/route-nav.vue b/src/modules/base/pages/layout/components/route-nav.vue
new file mode 100644
index 0000000..0e61154
--- /dev/null
+++ b/src/modules/base/pages/layout/components/route-nav.vue
@@ -0,0 +1,104 @@
+
+
+
+ {{ lastName }}
+
+
+
+
+ 首页
+ {{
+ (item.meta && item.meta.label) || item.name
+ }}
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/layout/components/slider.vue b/src/modules/base/pages/layout/components/slider.vue
new file mode 100644
index 0000000..ac9d8ca
--- /dev/null
+++ b/src/modules/base/pages/layout/components/slider.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
{{ app.info.name }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/layout/components/topbar.vue b/src/modules/base/pages/layout/components/topbar.vue
new file mode 100644
index 0000000..e6edcbe
--- /dev/null
+++ b/src/modules/base/pages/layout/components/topbar.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ user.info.nickName }}
+
+
+
+
+
+
+
+ 个人中心
+
+
+
+ 退出
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/layout/components/views.vue b/src/modules/base/pages/layout/components/views.vue
new file mode 100644
index 0000000..0f95b68
--- /dev/null
+++ b/src/modules/base/pages/layout/components/views.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/layout/index.vue b/src/modules/base/pages/layout/index.vue
new file mode 100644
index 0000000..a3cf360
--- /dev/null
+++ b/src/modules/base/pages/layout/index.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/login/components/captcha.vue b/src/modules/base/pages/login/components/captcha.vue
new file mode 100644
index 0000000..74c4df4
--- /dev/null
+++ b/src/modules/base/pages/login/components/captcha.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/pages/login/index.vue b/src/modules/base/pages/login/index.vue
new file mode 100644
index 0000000..ef2cc9f
--- /dev/null
+++ b/src/modules/base/pages/login/index.vue
@@ -0,0 +1,258 @@
+
+
+
+
+
+
+
diff --git a/src/modules/base/static/css/index.scss b/src/modules/base/static/css/index.scss
new file mode 100644
index 0000000..1dd00b9
--- /dev/null
+++ b/src/modules/base/static/css/index.scss
@@ -0,0 +1 @@
+@import "./theme.scss";
diff --git a/src/modules/base/static/css/theme.scss b/src/modules/base/static/css/theme.scss
new file mode 100644
index 0000000..9a888fc
--- /dev/null
+++ b/src/modules/base/static/css/theme.scss
@@ -0,0 +1,29 @@
+// customize style
+.scroller1 {
+ overflow: auto;
+ position: relative;
+ z-index: 9;
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: rgba(144, 147, 153, 0.3);
+ border-radius: 6px;
+ }
+
+ &::-webkit-scrollbar {
+ height: 6px;
+ width: 6px;
+ }
+}
+
+// Element-plus theme
+.el-input-number {
+ .el-input-number__decrease,
+ .el-input-number__increase {
+ border: 0 !important;
+ background-color: transparent;
+ }
+}
diff --git a/src/modules/base/store/app.ts b/src/modules/base/store/app.ts
new file mode 100644
index 0000000..fd2781d
--- /dev/null
+++ b/src/modules/base/store/app.ts
@@ -0,0 +1,62 @@
+import { defineStore } from "pinia";
+import { ref } from "vue";
+import { config } from "/@/cool";
+import { deepMerge, getBrowser, storage } from "/@/cool/utils";
+
+export const useAppStore = defineStore("app", function () {
+ // 基本信息
+ const info = ref({
+ ...config.app
+ });
+
+ // 浏览器信息
+ const browser = ref(getBrowser());
+
+ // 加载
+ const loading = ref(false);
+
+ // 是否折叠
+ const isFold = ref(browser.value.isMini || false);
+
+ // 折叠
+ function fold(v?: boolean) {
+ if (v === undefined) {
+ v = !isFold.value;
+ }
+
+ isFold.value = v;
+ }
+
+ // 设置基本信息
+ function set(data: any) {
+ deepMerge(info.value, data);
+ storage.set("__app__", info.value);
+ }
+
+ // 设置浏览器信息
+ function setBrowser() {
+ browser.value = getBrowser();
+ }
+
+ // 加载
+ function showLoading() {
+ loading.value = true;
+ }
+
+ // 关闭
+ function hideLoading() {
+ loading.value = false;
+ }
+
+ return {
+ info,
+ browser,
+ loading,
+ isFold,
+ fold,
+ set,
+ setBrowser,
+ showLoading,
+ hideLoading
+ };
+});
diff --git a/src/modules/base/store/index.ts b/src/modules/base/store/index.ts
new file mode 100644
index 0000000..5385acc
--- /dev/null
+++ b/src/modules/base/store/index.ts
@@ -0,0 +1,18 @@
+import { useAppStore } from "./app";
+import { useMenuStore } from "./menu";
+import { useProcessStore } from "./process";
+import { useUserStore } from "./user";
+
+export function useBaseStore() {
+ const app = useAppStore();
+ const menu = useMenuStore();
+ const process = useProcessStore();
+ const user = useUserStore();
+
+ return {
+ app,
+ menu,
+ process,
+ user
+ };
+}
diff --git a/src/modules/base/store/menu.ts b/src/modules/base/store/menu.ts
new file mode 100644
index 0000000..db9012f
--- /dev/null
+++ b/src/modules/base/store/menu.ts
@@ -0,0 +1,189 @@
+import { ElMessage } from "element-plus";
+import { defineStore } from "pinia";
+import { ref } from "vue";
+import { deepTree, revDeepTree, storage } from "/@/cool/utils";
+import { isArray, isEmpty, isObject, orderBy } from "lodash";
+import { viewer, service, config } from "/@/cool";
+import { revisePath } from "../utils";
+
+declare enum Type {
+ "目录" = 0,
+ "菜单" = 1,
+ "权限" = 2
+}
+
+declare interface Item {
+ id: number;
+ parentId: number;
+ path: string;
+ router?: string;
+ viewPath?: string;
+ type: Type;
+ name: string;
+ icon: string;
+ orderNum: number;
+ isShow: number;
+ keepAlive?: number;
+ meta?: {
+ label: string;
+ keepAlive: number;
+ };
+ children?: Item[];
+}
+
+// 本地缓存
+const data = storage.info();
+
+export const useMenuStore = defineStore("menu", function () {
+ // 视图路由
+ const routes = ref- (data["menu.routes"] || []);
+
+ // 菜单组
+ const group = ref
- (data["menu.group"] || []);
+
+ // 顶部菜单序号
+ const index = ref(0);
+
+ // 左侧菜单列表
+ const list = ref
- ([]);
+
+ // 权限列表
+ const perms = ref(data["menu.perms"] || []);
+
+ // 设置左侧菜单
+ function setMenu(i: number) {
+ if (isEmpty(index)) {
+ i = index.value;
+ }
+
+ // 显示一级菜单
+ if (config.app.theme.showAMenu) {
+ const { children = [] } = group.value[i] || {};
+
+ index.value = i;
+ list.value = children;
+ } else {
+ list.value = group.value;
+ }
+ }
+
+ // 设置权限
+ function setPerms(list: Item[]) {
+ function deep(d: any) {
+ if (isObject(d)) {
+ if (d.permission) {
+ d._permission = {};
+ for (const i in d.permission) {
+ d._permission[i] =
+ list.findIndex((e: any) =>
+ e
+ .replace(/:/g, "/")
+ .includes(`${d.namespace.replace("admin/", "")}/${i}`)
+ ) >= 0;
+ }
+ } else {
+ for (const i in d) {
+ deep(d[i]);
+ }
+ }
+ }
+ }
+
+ perms.value = list;
+ storage.set("menu.perms", list);
+
+ deep(service);
+ }
+
+ // 设置视图
+ function setRoutes(list: Item[]) {
+ viewer.add(list);
+
+ routes.value = list;
+ storage.set("menu.routes", list);
+ }
+
+ // 设置菜单组
+ function setGroup(list: Item[]) {
+ group.value = orderBy(list, "orderNum");
+ storage.set("menu.group", group.value);
+ }
+
+ // 获取菜单,权限信息
+ function get(): Promise {
+ return new Promise((resolve, reject) => {
+ function next(res: any) {
+ if (!isArray(res.menus)) {
+ res.menus = [];
+ }
+
+ if (!isArray(res.perms)) {
+ res.perms = [];
+ }
+
+ const list = res.menus
+ .filter((e: Item) => e.type != 2)
+ .map((e: Item) => {
+ return {
+ id: e.id,
+ parentId: e.parentId,
+ path: revisePath(e.router || String(e.id)),
+ viewPath: e.viewPath,
+ type: e.type,
+ name: e.name,
+ icon: e.icon,
+ orderNum: e.orderNum,
+ isShow: e.isShow === undefined ? true : e.isShow,
+ meta: {
+ label: e.name,
+ keepAlive: e.keepAlive
+ },
+ children: []
+ };
+ });
+
+ // 设置权限
+ setPerms(res.perms);
+
+ // 设置菜单组
+ setGroup(deepTree(list));
+
+ // 设置视图路由
+ setRoutes(list.filter((e: Item) => e.type == 1));
+
+ // 设置菜单
+ setMenu(index.value);
+
+ resolve(group.value);
+ }
+
+ if (isEmpty(config.app.menu.list)) {
+ service.base.comm
+ .permmenu()
+ .then(next)
+ .catch((err) => {
+ ElMessage.error("菜单加载异常!");
+ reject(err);
+ });
+ } else {
+ // 自定义菜单
+ next({
+ menus: revDeepTree(config.app.menu.list)
+ });
+ }
+ });
+ }
+
+ return {
+ routes,
+ group,
+ index,
+ list,
+ perms,
+ get,
+ setPerms,
+ setMenu,
+ setRoutes,
+ setGroup
+ };
+});
diff --git a/src/modules/base/store/process.ts b/src/modules/base/store/process.ts
new file mode 100644
index 0000000..e44718e
--- /dev/null
+++ b/src/modules/base/store/process.ts
@@ -0,0 +1,72 @@
+import { defineStore } from "pinia";
+import { ref } from "vue";
+
+interface Item {
+ label: string;
+ value: string;
+ active?: boolean;
+ keepAlive?: boolean;
+}
+
+export const useProcessStore = defineStore("process", function () {
+ const menu1: Item = {
+ label: "首页",
+ value: "/",
+ active: true
+ };
+
+ const list = ref
- ([menu1]);
+
+ // 添加
+ function add(item: Item) {
+ const index = list.value.findIndex(
+ (e: Item) => e.value.split("?")[0] === item.value.split("?")[0]
+ );
+
+ list.value.map((e: Item) => {
+ e.active = e.value == item.value;
+ });
+
+ if (index < 0) {
+ if (item.value == "/") {
+ item.label = menu1.label;
+ }
+
+ if (item.label) {
+ list.value.push({
+ ...item,
+ active: true
+ });
+ }
+ } else {
+ list.value[index].active = true;
+ list.value[index].label = item.label;
+ list.value[index].value = item.value;
+ }
+ }
+
+ // 移除
+ function remove(index: number) {
+ if (index != 0) {
+ list.value.splice(index, 1);
+ }
+ }
+
+ // 设置
+ function set(data: Item[]) {
+ list.value = data;
+ }
+
+ // 重置
+ function reset() {
+ list.value = [menu1];
+ }
+
+ return {
+ list,
+ add,
+ remove,
+ set,
+ reset
+ };
+});
diff --git a/src/modules/base/store/user.ts b/src/modules/base/store/user.ts
new file mode 100644
index 0000000..4c964b2
--- /dev/null
+++ b/src/modules/base/store/user.ts
@@ -0,0 +1,105 @@
+import { defineStore } from "pinia";
+import { ref } from "vue";
+import { href, storage } from "/@/cool/utils";
+import { service, config, router } from "/@/cool";
+
+interface User {
+ id: number;
+ name: string;
+ username: string;
+ nickName: string;
+ phone: string;
+ headImg: string;
+ email: string;
+ status: 0 | 1;
+ departmentId: string;
+ createTime: Date;
+ [key: string]: any;
+}
+
+// 本地缓存
+const data = storage.info();
+
+export const useUserStore = defineStore("user", function () {
+ // 标识
+ const token = ref(config.test.token || data.token);
+
+ // 设置标识
+ function setToken(data: {
+ token: string;
+ expire: string;
+ refreshToken: string;
+ refreshExpire: string;
+ }) {
+ // 请求的唯一标识
+ token.value = data.token;
+ storage.set("token", data.token, data.expire);
+
+ // 刷新 token 的唯一标识
+ storage.set("refreshToken", data.refreshToken, data.refreshExpire);
+ }
+
+ // 刷新标识
+ async function refreshToken(): Promise {
+ return new Promise((resolve, reject) => {
+ service.base.open
+ .refreshToken({
+ refreshToken: storage.get("refreshToken")
+ })
+ .then((res) => {
+ setToken(res);
+ resolve(res.token);
+ })
+ .catch((err) => {
+ logout();
+ reject(err);
+ });
+ });
+ }
+
+ // 用户信息
+ const info = ref(data.userInfo);
+
+ // 设置用户信息
+ function set(value: any) {
+ info.value = value;
+ storage.set("userInfo", value);
+ }
+
+ // 清除用户
+ function clear() {
+ storage.remove("userInfo");
+ storage.remove("token");
+ token.value = "";
+ info.value = null;
+ }
+
+ // 退出
+ async function logout() {
+ try {
+ await service.base.comm.logout();
+ } catch {}
+
+ clear();
+ router.href("login");
+ }
+
+ // 获取用户信息
+ async function get() {
+ return service.base.comm.person().then((res) => {
+ set(res);
+ return res;
+ });
+ }
+
+ return {
+ token,
+ info,
+ get,
+ set,
+ logout,
+ clear,
+ setToken,
+ refreshToken
+ };
+});
diff --git a/src/modules/base/utils/index.ts b/src/modules/base/utils/index.ts
new file mode 100644
index 0000000..814ea94
--- /dev/null
+++ b/src/modules/base/utils/index.ts
@@ -0,0 +1,21 @@
+export function revisePath(path: string) {
+ if (!path) {
+ return "";
+ }
+
+ return path[0] == "/" ? path : `/${path}`;
+}
+
+export function createLink(url: string, id?: string) {
+ const link = document.createElement("link");
+ link.href = url;
+ link.type = "text/css";
+ link.rel = "stylesheet";
+ if (id) {
+ link.id = id;
+ }
+
+ setTimeout(() => {
+ document.getElementsByTagName("head").item(0)?.appendChild(link);
+ }, 0);
+}
diff --git a/src/modules/base/views/components/dept-check.vue b/src/modules/base/views/components/dept-check.vue
new file mode 100644
index 0000000..cbec7d3
--- /dev/null
+++ b/src/modules/base/views/components/dept-check.vue
@@ -0,0 +1,144 @@
+
+
+
{{ title }}
+
+
+
+
+
+ 是否关联上下级
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/base/views/components/dept-move.tsx b/src/modules/base/views/components/dept-move.tsx
new file mode 100644
index 0000000..94a5b53
--- /dev/null
+++ b/src/modules/base/views/components/dept-move.tsx
@@ -0,0 +1,124 @@
+import { useCool } from "/@/cool";
+import { deepTree } from "/@/cool/utils";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { defineComponent, h, ref } from "vue";
+import { useCrud, useForm } from "@cool-vue/crud";
+
+export default defineComponent({
+ name: "dept-move",
+
+ setup() {
+ const { service } = useCool();
+
+ // cl-form
+ const Form = useForm();
+
+ // cl-crud
+ const Crud = useCrud();
+
+ // 树形列表
+ const list = ref([]);
+
+ // 刷新列表
+ async function refresh() {
+ return await service.base.sys.department.list().then(deepTree);
+ }
+
+ // 转移
+ async function toMove(ids: any[]) {
+ list.value = await refresh();
+
+ Form.value?.open({
+ title: "部门转移",
+ width: "600px",
+
+ props: {
+ labelWidth: "80px"
+ },
+ items: [
+ {
+ label: "选择部门",
+ prop: "dept",
+ component: {
+ name: "slot-move"
+ }
+ }
+ ],
+ on: {
+ submit: (data: any, { done, close }: any) => {
+ if (!data.dept) {
+ ElMessage.warning("请选择部门");
+ return done();
+ }
+
+ const { name, id } = data.dept;
+
+ ElMessageBox.confirm(`是否将用户转移到部门 “${name}” 下`, "提示", {
+ type: "warning"
+ })
+ .then(() => {
+ service.base.sys.user
+ .move({
+ departmentId: id,
+ userIds: ids
+ })
+ .then(() => {
+ ElMessage.success("转移成功");
+ Crud.value?.refresh();
+ close();
+ })
+ .catch((err) => {
+ ElMessage.error(err.message);
+ done();
+ });
+ })
+ .catch(() => null);
+ }
+ }
+ });
+ }
+
+ return {
+ Form,
+ list,
+ refresh,
+ toMove
+ };
+ },
+
+ render(ctx: any) {
+ return (
+
+ {h(
+
,
+ {},
+ {
+ "slot-move"({ scope }: any) {
+ return (
+
+ {
+ scope["dept"] = e;
+ }}
+ >
+
+ );
+ }
+ }
+ )}
+
+ );
+ }
+});
diff --git a/src/modules/base/views/components/dept-tree.vue b/src/modules/base/views/components/dept-tree.vue
new file mode 100644
index 0000000..53bbfc7
--- /dev/null
+++ b/src/modules/base/views/components/dept-tree.vue
@@ -0,0 +1,447 @@
+
+
+