发布5.7.0

This commit is contained in:
icssoa 2022-07-21 19:07:32 +08:00
parent ba28f1332b
commit deba2c184b
197 changed files with 7329 additions and 18421 deletions

View File

@ -1,5 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules
.DS_Store
dist
dist-ssr
*.local

View File

@ -1,11 +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
# 🎨 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

View File

@ -56,6 +56,7 @@ module.exports = {
"vue/html-closing-bracket-newline": "off",
"vue/max-attributes-per-line": "off",
"vue/multiline-html-element-content-newline": "off",
"vue/multi-word-component-names": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/attribute-hyphenation": "off",
"vue/html-self-closing": "off",

6
.gitattributes vendored
View File

@ -1,4 +1,4 @@
*.js text eol=lf
*.json text eol=lf
*.ts text eol=lf
*.js text eol=lf
*.json text eol=lf
*.ts text eol=lf
*.vue text eol=lf

10
.gitignore vendored
View File

@ -1,5 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules
.DS_Store
dist
dist-ssr
*.local

View File

@ -1,8 +1,8 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "none"
}
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "none"
}

15
.vscode/config.code-snippets vendored Normal file
View File

@ -0,0 +1,15 @@
{
"module-config": {
"prefix": "module-config",
"scope": "typescript",
"body": [
"import { ModuleConfig } from \"/@/cool\";",
"",
"export default (): ModuleConfig => {",
" return {};",
"};",
""
],
"description": "module config snippets"
}
}

View File

@ -1,6 +1,7 @@
{
"cl-crud": {
"prefix": "cl-crud",
"scope": "vue",
"body": [
"<template>",
" <cl-crud ref=\"Crud\">",
@ -32,13 +33,11 @@
" </cl-crud>",
"</template>",
"",
"<script lang=\"ts\" setup>",
"<script lang=\"ts\" name=\"菜单名称\" setup>",
"import { useCrud, useTable, useUpsert } from \"@cool-vue/crud\";",
"import { useCool } from \"/@/cool\";",
"",
"const { service, named } = useCool();",
"",
"named(\"菜单名称\");",
"const { service } = useCool();",
"",
"// cl-upsert 配置",
"const Upsert = useUpsert({",

View File

@ -1,3 +1,4 @@
{
"editor.cursorSmoothCaretAnimation": true
"editor.cursorSmoothCaretAnimation": true,
"editor.formatOnSave": true,
}

View File

@ -1,16 +1,16 @@
FROM node:lts-alpine
WORKDIR /build
# 设置Node-Sass的镜像地址
RUN npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# 设置npm镜像
RUN npm config set registry https://registry.npm.taobao.org
COPY package.json /build/package.json
RUN yarn
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
FROM node:lts-alpine
WORKDIR /build
# 设置Node-Sass的镜像地址
RUN npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# 设置npm镜像
RUN npm config set registry https://registry.npm.taobao.org
COPY package.json /build/package.json
RUN yarn
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

42
LICENSE
View File

@ -1,21 +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.
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.

View File

@ -1,12 +1,11 @@
import { Plugin } from "vite";
import { parseJson } from "./utils";
import { getModules } from "./lib/modules";
import { createEps, getEps } from "./lib/eps";
import { createMenu } from "./lib/menu";
import { createEps, createMenu, createSvg, createTag, getEps, getModules } from "./lib";
export const cool = (): Plugin | null => {
export function cool(): Plugin {
return {
name: "vite-cool",
enforce: "pre",
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
function done(data: any) {
@ -14,10 +13,9 @@ export const cool = (): Plugin | null => {
res.end(JSON.stringify(data));
}
// 自定义
if (req.url.includes("__cool")) {
if (req.url?.includes("__cool")) {
const body = await parseJson(req);
let next: any = null;
let next: any;
switch (req.url) {
// 快速创建菜单
@ -54,6 +52,12 @@ export const cool = (): Plugin | null => {
}
});
},
transform(code, id) {
return createTag(code, id);
},
transformIndexHtml(html) {
return createSvg(html);
},
config() {
return {
define: {
@ -62,4 +66,4 @@ export const cool = (): Plugin | null => {
};
}
};
};
}

View File

@ -1,22 +1,17 @@
export default {
// 实体生成
entity: {
// 是否生成
enable: true,
mapping: [
{
// 自定义匹配
custom: ({ entityName, propertyName, type }) => {
// status原本是tinyint如果是1的话== true是可以的但是不能 === true请谨慎使用
// status 原本是tinyint如果是1的话== true 是可以的,但是不能 === true请谨慎使用
if (propertyName === "status" && type == "tinyint") return "boolean";
// 如果没有返回null或者不返回则继续遍历其他匹配规则
return null;
}
},
{
// 返回类型
type: "string",
// 匹配列类型
includes: ["varchar", "text"]
},
{

View File

@ -3,73 +3,73 @@ import { isEmpty, last } from "lodash";
import { createDir, firstUpperCase, readFile, toCamel } from "../../utils";
import { createWriteStream } from "fs";
import { join } from "path";
// import * as config from "/@/cool/config";
import config from "./config";
// 临时目录路径
const tempPath = join(__dirname, "../../temp");
// 创建描述文件
export async function createEps({ list, service }: any) {
const t0 = [
[
`
declare interface Crud {
/**
*
* @returns Promise<any>
*/
add(data: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
delete(data: { ids?: number[] | string[]; [key: string]: any }): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data: { id?: number | string; [key: string]: any }): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data: { id?: number | string; [key: string]: any }): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: { page?: number | string; size?: number | string; [key: string]: any }): Promise<PageResponse>;
}
`,
// 获取类型
function getType({ entityName, propertyName, type }) {
for (const map of config.entity.mapping) {
if (map.custom) {
const resType = map.custom({ entityName, propertyName, type });
if (resType) return resType;
}
if (map.includes?.includes(type)) return map.type;
}
return type;
}
`
declare interface PageResponse {
list: any[];
pagination: { size: number; page: number; total: number };
[key: string]: any;
}
`,
// 创建 Entity
function createEntity({ list }: any) {
const t0: any[] = [];
`
declare interface RequestOptions {
params?: any;
data?: any;
url: string;
method?: "GET" | "get" | "POST" | "post" | string;
[key: string]: any;
}
`
]
for (const item of list) {
if (!item.name) continue;
const t = [`interface ${item.name} {`];
for (const col of item.columns) {
// 描述
t.push("\n");
t.push("/**\n");
t.push(` * ${col.comment}\n`);
t.push(" */\n");
t.push(
`${col.propertyName}?: ${getType({
entityName: item.name,
propertyName: col.propertyName,
type: col.type
})};`
);
}
t.push("\n");
t.push("/**\n");
t.push(` * 任意键值\n`);
t.push(" */\n");
t.push(`[key: string]: any;`);
t.push("}");
t0.push(t);
}
return t0.map((e) => e.join("")).join("\n\n");
}
// 创建 Service
function createService({ list, service }: any) {
const t0: any[] = [];
const t1 = [
`type Service = {
request(options: {
url: string;
method?: 'POST' | 'GET' | string;
data?: any;
params?: any;
proxy?: boolean;
[key: string]: any;
}): Promise<any>;
`
];
const t1 = [`declare type Service = {`, `request(data: RequestOptions): Promise<any>;`];
// 处理数据
function deep(d: any, k?: string) {
if (!k) k = "";
@ -82,9 +82,7 @@ export async function createEps({ list, service }: any) {
const item = list.find((e: any) => (e.prefix || "").includes(d[i].namespace));
if (item) {
const t = [
`declare interface ${name} ${item.extendCrud ? " extends Crud" : ""} {`
];
const t = [`interface ${name} {`];
t1.push(`${i}: ${name};`);
@ -132,10 +130,28 @@ export async function createEps({ list, service }: any) {
// 返回类型
let res = "";
// 实体名
const en = item.name || "any";
switch (a.path) {
case "/page":
res = "PageResponse";
res = `
{
pagination: { size: number; page: number; total: number };
list: ${en} [];
[key: string]: any;
}
`;
break;
case "/list":
res = `${en} []`;
break;
case "/info":
res = en;
break;
default:
res = "any";
break;
@ -145,7 +161,6 @@ export async function createEps({ list, service }: any) {
t.push("\n");
t.push("/**\n");
t.push(` * ${a.summary || n}\n`);
t.push(` * @returns Promise<${res}>\n`);
t.push(" */\n");
t.push(
@ -155,15 +170,28 @@ export async function createEps({ list, service }: any) {
);
}
permission.push(`${n}: string;`);
permission.push(n);
});
// 添加权限
// 权限标识
t.push("\n");
t.push("/**\n");
t.push(" * 权限\n");
t.push(" * 权限标识\n");
t.push(" */\n");
t.push(`permission: { ${permission.join("\n")} }`);
t.push(
`permission: { ${permission.map((e) => `${e}: string;`).join("\n")} };`
);
// 权限状态
t.push("\n");
t.push("/**\n");
t.push(" * 权限状态\n");
t.push(" */\n");
t.push(
`_permission: { ${permission
.map((e) => `${e}: boolean;`)
.join("\n")} };`
);
}
t.push("}");
@ -177,14 +205,30 @@ export async function createEps({ list, service }: any) {
}
}
// 深度
deep(service);
// 结束
t1.push("}");
// 追加
t0.push(t1);
return t0.map((e) => e.join("")).join("\n\n");
}
// 创建描述文件
export async function createEps({ list, service }: any) {
// 文件内容
const text = `
declare namespace Eps {
${createEntity({ list })}
${createService({ list, service })}
}
`;
// 文本内容
const content = prettier.format(t0.map((e) => e.join("")).join("\n\n"), {
const content = prettier.format(text, {
parser: "typescript",
useTabs: true,
tabWidth: 4,
@ -198,12 +242,12 @@ export async function createEps({ list, service }: any) {
// 创建 temp 目录
createDir(tempPath);
// 创建 service 描述文件
createWriteStream(join(tempPath, "service.d.ts"), {
// 创建 eps 描述文件
createWriteStream(join(tempPath, "eps.d.ts"), {
flags: "w"
}).write(content);
// 创建 eps 文件
// 创建 eps 数据文件
createWriteStream(join(tempPath, "eps.json"), {
flags: "w"
}).write(
@ -213,71 +257,9 @@ export async function createEps({ list, service }: any) {
})
)
);
if (config.entity.enable) createEntity(list);
}
// 获取描述
export function getEps() {
return JSON.stringify(readFile(join(tempPath, "eps.json")));
}
// 获取类型
function getType({ entityName, propertyName, type }) {
for (const map of config.entity.mapping) {
if (map.custom) {
const resType = map.custom({ entityName, propertyName, type });
if (resType) return resType;
}
if (map.includes?.includes(type)) return map.type;
}
return type;
}
// 创建Entity描述文件
export function createEntity(list: any[]) {
const t2: any[] = [];
for (const item of list) {
if (!item.name) continue;
const t = [`declare interface ${item.name} {`];
for (const col of item.columns) {
// 描述
t.push("\n");
t.push("/**\n");
t.push(` * ${col.comment}\n`);
t.push(" */\n");
t.push(
`${col.propertyName}?: ${getType({
entityName: item.name,
propertyName: col.propertyName,
type: col.type
})};`
);
}
t.push("\n");
t.push("/**\n");
t.push(` * 任意键值\n`);
t.push(" */\n");
t.push(`[key: string]: any;`);
t.push("}");
t2.push(t);
}
// 文本内容
const content = prettier.format(t2.map((e) => e.join("")).join("\n\n"), {
parser: "typescript",
useTabs: true,
tabWidth: 4,
endOfLine: "lf",
semi: true,
singleQuote: false,
printWidth: 100,
trailingComma: "none"
});
// 创建 entity 描述文件
createWriteStream(join(tempPath, "entity.d.ts"), {
flags: "w"
}).write(content);
}

5
build/cool/lib/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from "./eps";
export * from "./menu";
export * from "./module";
export * from "./svg";
export * from "./tag";

View File

@ -1,7 +1,7 @@
import { createWriteStream } from "fs";
import prettier from "prettier";
import { join } from "path";
import { createDir } from "../../utils";
import { mkdirs } from "../../utils";
import rules from "./rules";
import { isFunction, isRegExp, isString } from "lodash";
@ -108,7 +108,7 @@ const handler = {
function createComponent(item: any) {
const { propertyName: prop, comment: label } = item;
let d = null;
let d: any = null;
rules.forEach((r: any) => {
const s = r.test.find((e: any) => {
@ -172,7 +172,7 @@ function getPageName(router: string) {
router = router.substr(1, router.length);
}
return router ? router.replace("/", "-") : "";
return router ? router.replace(/\//g, "-") : "";
}
// 时间合并
@ -198,7 +198,7 @@ function datetimeMerge({ columns, item }: any) {
}
// 创建文件
export async function createMenu({ router, columns, prefix, api, module, filename }: any): void {
export async function createMenu({ router, columns, prefix, api, filePath }: any) {
const upsert: any = {
items: []
};
@ -310,13 +310,11 @@ export async function createMenu({ router, columns, prefix, api, module, filenam
</cl-crud>
</template>
<script lang="ts" setup>
<script lang="ts" name="${getPageName(router)}" setup>
import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
const { service, named } = useCool();
named("${getPageName(router)}");
const { service } = useCool();
// cl-upsert 配置
const Upsert = useUpsert(${JSON.stringify(upsert)});
@ -335,6 +333,7 @@ const Crud = useCrud(
);
</script>`;
// 文件内容
const content = prettier.format(temp, {
parser: "vue",
useTabs: true,
@ -347,14 +346,17 @@ const Crud = useCrud(
trailingComma: "none"
});
// views 目录是否存在
const dir = join(__dirname, `../../../../src/modules/${module}/views`);
// 目录路径
const dir = filePath.split("/");
// 文件名
const fname = dir.pop();
// 创建目录
createDir(dir);
const path = mkdirs(`./src/modules/${dir.join("/")}`);
// 创建文件
createWriteStream(join(dir, `${filename}.vue`), {
createWriteStream(join(path, fname), {
flags: "w"
}).write(content);
}

View File

@ -0,0 +1,12 @@
import fs from "fs";
export function getModules() {
return new Promise((resolve, reject) => {
try {
const dirs = fs.readdirSync("./src/modules");
resolve(dirs.filter((e) => !e.includes(".")));
} catch (e) {
reject(e);
}
});
}

View File

@ -1,11 +0,0 @@
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);
}
}

View File

@ -0,0 +1,54 @@
import { readFileSync, readdirSync } from "fs";
import { extname } from "path";
function findFiles(dir: string): string[] {
const res: string[] = [];
const dirs = readdirSync(dir, {
withFileTypes: true
});
for (const d of dirs) {
if (d.isDirectory()) {
res.push(...findFiles(dir + d.name + "/"));
} else {
if (extname(d.name) == ".svg") {
const svg = readFileSync(dir + d.name)
.toString()
.replace(/(\r)|(\n)/g, "")
.replace(/<svg([^>+].*?)>/, (_: any, $2: any) => {
let width = 0;
let height = 0;
let content = $2.replace(
/(width|height)="([^>+].*?)"/g,
(_: any, s2: any, s3: any) => {
if (s2 === "width") {
width = s3;
} else if (s2 === "height") {
height = s3;
}
return "";
}
);
if (!/(viewBox="[^>+].*?")/g.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`;
}
return `<symbol id="icon-${d.name.replace(".svg", "")}" ${content}>`;
})
.replace("</svg>", "</symbol>");
res.push(svg);
}
}
}
return res;
}
export function createSvg(html: string) {
const res = findFiles("./src/modules/");
return html.replace(
"<body>",
`<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join("")}
</svg>`
);
}

View File

@ -0,0 +1,32 @@
import { parse, compileScript } from "@vue/compiler-sfc";
import magicString from "magic-string";
export function createTag(code: string, id: string) {
if (/\.vue$/.test(id)) {
let s: any;
const str = () => s || (s = new magicString(code));
const { descriptor } = parse(code);
if (!descriptor.script && descriptor.scriptSetup) {
const res = compileScript(descriptor, { id });
const { name, lang }: any = res.attrs;
str().appendLeft(
0,
`<script lang="${lang}">
import { defineComponent } from 'vue'
export default defineComponent({
name: "${name}"
})
<\/script>`
);
return {
map: str().generateMap(),
code: str().toString()
};
}
}
return null;
}

View File

@ -1,507 +0,0 @@
declare interface BaseSysDepartmentEntity {
/**
* ID
*/
id?: number;
/**
*
*/
name?: string;
/**
* ID
*/
parentId?: BigInt;
/**
*
*/
orderNum?: number;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface BaseSysLogEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
userId?: BigInt;
/**
*
*/
action?: string;
/**
* ip
*/
ip?: string;
/**
* ip地址
*/
ipAddr?: string;
/**
*
*/
params?: string;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface BaseSysMenuEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
parentId?: BigInt;
/**
*
*/
name?: string;
/**
*
*/
router?: string;
/**
*
*/
perms?: string;
/**
* 0 1 2
*/
type?: number;
/**
*
*/
icon?: string;
/**
*
*/
orderNum?: number;
/**
*
*/
viewPath?: string;
/**
*
*/
keepAlive?: boolean;
/**
*
*/
isShow?: boolean;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface BaseSysParamEntity {
/**
* ID
*/
id?: number;
/**
*
*/
keyName?: string;
/**
*
*/
name?: string;
/**
*
*/
data?: string;
/**
* 0:字符串 1 2
*/
dataType?: number;
/**
*
*/
remark?: string;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface BaseSysRoleEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
userId?: string;
/**
*
*/
name?: string;
/**
*
*/
label?: string;
/**
*
*/
remark?: string;
/**
*
*/
relevance?: number;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface BaseSysUserEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
departmentId?: BigInt;
/**
*
*/
name?: string;
/**
*
*/
username?: string;
/**
*
*/
password?: string;
/**
* , token失效
*/
passwordV?: number;
/**
*
*/
nickName?: string;
/**
*
*/
headImg?: string;
/**
*
*/
phone?: string;
/**
*
*/
email?: string;
/**
*
*/
remark?: string;
/**
* 0:禁用 1
*/
status?: boolean;
/**
* socketId
*/
socketId?: string;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface DemoGoodsEntity {
/**
* ID
*/
id?: number;
/**
*
*/
title?: string;
/**
*
*/
pic?: string;
/**
*
*/
price?: number;
/**
*
*/
type?: number;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface DictInfoEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
typeId?: number;
/**
*
*/
name?: string;
/**
*
*/
orderNum?: number;
/**
*
*/
remark?: string;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface DictTypeEntity {
/**
* ID
*/
id?: number;
/**
*
*/
name?: string;
/**
*
*/
key?: string;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface SpaceInfoEntity {
/**
* ID
*/
id?: number;
/**
*
*/
url?: string;
/**
*
*/
type?: string;
/**
* ID
*/
classifyId?: BigInt;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface SpaceTypeEntity {
/**
* ID
*/
id?: number;
/**
*
*/
name?: string;
/**
* ID
*/
parentId?: number;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
declare interface TaskInfoEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
jobId?: string;
/**
*
*/
repeatConf?: string;
/**
*
*/
name?: string;
/**
* cron
*/
cron?: string;
/**
*
*/
limit?: number;
/**
* cron设置了
*/
every?: number;
/**
*
*/
remark?: string;
/**
* 0:停止 1
*/
status?: boolean;
/**
*
*/
startDate?: Date;
/**
*
*/
endDate?: Date;
/**
*
*/
data?: string;
/**
* service实例ID
*/
service?: string;
/**
* 0:系统 1
*/
type?: number;
/**
*
*/
nextRunTime?: Date;
/**
* 0:cron 1
*/
taskType?: number;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}

1574
build/cool/temp/eps.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,952 +0,0 @@
declare interface Crud {
/**
*
* @returns Promise<any>
*/
add(data: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
delete(data: { ids?: number[] | string[]; [key: string]: any }): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data: { id?: number | string; [key: string]: any }): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data: { id?: number | string; [key: string]: any }): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: {
page?: number | string;
size?: number | string;
[key: string]: any;
}): Promise<PageResponse>;
}
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<any>
*/
personUpdate(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
uploadMode(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
permmenu(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
person(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
upload(data?: any): Promise<any>;
/**
* 退
* @returns Promise<any>
*/
logout(data?: any): Promise<any>;
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
* page
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
* update
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
* delete
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
* add
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
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<any>
*/
refreshToken(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
captcha(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
login(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
html(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
eps(data?: any): Promise<any>;
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
* page
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
* update
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
* delete
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
* add
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
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<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
order(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
* page
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
order: string;
list: string;
add: string;
page: string;
info: string;
};
}
declare interface BaseSysLog {
/**
*
* @returns Promise<any>
*/
setKeep(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
getKeep(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
clear(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
* update
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
* delete
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
* add
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
setKeep: string;
getKeep: string;
clear: string;
page: string;
list: string;
info: string;
update: string;
delete: string;
add: string;
};
}
declare interface BaseSysMenu {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface BaseSysParam {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
html(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
html: string;
info: string;
page: string;
add: string;
list: string;
};
}
declare interface BaseSysRole {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface BaseSysUser {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
move(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
move: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface DemoGoods {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
page: string;
list: string;
add: string;
};
}
declare interface DictInfo {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
data(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
data: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface DictType {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface SpaceInfo {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface SpaceType {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
}
declare interface TaskInfo {
/**
*
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
start(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
once(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
stop(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
*
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
*
* @returns Promise<any>
*/
log(data?: any): Promise<any>;
/**
*
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
start: string;
once: string;
stop: string;
info: string;
page: string;
log: string;
add: string;
list: string;
};
}
declare interface ChatMessage {
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
* page
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
* update
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
* delete
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
* add
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
list: string;
page: string;
info: string;
update: string;
delete: string;
add: string;
};
}
declare interface ChatSession {
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
* page
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
* update
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
* delete
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
* add
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
list: string;
page: string;
info: string;
update: string;
delete: string;
add: string;
};
}
declare interface Test {
/**
* list
* @returns Promise<any>
*/
list(data?: any): Promise<any>;
/**
* page
* @returns Promise<PageResponse>
*/
page(data?: any): Promise<PageResponse>;
/**
* info
* @returns Promise<any>
*/
info(data?: any): Promise<any>;
/**
* update
* @returns Promise<any>
*/
update(data?: any): Promise<any>;
/**
* delete
* @returns Promise<any>
*/
delete(data?: any): Promise<any>;
/**
* add
* @returns Promise<any>
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
list: string;
page: string;
info: string;
update: string;
delete: string;
add: string;
};
}
declare type Service = {
request(data: RequestOptions): Promise<any>;
base: {
comm: BaseComm;
open: BaseOpen;
sys: {
department: BaseSysDepartment;
log: BaseSysLog;
menu: BaseSysMenu;
param: BaseSysParam;
role: BaseSysRole;
user: BaseSysUser;
};
};
demo: { goods: DemoGoods };
dict: { info: DictInfo; type: DictType };
space: { info: SpaceInfo; type: SpaceType };
task: { info: TaskInfo };
chat: { message: ChatMessage; session: ChatSession };
test: Test;
};

View File

@ -1,4 +1,5 @@
import fs from "fs";
import { isAbsolute, join, relative, sep } from "path";
// 首字母大写
export function firstUpperCase(value: string): string {
@ -44,3 +45,24 @@ export function parseJson(req: any) {
});
});
}
// 深度创建目录
export function mkdirs(path: string) {
const arr = path.split(sep);
let p = "";
arr.forEach((e) => {
try {
fs.statSync(join(p, e));
} catch (err) {
try {
fs.mkdirSync(join(p, e));
} catch (error) {
console.error(error);
}
}
p = join(p, e);
});
return p;
}

View File

@ -1,106 +0,0 @@
import { Plugin } from "vite";
import { readFileSync, readdirSync, accessSync } from "fs";
import path from "path";
import { isArray } from "lodash";
let idPerfix = "";
const svgTitle = /<svg([^>+].*?)>/;
const clearHeightWidth = /(width|height)="([^>+].*?)"/g;
const hasViewBox = /(viewBox="[^>+].*?")/g;
const clearReturn = /(\r)|(\n)/g;
function findSvgFile(dir: string, uniqueNames: Record<string, boolean>): string[] {
const svgRes = [];
const dirents = readdirSync(dir, {
withFileTypes: true
});
for (const dirent of dirents) {
if (dirent.isDirectory()) {
svgRes.push(...findSvgFile(path.join(dir, dirent.name), uniqueNames));
} else if (uniqueNames[dirent.name]) {
continue;
} else {
uniqueNames[dirent.name] = true;
const svg = readFileSync(path.join(dir, dirent.name))
.toString()
.replace(clearReturn, "")
.replace(svgTitle, (_: any, $2: any) => {
let width = 0;
let height = 0;
let content = $2.replace(clearHeightWidth, (_: any, s2: any, s3: any) => {
if (s2 === "width") {
width = s3;
} else if (s2 === "height") {
height = s3;
}
return "";
});
if (!hasViewBox.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`;
}
return `<symbol id="${idPerfix}-${dirent.name.replace(
".svg",
""
)}" ${content}>`;
})
.replace("</svg>", "</symbol>");
svgRes.push(svg);
}
}
return svgRes;
}
export const svgBuilder = (paths: string | string[], perfix = "icon"): Plugin | null => {
if (paths) {
idPerfix = perfix;
paths = isArray(paths) ? paths : [paths];
const uniqueNames: Record<string, boolean> = {};
const res = paths.reduce(
(previousValue, currentValue) =>
previousValue.concat(findSvgFile(currentValue, uniqueNames)),
[]
);
return {
name: "svg-transform",
transformIndexHtml(html): string {
return html.replace(
"<body>",
`
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join("")}
</svg>
`
);
}
};
} else {
return null;
}
};
export const findSvgFolders = (dir: string): string[] => {
const svgFolders = [];
const dirents = readdirSync(dir, {
withFileTypes: true
});
// 找到结构为icons/svg的文件夹
for (const dirent of dirents) {
if (dirent.isDirectory()) {
const testPath =
dirent.name === "icons"
? path.join(dir, "icons/svg")
: path.join(dir, dirent.name, "icons/svg");
try {
accessSync(testPath);
svgFolders.push(testPath);
} catch (e) {
continue;
}
}
}
return svgFolders;
};

View File

@ -9,7 +9,7 @@
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
/>
<title>COOL-ADMIN</title>
<title></title>
<link rel="icon" href="favicon.ico" />
<style>
html,
@ -144,20 +144,20 @@
</style>
</head>
<body>
<div id="app">
<div class="preload__wrap">
<div class="preload__container">
<p class="preload__name">COOL-ADMIN</p>
<div class="preload__loading"></div>
<p class="preload__title">正在加载资源...</p>
<p class="preload__sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
</div>
<div class="preload__wrap" id="Loading">
<div class="preload__container">
<p class="preload__name">COOL-ADMIN</p>
<div class="preload__loading"></div>
<p class="preload__title">正在加载资源...</p>
<p class="preload__sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
</div>
<div class="preload__footer">
<a href="https://cool-js.com/" target="_blank"> https://cool-js.com </a>
</div>
<div class="preload__footer">
<a href="https://cool-js.com/" target="_blank"> https://cool-js.com </a>
</div>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,123 +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:8001;
}
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;
}
}
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:8001;
}
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;
}
}
}

9274
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "front-next",
"version": "5.6.2",
"version": "5.7.0",
"scripts": {
"dev": "vite --host",
"build": "vite build",
@ -9,63 +9,60 @@
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix"
},
"dependencies": {
"@cool-vue/crud": "^5.2.10",
"@element-plus/icons-vue": "^1.1.3",
"@vueuse/core": "^8.2.5",
"@codemirror/lang-javascript": "^6.0.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@cool-vue/crud": "^5.3.0",
"@element-plus/icons-vue": "^2.0.6",
"@vueuse/core": "^8.9.4",
"axios": "^0.27.2",
"codemirror": "^5.62.0",
"core-js": "^3.6.5",
"echarts": "^5.0.2",
"element-plus": "^2.2.5",
"codemirror": "^6.0.1",
"core-js": "^3.23.5",
"echarts": "^5.3.3",
"element-plus": "^2.2.9",
"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",
"pinia": "^2.0.16",
"quill": "^1.3.7",
"socket.io-client": "^4.5.1",
"store": "^2.0.12",
"unocss": "^0.31.0",
"vue": "^3.2.32",
"vue-echarts": "^6.0.2",
"vue-router": "^4.0.14",
"unocss": "^0.44.3",
"vue": "^3.2.37",
"vue-codemirror": "^6.0.0",
"vue-echarts": "^6.2.3",
"vue-router": "^4.1.2",
"vuedraggable": "^4.1.0",
"xlsx": "^0.16.9"
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/lodash": "^4.14.168",
"@types/node": "^16.10.2",
"@types/lodash": "^4.14.182",
"@types/mockjs": "^1.0.6",
"@types/node": "^18.0.6",
"@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",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@unocss/preset-uno": "^0.44.3",
"@vitejs/plugin-vue": "^3.0.1",
"@vitejs/plugin-vue-jsx": "^2.0.0",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/compiler-sfc": "^3.2.37",
"@vue/composition-api": "^1.7.0",
"eslint": "^8.20.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"
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.2.0",
"magic-string": "^0.26.2",
"prettier": "^2.7.1",
"sass": "^1.53.0",
"sass-loader": "^13.0.2",
"typescript": "^4.7.4",
"vite": "^3.0.2",
"vite-plugin-compression": "^0.5.1"
}
}

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,18 +1,5 @@
<template>
<el-config-provider :locale="zhCn">
<div class="preload__wrap" v-if="app.loading">
<div class="preload__container">
<p class="preload__name">{{ app.info.name }}</p>
<div class="preload__loading"></div>
<p class="preload__title">正在加载菜单...</p>
<p class="preload__sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
</div>
<div class="preload__footer">
<a href="https://cool-js.com" target="_blank"> https://cool-js.com </a>
</div>
</div>
<router-view />
</el-config-provider>
</template>
@ -20,9 +7,4 @@
<script lang="ts" setup>
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import { useBase } from "/$/base";
const { app } = useBase();
</script>
<style lang="scss" src="./assets/css/index.scss"></style>

View File

@ -1,44 +0,0 @@
* {
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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,56 +1,36 @@
import { createPinia } from "pinia";
import { App } from "vue";
import { useModule } from "./module";
import { router, viewer } from "./router";
import { modular } from "./module";
import { router } from "./router";
import { useBase } 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";
import { useDict } from "/$/dict";
export async function bootstrap(Vue: App) {
// 缓存
// pinia
Vue.use(createPinia());
// ui库
// element-plus
Vue.use(ElementPlus);
// 事件通讯
// mitt
Vue.provide("mitt", mitt());
// 可视图表
// charts
Vue.component("v-chart", VueECharts);
// 基础
const { app, user, menu } = useBase();
// 加载模块
useModule(Vue);
// 取缓存视图
viewer.add(menu.routes);
// 路由
Vue.use(router);
// 开启
app.showLoading();
// 模块
Vue.use(modular);
if (user.token) {
// 字典
const { dict } = useDict();
// 数据
const { app } = useBase();
// 获取字典数据
dict.refresh();
// 获取用户信息
user.get();
// 获取菜单权限
await menu.get();
}
app.hideLoading();
// 事件加载
app.req = modular.emit("onLoad");
}

View File

@ -22,10 +22,8 @@ export const config = {
router: {
// 模式
mode: "history",
// 页面
pages: [],
// 视图 / 路由下的 children
views: []
// 首页组件
home: import("/$/demo/views/home/index.vue")
},
// 字体图标库

View File

@ -1,7 +1,10 @@
import { onBeforeUpdate, ref, inject, getCurrentInstance } from "vue";
import { Emitter } from "mitt";
import { onBeforeUpdate, ref, inject } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useService } from "../service";
const service = useService();
export function useRefs() {
const refs: any = ref<any[]>([]);
@ -16,34 +19,12 @@ export function useRefs() {
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<any>("mitt");
// 路由
const route = useRoute();
// 路由器
const router = useRouter();
return {
route,
router,
refs,
setRefs,
service,
mitt,
named
route: useRoute(),
router: useRouter(),
mitt: inject("mitt") as Emitter<any>,
...useRefs()
};
}

View File

@ -3,4 +3,6 @@ export * from "./bootstrap";
export * from "./hook";
export * from "./router";
export * from "./config";
export { storage, module } from "./utils";
export * from "./module";
export * from "./types/index.d";
export { storage, hideLoading } from "./utils";

View File

@ -1,162 +1,121 @@
import { App } from "vue";
import modules from "/@/modules";
import { router, viewer } from "../router";
import { filename, module } from "../utils";
import { isFunction, isObject } from "lodash";
import { isFunction, orderBy } from "lodash";
import { Module } from "../types";
import { filename } from "../utils";
// 扫描文件
const files = import.meta.globEager("/src/modules/**/*");
const files: any = import.meta.glob("/src/modules/*/{config.ts,service/**,directives/**}", {
eager: true
});
// 模块列表
const list: any[] = [...modules];
// @ts-ignore
const list: Module[] = window.__modules__ || (window.__modules__ = []);
function main() {
for (const i in files) {
// 模块名
const [, , , name, action] = i.split("/");
// 模块
const module = {
list,
// 文件内容
let value: any = null;
get(name: string): Module {
// @ts-ignore
return this.list.find((e) => e.name == name);
},
try {
value = files[i].default;
} catch (err) {
console.error(err, i);
value = files[i];
}
add(data: Module) {
this.list.push(data);
}
};
if (!value) {
continue;
}
// 解析
for (const i in files) {
// 分割
const [, , , name, action] = i.split("/");
// 文件名
const fname: string = filename(i);
// 文件名
const fname = filename(i);
// 文件内容
const v = files[i]?.default;
// 模块是否存在
const m = module.get(name);
// 数据
const d = m || {
name,
value: null,
services: [],
directives: []
};
switch (action) {
// 配置参数
function next(d: any) {
// 配置参数入口
if (action == "config.ts") {
d.options = value || {};
case "config.ts":
d.value = v;
break;
// 请求服务
case "service":
const s = new v();
if (s) {
d.services?.push({
path: s.namespace,
value: s
});
}
break;
// 模块入口
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: []
})
);
}
// 指令
case "directives":
d.directives?.push({ name: fname, value: v });
break;
}
module.set(list);
if (!m) {
module.add(d);
}
}
main();
// 模块处理器
const modular = {
install(app: App) {
module.list.forEach((e) => {
const d = isFunction(e.value) ? e.value(app) : e.value;
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);
}
}
if (d) {
Object.assign(e, d);
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]);
}
}
// 注册组件
e.components?.forEach(async (c: any) => {
const v = await (isFunction(c) ? c() : c);
const n = v.default || v;
app.component(n.name, n);
});
// 注册指令
e.directives?.forEach((v) => {
app.directive(v.name, v.value);
});
// 安装事件
if (d.install) {
d.install(app, d.options);
}
}
});
},
async emit(name: "onLoad") {
const list = orderBy(module.list, "order");
const events = {};
// 注册指令
if (e.directives) {
for (const i in e.directives) {
app.directive(i, e.directives[i]);
}
for (let i = 0; i < list.length; i++) {
if (list[i][name]) {
// @ts-ignore
const e = await list[i][name](events);
Object.assign(events, e);
}
// 注册页面
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);
}
});
}
}
};
export { module, modular };

View File

@ -1,82 +1,41 @@
// @ts-nocheck
import { ElMessage } from "element-plus";
import {
createRouter,
createWebHashHistory,
createWebHistory,
NavigationGuardNext,
RouteRecordRaw
} from "vue-router";
import { storage, config } from "/@/cool";
import { createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw } from "vue-router";
import { config, Router, storage, module, hideLoading } from "/@/cool";
import { isArray } from "lodash";
import { useBase } 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 files = import.meta.glob(["/src/modules/*/{views,pages}/**/*", "!**/components"]);
// 默认路由
const routes: RouteRecordRaw[] = [
{
path: "/",
name: "index",
component: () => import("/$/base/pages/layout/index.vue"),
component: () => import("/$/base/layout/index.vue"),
children: [
{
path: "/",
name: "数据统计",
component: () => import("/@/views/home/index.vue")
},
...config.app.router.views
path: "",
name: "home",
component: config.app.router.home
}
]
},
...config.app.router.pages,
{
path: "/:catchAll(.*)",
name: "404",
redirect: "/404"
}
];
// 创建
// 创建路由器
const router = createRouter({
history: config.app.router.mode == "history" ? createWebHistory() : createWebHashHistory(),
routes
}) as CoolRouter;
}) as Router;
// 路由守卫
router.beforeEach((to: any, _: any, next: NavigationGuardNext) => {
const { user, process } = useBase();
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.beforeResolve(() => {
hideLoading();
});
// 自定义
router.href = function (path: string) {
// 跳转
router.href = function (path) {
const url = import.meta.env.BASE_URL + path;
if (url != location.pathname) {
@ -84,6 +43,44 @@ router.href = function (path: string) {
}
};
// 添加试图,页面路由
router.append = function (data) {
const list = isArray(data) ? data : [data];
list.forEach((e) => {
const d = { ...e };
if (!d.name) {
d.name = d.path.substring(1);
}
if (e.isPage) {
router.addRoute(d);
} else {
if (!d.component) {
const url = d.viewPath;
if (url) {
if (url.indexOf("http") == 0) {
if (d.meta) {
d.meta.iframeUrl = url;
}
d.component = () => import(`/$/base/views/iframe/index.vue`);
} else {
d.component = files["/src/" + url.replace("cool/", "")];
}
} else {
d.redirect = "/404";
}
}
// @ts-ignore
router.addRoute("index", d);
}
});
};
let lock = false;
// 错误监听
@ -100,45 +97,93 @@ router.onError((err: any) => {
}
});
// 视图
const viewer = {
add(data: any[] | any) {
// 列表
const list = isArray(data) ? data : [data];
// 注册
async function register(path: string) {
// 当前路由是否存在
const d = router.getRoutes().find((e) => e.path == path);
list.forEach((e: any) => {
const d: any = cloneDeep(e);
if (!d) {
const { app, menu } = useBase();
// 命名
d.name = d.router;
// 等待加载
await app.req;
if (!d.component) {
const url = d.viewPath;
// 待注册列表
const list: any[] = [];
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";
}
// 菜单数据
menu.routes.find((e) => {
list.push({
...e,
isPage: e.viewPath?.includes("/pages")
});
});
// 模块数据
module.list.forEach((e) => {
if (e.views) {
list.push(...e.views);
}
// 批量添加
router.addRoute("index", d);
if (e.pages) {
list.push(
...e.pages.map((d) => {
return {
...d,
isPage: true
};
})
);
}
});
},
get() {
return router.getRoutes().find((e) => e.name == "index")?.children;
// 需要注册的路由
const r = list.find((e) => e.path == path);
if (r) {
router.append(r);
}
return r?.path || "/404";
} else {
return null;
}
};
}
export { router, viewer };
// 路由守卫
router.beforeEach(async (to, from, next) => {
// 注册路由
const path = await register(to.path);
if (path) {
// 重定向
next({ ...to, path });
} else {
// 数据缓存
const { user, process } = useBase();
// 登录成功
if (user.token) {
// 在登录页
if (to.path.includes("/login")) {
// Token 未过期
if (!storage.isExpired("token")) {
// 回到首页
return next("/");
}
} else {
// 添加路由进程
process.add(to);
}
} else {
// 忽略部分 Token 验证
if (!config.ignore.token.find((e) => to.path == e)) {
return next("/login");
}
}
next();
}
});
export { router };

View File

@ -46,15 +46,7 @@ export class BaseService {
}
}
request(
options = {} as {
params?: any;
data?: any;
url: string;
method?: "GET" | "get" | "POST" | "post" | string;
[key: string]: any;
}
) {
request(options: any = {}) {
if (!options.params) options.params = {};
let ns = "";
@ -91,7 +83,7 @@ export class BaseService {
});
}
page(data: { page?: number; size?: number; [key: string]: any }) {
page(data: any) {
return this.request({
url: "/page",
method: "POST",
@ -99,14 +91,14 @@ export class BaseService {
});
}
info(params: { id?: number | string; [key: string]: any }) {
info(params: any) {
return this.request({
url: "/info",
params
});
}
update(data: { id?: number | string; [key: string]: any }) {
update(data: any) {
return this.request({
url: "/update",
method: "POST",
@ -114,7 +106,7 @@ export class BaseService {
});
}
delete(data: { ids?: number[] | string[]; [key: string]: any }) {
delete(data: any) {
return this.request({
url: "/delete",
method: "POST",

View File

@ -13,7 +13,7 @@ function getNames(v: any) {
// 标签名
const names = getNames(new BaseService());
export function useEps(service: Service) {
export function useEps(service: Eps.Service) {
// 创建描述文件
function createDts(list: any[]) {
function deep(v: any) {

View File

@ -1,9 +1,35 @@
import { deepFiles, deepMerge, module } from "../utils";
import { deepMerge, basename } from "../utils";
import { BaseService } from "./base";
import { useEps } from "./eps";
import { module } from "../module";
// 路径转对象
function deepFiles(list: any[]) {
const data: any = {};
list.forEach(({ path, value }) => {
const arr: string[] = path.split("/");
const parents = arr.slice(0, arr.length - 1);
const name = basename(path).replace(".ts", "");
let curr = data;
parents.forEach((k) => {
if (!curr[k]) {
curr[k] = {};
}
curr = curr[k];
});
curr[name] = value;
});
return data;
}
// 基础服务
export const service: Service = {
export const service: Eps.Service = {
request: new BaseService().request
};
@ -13,7 +39,7 @@ export function useService() {
// 模块内容
module.list.forEach((e) => {
deepMerge(service, deepFiles(e.service || []));
deepMerge(service, deepFiles(e.services || []));
});
return service;

View File

@ -15,22 +15,22 @@ NProgress.configure({
});
// 请求队列
let requests: Array<Function> = [];
let requests: Array<(token: string) => void> = [];
// Token 是否刷新中
// 是否刷新中
let isRefreshing = false;
// @ts-ignore
// @ts-ignore 避免热更新后多次执行
axios.interceptors.request.eject(axios._req);
// @ts-ignore
// @ts-ignore 请求
axios._req = axios.interceptors.request.use(
(req: any) => {
(req) => {
const { user } = useBase();
if (req.url) {
// 请求进度条
if (!config.ignore.NProgress.some((e: string) => req.url.includes(e))) {
if (!config.ignore.NProgress.some((e) => req.url?.includes(e))) {
NProgress.start();
}
}
@ -46,9 +46,11 @@ axios._req = axios.interceptors.request.use(
// 验证 token
if (user.token) {
// 请求标识
req.headers["Authorization"] = user.token;
if (req.headers) {
req.headers["Authorization"] = user.token;
}
if (req.url.includes("refreshToken")) {
if (req.url?.includes("refreshToken")) {
return req;
}
@ -64,7 +66,7 @@ axios._req = axios.interceptors.request.use(
isRefreshing = true;
user.refreshToken()
.then((token: string) => {
.then((token) => {
requests.forEach((cb) => cb(token));
requests = [];
isRefreshing = false;
@ -76,9 +78,11 @@ axios._req = axios.interceptors.request.use(
return new Promise((resolve) => {
// 继续请求
requests.push((token: string) => {
requests.push((token) => {
// 重新设置 token
req.headers["Authorization"] = token;
if (req.headers) {
req.headers["Authorization"] = token;
}
resolve(req);
});
});

33
src/cool/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
import { App, Component, Directive } from "vue";
import { Router as VueRouter, RouteRecordRaw } from "vue-router";
export declare interface ModuleConfig {
order?: number;
options?: {
[key: string]: any;
};
components?: Component[];
views?: RouteRecordRaw[];
pages?: RouteRecordRaw[];
install?(app: App, options?: { [key: string]: any }): void;
onLoad?(events: {
hasToken: (cb: () => Promise<any> | void) => Promise<any> | void;
[key: string]: any;
}): Promise<{ [key: string]: any }> | void;
}
export declare interface Module extends ModuleConfig {
name: string;
options: {
[key: string]: any;
};
value?: any;
services?: { path: string; value: any }[];
directives?: { name: string; value: Directive }[];
}
export declare interface Router extends VueRouter {
href(path: string): void;
append(data: any[] | any): void;
[key: string]: any;
}

View File

@ -1,6 +1,5 @@
import { isArray, orderBy } from "lodash";
import storage from "./storage";
import module from "./module";
// 首字母大写
export function firstUpperCase(value: string): string {
@ -32,37 +31,35 @@ export function getUrlParam(name: string): string | null {
return null;
}
// 文件路径转对象
export function deepFiles(list: any[]) {
const modules: any = {};
// 路径转数组
export function deepPaths(paths: string[], splitor?: string) {
const list: any[] = [];
list.forEach((e) => {
const arr = e.path.split("/");
const parents = arr.slice(0, arr.length - 1);
const name = basename(e.path).replace(".ts", "");
paths.forEach((e) => {
const arr: string[] = e.split(splitor || "/").filter(Boolean);
let curr: any = modules;
let prev: any = null;
let key: any = null;
let c = list;
parents.forEach((k: string) => {
if (!curr[k]) {
curr[k] = {};
arr.forEach((a, i) => {
let d = c.find((e) => e.label == a);
if (!d) {
d = {
label: a,
value: a,
children: arr[i + 1] ? [] : null
};
c.push(d);
}
prev = curr;
curr = curr[k];
key = k;
if (d.children) {
c = d.children;
}
});
if (name == "index") {
prev[key] = e.value;
} else {
curr[name] = e.value;
}
});
return modules;
return list;
}
// 文件名
@ -213,7 +210,7 @@ export function getBrowser() {
// 列表转树形
export function deepTree(list: any[]): any[] {
const newList: Array<any> = [];
const newList: any[] = [];
const map: any = {};
list.forEach((e) => (map[e.id] = e));
@ -230,9 +227,8 @@ export function deepTree(list: any[]): any[] {
const fn = (list: Array<any>) => {
list.map((e) => {
if (e.children instanceof Array) {
if (isArray(e.children)) {
e.children = orderBy(e.children, "orderNum");
fn(e.children);
}
});
@ -240,15 +236,15 @@ export function deepTree(list: any[]): any[] {
fn(newList);
return orderBy(newList, "orderNum");
return orderBy(newList, "orderNum").filter((e) => !e.parentId);
}
// 树形转列表
export function revDeepTree(list: Array<any> = []) {
const d: Array<any> = [];
export function revDeepTree(list: any[]) {
const arr: any[] = [];
let id = 0;
const deep = (list: Array<any>, parentId: any) => {
function deep(list: any[], parentId: any) {
list.forEach((e) => {
if (!e.id) {
e.id = id++;
@ -256,17 +252,25 @@ export function revDeepTree(list: Array<any> = []) {
e.parentId = parentId;
d.push(e);
arr.push(e);
if (e.children && isArray(e.children)) {
deep(e.children, e.id);
}
});
};
}
deep(list || [], null);
return d;
return arr;
}
export { storage, module };
export function hideLoading() {
const el = document.getElementById("Loading");
if (el) {
el.style.display = "none";
}
}
export { storage };

View File

@ -1,31 +0,0 @@
// @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;

3
src/env.d.ts vendored
View File

@ -1,5 +1,4 @@
/// <reference types="@cool-vue/crud" />
/// <reference types="../build/cool/temp/service" />
/// <reference types="../build/cool/temp/entity" />
/// <reference types="../build/cool/temp/eps" />
declare const __EPS__: string;

View File

@ -2,9 +2,6 @@ import { createApp } from "vue";
import App from "./App.vue";
import { bootstrap } from "./cool";
// mock
// import "./mock";
const app = createApp(App);
// 启动

View File

@ -1,3 +0,0 @@
// @ts-nocheck
const xhr = new window._XMLHttpRequest();
window.XMLHttpRequest.prototype.upload = xhr.upload;

View File

@ -1,4 +1,8 @@
import "./resize";
import { resize } from "./resize";
window.onload = function () {
resize();
};
export * from "./theme";
export * from "./permission";

View File

@ -1,5 +1,5 @@
import { useStore } from "../store";
import { isArray, isObject } from "lodash";
import { isObject } from "lodash";
function parse(value: any) {
const { menu } = useStore();
@ -11,7 +11,7 @@ function parse(value: any) {
}
}
export function checkPerm(value: any) {
export function checkPerm(value: string | { or?: string[]; and?: string[] }) {
if (!value) {
return false;
}
@ -28,15 +28,3 @@ export function checkPerm(value: any) {
return parse(value);
}
export function getPerm(service: any, names: string[] | string) {
if (!service._permission) {
return false;
}
if (!isArray(names)) {
names = [names];
}
return !names.find((e) => !service._permission[e]);
}

View File

@ -1,13 +1,13 @@
import { useEventListener } from "@vueuse/core";
import { useStore } from "../store";
function resize() {
function update() {
const { app } = useStore();
app.setBrowser();
app.isFold = app.browser.isMini;
}
window.onload = function () {
useEventListener(window, "resize", resize);
resize();
};
export function resize() {
useEventListener(window, "resize", update);
update();
}

View File

@ -13,7 +13,9 @@ if (config.app.iconfont) {
createLink("//at.alicdn.com/t/font_3254019_60a2xxj8uus.css");
// svg 图标加载
const svgFiles = import.meta.globEager("/src/icons/svg/**/*.svg");
const svgFiles = import.meta.glob("/src/modules/*/static/**/*.svg", {
eager: true
});
function iconList() {
const list: string[] = [];

View File

@ -1,142 +1,86 @@
<template>
<div ref="editorRef" class="cl-codemirror">
<textarea id="editor" class="cl-code" :height="height" :width="width"></textarea>
<div class="cl-codemirror__tools">
<el-button @click="formatCode">格式化</el-button>
</div>
<div class="cl-codemirror">
<codemirror
v-model="content"
:placeholder="placeholder"
:style="{ height, fontSize }"
autofocus
indent-with-tab
:tab-size="4"
:extensions="extensions"
@change="onChange"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, nextTick, onMounted, ref, watch } from "vue";
import CodeMirror from "codemirror";
import beautifyJs from "js-beautify";
import "codemirror/lib/codemirror.css";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/theme/hopscotch.css";
import "codemirror/addon/hint/javascript-hint";
import "codemirror/mode/javascript/javascript";
import { deepMerge } from "/@/cool/utils";
<script lang="ts" name="cl-codemirror" setup>
import { Codemirror } from "vue-codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { oneDark } from "@codemirror/theme-one-dark";
import { ref, watch } from "vue";
import { useDark } from "@vueuse/core";
export default defineComponent({
name: "cl-codemirror",
props: {
modelValue: null,
height: String,
width: String,
options: Object
const props = defineProps({
modelValue: {
type: String,
required: true
},
emits: ["update:modelValue", "load"],
setup(props, { emit }) {
const editorRef = ref<any>(null);
let editor: any = null;
//
function getValue() {
if (editor) {
return editor.getValue();
} else {
return "";
}
}
//
function setValue(val?: string) {
if (editor) {
editor.setValue(val || "");
}
}
//
function formatCode() {
if (editor) {
editor.setValue(beautifyJs(getValue()));
}
}
//
watch(
() => props.modelValue,
(val: string) => {
if (editor && val != getValue()) {
setValue(val);
}
}
);
onMounted(function () {
nextTick(() => {
//
editor = CodeMirror.fromTextArea(
editorRef.value.querySelector("#editor"),
deepMerge(
{
mode: "javascript",
theme: "hopscotch",
styleActiveLine: true,
lineNumbers: true,
lineWrapping: true,
indentWithTabs: true,
indentUnit: 4,
extraKeys: { Ctrl: "autocomplete" },
foldGutter: true,
autofocus: true,
matchBrackets: true,
autoCloseBrackets: true,
gutters: [
"CodeMirror-linenumbers",
"CodeMirror-foldgutter",
"CodeMirror-lint-markers"
]
},
props.options
)
);
//
editor.on("change", (e: any) => {
emit("update:modelValue", e.getValue());
});
//
setValue(props.modelValue);
//
formatCode();
//
emit("load", editor);
//
editor.setSize(props.width || "auto", props.height || "auto");
});
});
return {
editorRef,
formatCode
};
placeholder: {
type: String,
default: "请输入"
},
height: {
type: String,
default: "400px"
},
fontSize: {
type: String,
default: "14px"
}
});
const emit = defineEmits(["update:modelValue", "change"]);
//
const isDark = ref(useDark());
//
const extensions: any[] = [javascript(), isDark.value && oneDark];
//
const content = ref("");
//
function onChange(value: string) {
emit("update:modelValue", value);
emit("change", value);
}
watch(
() => props.modelValue,
(val) => {
content.value = val;
},
{
immediate: true
}
);
</script>
<style lang="scss">
<style lang="scss" scoped>
.cl-codemirror {
border-radius: 3px;
border: 1px solid #dcdfe6;
border: 1px solid var(--el-border-color);
box-sizing: border-box;
border-radius: 3px;
line-height: 150%;
&__tools {
background-color: #322931;
padding: 10px;
border-top: 1px solid #444;
:deep(.cm-editor) {
.cm-foldGutter {
width: 12px;
text-align: center;
}
&.cm-focused {
outline: 0;
}
}
}
</style>

View File

@ -4,50 +4,34 @@
</svg>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from "vue";
<script lang="ts" name="cl-svg" setup>
import { computed, ref } from "vue";
import { isNumber } from "lodash";
export default defineComponent({
name: "icon-svg",
cool: {
global: true
const props = defineProps({
name: {
type: String
},
props: {
name: {
type: String
},
className: {
type: String
},
size: {
type: [String, Number]
}
className: {
type: String
},
setup(props) {
const style = ref<any>({
fontSize: isNumber(props.size) ? props.size + "px" : props.size
});
const iconName = computed<string>(() => `#icon-${props.name}`);
const svgClass = computed<Array<string>>(() => {
return ["icon-svg", `icon-svg__${props.name}`, String(props.className || "")];
});
return {
style,
iconName,
svgClass
};
size: {
type: [String, Number]
}
});
const style = ref({
fontSize: isNumber(props.size) ? props.size + "px" : props.size
});
const iconName = computed(() => `#icon-${props.name}`);
const svgClass = computed(() => {
return ["cl-svg", `cl-svg__${props.name}`, String(props.className || "")];
});
</script>
<style scoped>
.icon-svg {
<style lang="scss" scoped>
.cl-svg {
width: 1em;
height: 1em;
vertical-align: -0.15em;

View File

@ -45,10 +45,7 @@ export default defineComponent({
type: [Number, Array],
default: 100
},
lazy: {
type: Boolean,
default: true
},
lazy: Boolean,
fit: {
type: String,
default: "cover"

View File

@ -25,56 +25,45 @@
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from "vue";
<script lang="ts" name="cl-view-group" setup>
import { ref, watch } from "vue";
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
import { useBase } from "/$/base";
export default defineComponent({
name: "cl-view-group",
components: {
ArrowLeft,
ArrowRight
},
props: {
title: String
},
setup() {
const { app } = useBase();
defineProps({
title: String
});
//
const isExpand = ref<boolean>(true);
const { app } = useBase();
//
function toExpand(value?: boolean) {
isExpand.value = value === undefined ? !isExpand.value : value;
}
//
const isExpand = ref<boolean>(true);
//
function checkExpand(value?: boolean) {
if (app.browser.isMini) {
toExpand(value);
}
}
//
function toExpand(value?: boolean) {
isExpand.value = value === undefined ? !isExpand.value : value;
}
//
watch(
() => app.browser.isMini,
(val: boolean) => {
isExpand.value = !val;
},
{
immediate: true
}
);
return {
isExpand,
toExpand,
checkExpand,
app
};
//
function checkExpand(value?: boolean) {
if (app.browser.isMini) {
toExpand(value);
}
}
//
watch(
() => app.browser.isMini,
(val: boolean) => {
isExpand.value = !val;
},
{
immediate: true
}
);
defineExpose({
checkExpand
});
</script>
@ -88,17 +77,18 @@ export default defineComponent({
height: 100%;
width: 100%;
position: relative;
background-color: var(--el-bg-color);
}
&__left {
height: 100%;
width: 300px;
max-width: calc(100% - 50px);
background-color: #fff;
transition: width 0.3s;
margin-right: 10px;
flex-shrink: 0;
overflow: hidden;
border-right: 1px solid var(--el-border-color);
box-sizing: border-box;
&._collapse {
margin-right: 0;
@ -117,7 +107,6 @@ export default defineComponent({
justify-content: center;
height: 40px;
position: relative;
background-color: #fff;
span {
font-size: 14px;
@ -133,7 +122,6 @@ export default defineComponent({
top: 0;
font-size: 18px;
cursor: pointer;
background-color: #fff;
height: 40px;
width: 80px;
padding-left: 10px;

View File

@ -0,0 +1,82 @@
import { ModuleConfig, config } from "/@/cool";
import { useStore } from "./store";
import "./static/css/index.scss";
export default (): ModuleConfig => {
return {
order: 99,
components: Object.values(import.meta.glob("./components/**/*")),
views: [
{
path: "/my/info",
meta: {
label: "个人中心"
},
component: () => import("./views/info.vue")
}
],
pages: [
{
path: "/login",
component: () => import("./pages/login/index.vue")
},
{
path: "/401",
meta: {
process: false
},
component: () => import("./pages/error-page/401.vue")
},
{
path: "/403",
meta: {
process: false
},
component: () => import("./pages/error-page/403.vue")
},
{
path: "/404",
meta: {
process: false
},
component: () => import("./pages/error-page/404.vue")
},
{
path: "/500",
meta: {
process: false
},
component: () => import("./pages/error-page/500.vue")
},
{
path: "/502",
meta: {
process: false
},
component: () => import("./pages/error-page/502.vue")
}
],
install() {
// 设置标题
document.title = config.app.name;
},
async onLoad() {
const { user, menu } = useStore();
if (user.token) {
// 获取用户信息
user.get();
// 获取菜单权限
await menu.get();
}
return {
async hasToken(cb: () => Promise<any> | void) {
if (user.token) {
if (cb) await cb();
}
}
};
}
};
};

View File

@ -5,7 +5,7 @@ function change(el: any, binding: any) {
}
export default {
beforeMount(el: any, binding: any) {
created(el: any, binding: any) {
el.setAttribute("_display", el.style.display || "");
change(el, binding);
},

View File

@ -1,5 +1,4 @@
import { useStore } from "./store";
import "./static/css/index.scss";
export function useBase() {
return {
@ -8,3 +7,4 @@ export function useBase() {
}
export * from "./common";
export * from "./types/index.d";

View File

@ -7,20 +7,19 @@
@select="select"
>
<el-menu-item v-for="(item, index) in menu.group" :key="index" :index="`${index}`">
<icon-svg v-if="item.icon" :name="item.icon" :size="18" />
<cl-svg v-if="item.icon" :name="item.icon" :size="18" />
<span class="a-menu__name">{{ item.name }}</span>
</el-menu-item>
</el-menu>
</div>
</template>
<script lang="ts" setup>
<script lang="ts" name="a-menu" setup>
import { onMounted, ref } from "vue";
import { useBase } from "/$/base";
import { useCool } from "/@/cool";
const { router, route } = useCool();
const { menu } = useBase();
//
@ -101,7 +100,7 @@ onMounted(function () {
color: #000;
}
.icon-svg {
.cl-svg {
margin-right: 5px;
}
}

View File

@ -0,0 +1,118 @@
import { h, ref, watch } from "vue";
import { useStore } from "../../store";
import { Menu } from "../../types";
import { useCool } from "/@/cool";
export default {
name: "b-menu",
setup() {
const { router, route } = useCool();
const { menu } = useStore();
// 是否可见
const visible = ref(true);
// 页面跳转
function toView(url: string) {
if (url != route.path) {
router.push(url);
}
}
// 刷新菜单
function refresh() {
visible.value = false;
setTimeout(() => {
visible.value = true;
}, 0);
}
// 监听菜单变化
watch(menu.list, refresh);
return {
route,
visible,
toView,
refresh,
menu
};
},
render(ctx: any) {
const { app } = useStore();
// 设置子菜单
function deep(list: Menu.Item[], index: number) {
return list
.filter((e) => e.isShow)
.map((e) => {
let html = null;
if (e.type == 0) {
html = h(
<el-sub-menu></el-sub-menu>,
{
index: String(e.id),
key: e.id,
popperClass: "app-slider__menu"
},
{
title() {
return (
<div class="wrap">
<cl-svg name={e.icon} />
<span v-show={!app.isFold || index != 1}>{e.name}</span>
</div>
);
},
default() {
return deep(e.children || [], index + 1);
}
}
);
} else {
html = h(
<el-menu-item />,
{
index: e.path,
key: e.id
},
{
default() {
return (
<div class="wrap">
<cl-svg name={e.icon} />
<span v-show={!app.isFold || index != 1}>{e.name}</span>
</div>
);
}
}
);
}
return html;
});
}
const children = deep(ctx.menu.list, 1);
return (
ctx.visible && (
<div class="app-slider__menu">
<el-menu
default-active={ctx.route.path}
background-color="transparent"
collapse-transition={false}
collapse={app.browser.isMini ? false : app.isFold}
onSelect={ctx.toView}
>
{children}
</el-menu>
</div>
)
);
}
};

View File

@ -2,7 +2,7 @@
<div class="app-process">
<div class="app-process__back" @click="router.back">
<el-icon :size="15"><arrow-left /></el-icon>
<span>返回</span>
<span>后退</span>
</div>
<div :ref="setRefs('scroller')" class="app-process__scroller">
@ -16,9 +16,9 @@
@click="onTap(item, Number(index))"
@contextmenu.stop.prevent="openCM($event, item)"
>
<span>{{ item.label }}</span>
<el-icon v-if="index > 0" @mousedown.stop="onDel(Number(index))">
<close />
<span>{{ item.meta?.label || item.name || item.path }}</span>
<el-icon @mousedown.stop="onDel(Number(index))">
<Close />
</el-icon>
</div>
</div>
@ -32,17 +32,18 @@ import { useCool } from "/@/cool";
import { ArrowLeft, Close } from "@element-plus/icons-vue";
import { ContextMenu } from "@cool-vue/crud";
import { useBase } from "/$/base";
import { Process } from "/$/base/types";
const { refs, setRefs, route, router } = useCool();
const { process } = useBase();
//
function toPath() {
const active = process.list.find((e: any) => e.active);
const d = process.list.find((e) => e.active);
if (!active) {
if (!d) {
const next = last(process.list);
router.push(next ? next.value : "/");
router.push(next ? next.fullPath : "/");
}
}
@ -64,9 +65,9 @@ function adScroll(index: number) {
}
//
function onTap(item: any, index: number) {
function onTap(item: Process.Item, index: number) {
adScroll(index);
router.push(item.value);
router.push(item.fullPath);
}
//
@ -76,14 +77,14 @@ function onDel(index: number) {
}
//
function openCM(e: any, item: any) {
function openCM(e: any, item: Process.Item) {
ContextMenu.open(e, {
list: [
{
label: "关闭当前",
hidden: item.value !== route.path,
hidden: item.fullPath !== route.path,
callback(done) {
onDel(process.list.findIndex((e: any) => e.value == item.value));
onDel(process.list.findIndex((e) => e.fullPath == item.fullPath));
done();
toPath();
}
@ -91,9 +92,7 @@ function openCM(e: any, item: any) {
{
label: "关闭其他",
callback(done) {
process.set(
process.list.filter((e: any) => e.value == item.value || e.value == "/")
);
process.set(process.list.filter((e) => e.fullPath == item.fullPath));
done();
toPath();
}
@ -101,7 +100,7 @@ function openCM(e: any, item: any) {
{
label: "关闭所有",
callback(done) {
process.set(process.list.filter((e: any) => e.value == "/"));
process.clear();
done();
toPath();
}
@ -113,7 +112,7 @@ function openCM(e: any, item: any) {
watch(
() => route.path,
function (val) {
adScroll(process.list.findIndex((e: any) => e.value === val) || 0);
adScroll(process.list.findIndex((e) => e.fullPath === val) || 0);
}
);
</script>
@ -138,6 +137,7 @@ watch(
margin-right: 10px;
font-size: 12px;
cursor: pointer;
color: #000;
&:hover {
background-color: #eee;

View File

@ -0,0 +1,79 @@
<template>
<div class="route-nav">
<p v-if="app.browser.isMini" class="title">
{{ lastName }}
</p>
<template v-else>
<el-breadcrumb>
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in list" :key="index">{{
item.meta?.label || item.name
}}</el-breadcrumb-item>
</el-breadcrumb>
</template>
</div>
</template>
<script lang="ts" name="route-nav" setup>
import { computed } from "vue";
import _ from "lodash";
import { useCool } from "/@/cool";
import { useBase } from "/$/base";
const { route } = useCool();
const { app, menu } = useBase();
//
const list = computed(() => {
function deep(item: any) {
if (route.path === "/") {
return false;
}
if (item.path == route.path) {
return item;
} else {
if (item.children) {
const ret = item.children.map(deep).find(Boolean);
if (ret) {
return [item, ret];
} else {
return false;
}
} else {
return false;
}
}
}
return _(menu.group).map(deep).filter(Boolean).flattenDeep().value();
});
//
const lastName = computed(() => _.last(list.value).name);
</script>
<style lang="scss" scoped>
.route-nav {
white-space: nowrap;
.el-breadcrumb {
margin: 0 10px;
&__inner {
font-size: 13px;
padding: 0 10px;
font-weight: normal;
letter-spacing: 1px;
}
}
.title {
font-size: 15px;
font-weight: 500;
margin-left: 5px;
}
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<div class="app-slider">
<div class="app-slider__logo" @click="toHome">
<img src="/logo.png" />
<span v-if="!app.isFold || app.browser.isMini">{{ app.info.name }}</span>
</div>
<div class="app-slider__container">
<b-menu />
</div>
</div>
</template>
<script lang="ts" setup>
import { useBase } from "/$/base";
import BMenu from "./bmenu";
const { app } = useBase();
function toHome() {
location.href = "https://cool-js.com";
}
</script>
<style lang="scss">
.app-slider {
height: 100%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
background-color: #2f3447;
&__logo {
display: flex;
align-items: center;
justify-content: center;
height: 80px;
cursor: pointer;
img {
height: 30px;
width: 30px;
}
span {
color: #fff;
font-weight: bold;
font-size: 26px;
margin-left: 10px;
font-family: inherit;
white-space: nowrap;
}
}
&__container {
height: calc(100% - 80px);
overflow-y: auto;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
&__menu {
&.el-popper {
&.is-light {
border: 0;
}
}
.el-menu {
border-right: 0;
background-color: transparent;
&--popup {
.cl-svg,
span {
color: #000;
}
}
.el-sub-menu__title,
&-item {
&.is-active,
&:hover {
background-color: var(--color-primary) !important;
.cl-svg,
span {
color: #fff;
}
}
}
.el-sub-menu__title,
&-item,
&__title {
color: #eee;
letter-spacing: 0.5px;
height: 50px;
line-height: 50px;
.wrap {
width: 100%;
}
.cl-svg {
font-size: 16px;
}
span {
display: inline-block;
font-size: 12px;
letter-spacing: 1px;
margin-left: 10px;
user-select: none;
}
}
&--collapse {
.wrap {
text-align: center;
.cl-svg {
font-size: 18px;
}
}
}
}
}
}
</style>

View File

@ -30,7 +30,7 @@
<!-- 用户信息 -->
<div class="app-topbar__user" v-if="user.info">
<el-dropdown trigger="click" :hide-on-click="false" @command="onCommand">
<el-dropdown trigger="click" hide-on-click @command="onCommand">
<span class="el-dropdown-link">
<span class="name">{{ user.info.nickName }}</span>
<img class="avatar" :src="user.info.headImg" />
@ -53,7 +53,7 @@
</div>
</template>
<script lang="ts" setup>
<script lang="ts" name="topbar" setup>
import { useBase } from "/$/base";
import { useCool } from "/@/cool";
import RouteNav from "./route-nav.vue";
@ -117,7 +117,7 @@ function onCommand(name: string) {
align-items: center;
list-style: none;
height: 45px;
width: 45px;
min-width: 45px;
border-radius: 3px;
cursor: pointer;
margin-left: 10px;

View File

@ -1,5 +1,5 @@
<template>
<div class="app-views" v-if="!app.loading">
<div class="app-views">
<router-view v-slot="{ Component }">
<keep-alive :include="caches">
<component :is="Component" />
@ -12,14 +12,14 @@
import { computed } from "vue";
import { useBase } from "/$/base";
const { app, process } = useBase();
const { process } = useBase();
//
const caches = computed(() => {
return process.list
.filter((e: any) => e.keepAlive)
.map((e: any) => {
return e.value.substring(1, e.value.length).replace(/\//g, "-");
.filter((e) => e.meta?.keepAlive)
.map((e) => {
return e.path.substring(1, e.path.length).replace(/\//g, "-");
});
});
</script>
@ -28,10 +28,10 @@ const caches = computed(() => {
.app-views {
flex: 1;
overflow: hidden;
padding: 0 10px;
margin: 0 10px;
margin-bottom: 10px;
height: 100%;
width: 100%;
width: calc(100% - 20px);
box-sizing: border-box;
border-radius: 3px;

View File

@ -1,12 +1,12 @@
<template>
<div class="page-layout" :class="{ collapse: app.isFold }">
<div class="page-layout__mask" @click="app.fold(true)"></div>
<div class="app-layout" :class="{ collapse: app.isFold }">
<div class="app-layout__mask" @click="app.fold(true)"></div>
<div class="page-layout__left">
<div class="app-layout__left">
<slider />
</div>
<div class="page-layout__right">
<div class="app-layout__right">
<topbar />
<process />
<views />
@ -17,7 +17,7 @@
<script lang="ts" setup>
import Topbar from "./components/topbar.vue";
import Slider from "./components/slider.vue";
import Process from "./components/process.vue";
import process from "./components/process.vue";
import Views from "./components/views.vue";
import { useBase } from "/$/base";
@ -25,7 +25,7 @@ const { app } = useBase();
</script>
<style lang="scss" scoped>
.page-layout {
.app-layout {
display: flex;
background-color: #f7f7f7;
height: 100%;
@ -57,7 +57,7 @@ const { app } = useBase();
}
@media only screen and (max-width: 768px) {
.page-layout__left {
.app-layout__left {
position: absolute;
left: 0;
z-index: 9999;
@ -65,37 +65,37 @@ const { app } = useBase();
box-shadow 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
}
.page-layout__right {
.app-layout__right {
width: 100%;
}
&.collapse {
.page-layout__left {
.app-layout__left {
transform: translateX(-100%);
}
.page-layout__mask {
.app-layout__mask {
display: none;
}
}
}
@media only screen and (min-width: 768px) {
.page-layout__left,
.page-layout__right {
.app-layout__left,
.app-layout__right {
transition: width 0.2s ease-in-out;
}
.page-layout__mask {
.app-layout__mask {
display: none;
}
&.collapse {
.page-layout__left {
.app-layout__left {
width: 64px;
}
.page-layout__right {
.app-layout__right {
width: calc(100% - 64px);
}
}

View File

@ -2,18 +2,6 @@
<error-page :code="401" desc="认证失败,请重新登录!" />
</template>
<script lang="ts">
<script lang="ts" name="401" setup>
import ErrorPage from "./components/error-page.vue";
export default {
cool: {
route: {
path: "/401"
}
},
components: {
ErrorPage
}
};
</script>

View File

@ -2,18 +2,6 @@
<error-page :code="403" desc="您无权访问此页面" />
</template>
<script lang="ts">
<script lang="ts" name="403" setup>
import ErrorPage from "./components/error-page.vue";
export default {
cool: {
route: {
path: "/403"
}
},
components: {
ErrorPage
}
};
</script>

View File

@ -2,18 +2,6 @@
<error-page :code="404" desc="找不到您要查找的页面" />
</template>
<script lang="ts">
<script lang="ts" name="404" setup>
import ErrorPage from "./components/error-page.vue";
export default {
cool: {
route: {
path: "/404"
}
},
components: {
ErrorPage
}
};
</script>

View File

@ -2,18 +2,6 @@
<error-page :code="500" desc="糟糕,出了点问题" />
</template>
<script lang="ts">
<script lang="ts" name="500" setup>
import ErrorPage from "./components/error-page.vue";
export default {
cool: {
route: {
path: "/500"
}
},
components: {
ErrorPage
}
};
</script>

View File

@ -2,18 +2,6 @@
<error-page :code="502" desc="马上回来" />
</template>
<script lang="ts">
<script lang="ts" name="502" setup>
import ErrorPage from "./components/error-page.vue";
export default {
cool: {
route: {
path: "/502"
}
},
components: {
ErrorPage
}
};
</script>

View File

@ -12,7 +12,7 @@
</el-option>
</el-select>
<el-button round @click="navTo">跳转</el-button>
<el-button type="primary" round @click="navTo">跳转</el-button>
</div>
<ul class="link">
@ -27,63 +27,45 @@
<el-button round @click="toLogin">返回登录页</el-button>
</div>
</template>
<p class="copyright">Copyright © cool-admin-next 2023</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
<script lang="ts" setup>
import { ref } from "vue";
import { useCool } from "/@/cool";
import { useBase } from "/$/base";
export default defineComponent({
props: {
code: Number,
desc: String
},
setup() {
const { router } = useCool();
const { user, menu } = useBase();
const url = ref<string>("");
const isLogout = ref<boolean>(false);
function navTo() {
router.push(url.value);
}
function toLogin() {
router.push("/login");
}
async function reLogin() {
isLogout.value = true;
user.logout();
}
function back() {
history.back();
}
function home() {
router.push("/");
}
return {
user,
menu,
url,
isLogout,
navTo,
toLogin,
reLogin,
back,
home
};
}
defineProps({
code: Number,
desc: String
});
const { router } = useCool();
const { user, menu } = useBase();
const url = ref<string>("");
const isLogout = ref<boolean>(false);
function navTo() {
router.push(url.value);
}
function toLogin() {
router.push("/login");
}
async function reLogin() {
isLogout.value = true;
user.logout();
}
function back() {
history.back();
}
function home() {
router.push("/");
}
</script>
<style lang="scss" scoped>
@ -123,13 +105,7 @@ export default defineComponent({
.el-button {
margin-left: 15px;
background-color: var(--color-primary);
border-color: var(--color-primary);
color: #fff;
padding: 0 30px;
letter-spacing: 1px;
height: 36px;
line-height: 36px;
}
}

View File

@ -1,105 +0,0 @@
<template>
<div class="route-nav">
<p v-if="app.browser.isMini" class="title">
{{ lastName }}
</p>
<template v-else>
<el-breadcrumb>
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in list" :key="index">{{
(item.meta && item.meta.label) || item.name
}}</el-breadcrumb-item>
</el-breadcrumb>
</template>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from "vue";
import _ from "lodash";
import { useCool } from "/@/cool";
import { useBase } from "/$/base";
export default defineComponent({
name: "route-nav",
setup() {
const { route } = useCool();
const { app, menu } = useBase();
//
const list = ref<any[]>([]);
//
watch(
() => route,
(val: any) => {
function deep(item: any) {
if (route.path === "/") {
return false;
}
if (item.path == route.path) {
return item;
} else {
if (item.children) {
const ret = item.children.map(deep).find(Boolean);
if (ret) {
return [item, ret];
} else {
return false;
}
} else {
return false;
}
}
}
list.value = _(menu.group).map(deep).filter(Boolean).flattenDeep().value();
if (_.isEmpty(list.value)) {
list.value.push(val);
}
},
{
immediate: true,
deep: true
}
);
//
const lastName = computed(() => _.last(list.value).name);
return {
list,
lastName,
app
};
}
});
</script>
<style lang="scss">
.route-nav {
white-space: nowrap;
.el-breadcrumb {
margin: 0 10px;
&__inner {
font-size: 13px;
padding: 0 10px;
font-weight: normal;
letter-spacing: 1px;
}
}
.title {
font-size: 15px;
font-weight: 500;
margin-left: 5px;
}
}
</style>

View File

@ -1,259 +0,0 @@
<template>
<div class="app-slider">
<div class="app-slider__logo" @click="toHome">
<img :src="Logo" />
<span v-if="!app.isFold || app.browser.isMini">{{ app.info.name }}</span>
</div>
<div class="app-slider__container">
<menu-nav />
</div>
</div>
</template>
<script lang="tsx">
import { defineComponent, ref, watch, h } from "vue";
import { useBase } from "/$/base";
import { useCool } from "/@/cool";
import Logo from "/@/assets/logo.png";
export default defineComponent({
name: "app-slider",
components: {
MenuNav: {
setup() {
const { router, route } = useCool();
const { menu } = useBase();
//
const visible = ref<boolean>(true);
//
function toView(url: string) {
if (url != route.path) {
router.push(url);
}
}
//
function refresh() {
visible.value = false;
setTimeout(() => {
visible.value = true;
}, 0);
}
//
watch(menu.list, refresh);
return {
route,
visible,
toView,
refresh,
menu
};
},
render(ctx: any) {
const { app } = useBase();
function deepMenu(list: any[], index: number) {
return list
.filter((e: any) => e.isShow)
.map((e: any) => {
let html = null;
if (e.type == 0) {
html = h(
<el-sub-menu></el-sub-menu>,
{
index: String(e.id),
key: e.id,
popperClass: "app-slider__menu"
},
{
title() {
return (
<div class="wrap">
<icon-svg name={e.icon}></icon-svg>
<span v-show={!app.isFold || index != 1}>
{e.name}
</span>
</div>
);
},
default() {
return deepMenu(e.children, index + 1);
}
}
);
} else {
html = h(
<el-menu-item></el-menu-item>,
{
index: e.path,
key: e.id
},
{
default() {
return (
<div class="wrap">
<icon-svg name={e.icon}></icon-svg>
<span v-show={!app.isFold || index != 1}>
{e.name}
</span>
</div>
);
}
}
);
}
return html;
});
}
const children = deepMenu(ctx.menu.list, 1);
return (
ctx.visible && (
<div class="app-slider__menu">
<el-menu
default-active={ctx.route.path}
background-color="transparent"
collapse-transition={false}
collapse={app.browser.isMini ? false : app.isFold}
onSelect={ctx.toView}
>
{children}
</el-menu>
</div>
)
);
}
}
},
setup() {
function toHome() {
location.href = "https://cool-js.com";
}
return {
toHome,
Logo,
...useBase()
};
}
});
</script>
<style lang="scss">
.app-slider {
height: 100%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
background-color: #2f3447;
&__logo {
display: flex;
align-items: center;
justify-content: center;
height: 80px;
cursor: pointer;
img {
height: 30px;
width: 30px;
}
span {
color: #fff;
font-weight: bold;
font-size: 26px;
margin-left: 10px;
font-family: inherit;
white-space: nowrap;
}
}
&__container {
height: calc(100% - 80px);
overflow-y: auto;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
&__menu {
&.el-popper {
&.is-light {
border: 0;
}
}
.el-menu {
border-right: 0;
background-color: transparent;
&--popup {
.icon-svg,
span {
color: #000;
}
}
.el-sub-menu__title,
&-item {
&.is-active,
&:hover {
background-color: var(--color-primary) !important;
.icon-svg,
span {
color: #fff;
}
}
}
.el-sub-menu__title,
&-item,
&__title {
color: #eee;
letter-spacing: 0.5px;
height: 50px;
line-height: 50px;
.wrap {
width: 100%;
}
.icon-svg {
font-size: 16px;
}
span {
display: inline-block;
font-size: 12px;
letter-spacing: 1px;
margin-left: 10px;
}
}
&--collapse {
.wrap {
text-align: center;
.icon-svg {
font-size: 18px;
}
}
}
}
}
}
</style>

View File

@ -5,58 +5,52 @@
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { ElMessage } from "element-plus";
import { useCool } from "/@/cool";
export default defineComponent({
emits: ["update:modelValue", "change"],
const emit = defineEmits(["update:modelValue", "change"]);
setup(_, { emit }) {
const { service } = useCool();
const { service } = useCool();
// base64
const base64 = ref<string>("");
// base64
const base64 = ref<string>("");
// svg
const svg = ref<string>("");
// svg
const svg = ref<string>("");
function refresh() {
service.base.open
.captcha({
height: 40,
width: 150
})
.then(({ captchaId, data }: any) => {
if (data.includes(";base64,")) {
base64.value = data;
} else {
svg.value = data;
}
function refresh() {
service.base.open
.captcha({
height: 40,
width: 150
})
.then(({ captchaId, data }: any) => {
if (data.includes(";base64,")) {
base64.value = data;
} else {
svg.value = data;
}
emit("update:modelValue", captchaId);
emit("change", {
base64,
svg,
captchaId
});
})
.catch((err) => {
ElMessage.error(err.message);
});
}
onMounted(() => {
refresh();
emit("update:modelValue", captchaId);
emit("change", {
base64,
svg,
captchaId
});
})
.catch((err) => {
ElMessage.error(err.message);
});
}
return {
base64,
svg,
refresh
};
}
onMounted(() => {
refresh();
});
defineExpose({
refresh
});
</script>

View File

@ -1,7 +1,10 @@
<template>
<div class="page-login">
<div class="box">
<img class="logo" :src="Logo" alt="Logo" />
<div class="logo">
<img src="/logo.png" alt="Logo" />
<span>{{ app.info.name }}</span>
</div>
<p class="desc">一款快速开发后台权限管理系统</p>
<el-form label-position="top" class="form" :disabled="saving" size="large">
@ -54,96 +57,73 @@
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
<script lang="ts" name="login" setup>
import { reactive, ref } from "vue";
import { ElMessage } from "element-plus";
import { useCool } from "/@/cool";
import { useBase } from "/$/base";
import Captcha from "./components/captcha.vue";
import Logo from "/@/assets/logo-text.png";
export default defineComponent({
cool: {
route: {
path: "/login"
}
},
const { refs, setRefs, router, service } = useCool();
const { user, menu, app } = useBase();
components: {
Captcha
},
// 1
const saving = ref(false);
setup() {
const { refs, setRefs, router, service } = useCool();
const { user, menu } = useBase();
//
const form = reactive({
username: "",
password: "",
captchaId: "",
verifyCode: ""
});
// 1
const saving = ref(false);
//
async function toLogin() {
if (!form.username) {
return ElMessage.error("用户名不能为空");
}
//
const form = reactive({
username: "",
password: "",
captchaId: "",
verifyCode: ""
if (!form.password) {
return ElMessage.error("密码不能为空");
}
if (!form.verifyCode) {
return ElMessage.error("图片验证码不能为空");
}
saving.value = true;
try {
//
await service.base.open.login(form).then((res) => {
user.setToken(res);
});
//
async function toLogin() {
if (!form.username) {
return ElMessage.error("用户名不能为空");
}
//
user.get();
if (!form.password) {
return ElMessage.error("密码不能为空");
}
//
await menu.get();
if (!form.verifyCode) {
return ElMessage.error("图片验证码不能为空");
}
saving.value = true;
try {
//
await service.base.open.login(form).then((res) => {
user.setToken(res);
});
//
await user.get();
//
await menu.get();
//
menu.getPath();
router.push("/");
} catch (err: any) {
refs.value.captcha.refresh();
ElMessage.error(err.message);
}
saving.value = false;
}
return {
refs,
setRefs,
form,
saving,
toLogin,
Logo
};
//
router.push("/");
} catch (err: any) {
refs.value.captcha.refresh();
ElMessage.error(err.message);
}
});
saving.value = false;
}
</script>
<style lang="scss" scoped>
.page-login {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
position: relative;
background-color: #2f3447;
@ -152,15 +132,24 @@ export default defineComponent({
flex-direction: column;
justify-content: center;
align-items: center;
height: 500px;
width: 500px;
position: absolute;
left: calc(50% - 250px);
top: calc(50% - 250px);
.logo {
height: 50px;
margin-bottom: 30px;
display: flex;
align-items: center;
color: #fff;
img {
height: 50px;
}
span {
font-size: 38px;
margin-left: 10px;
letter-spacing: 5px;
font-weight: bold;
}
}
.desc {

View File

@ -1 +1,46 @@
* {
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;
}
}
@import "./theme.scss";

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

View File

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 763 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 753 B

After

Width:  |  Height:  |  Size: 753 B

View File

Before

Width:  |  Height:  |  Size: 965 B

After

Width:  |  Height:  |  Size: 965 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

View File

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

Some files were not shown because too many files have changed in this diff Show More