发布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
+5 -5
View File
@@ -1,5 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules
.DS_Store
dist
dist-ssr
*.local
+11 -11
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
+1
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",
+3 -3
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
+5 -5
View File
@@ -1,5 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
node_modules
.DS_Store
dist
dist-ssr
*.local
+8 -8
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
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"
}
}
+3 -4
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({",
+2 -1
View File
@@ -1,3 +1,4 @@
{
"editor.cursorSmoothCaretAnimation": true
"editor.cursorSmoothCaretAnimation": true,
"editor.formatOnSave": true,
}
+15 -15
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
+21 -21
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.
+12 -8
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 => {
};
}
};
};
}
+1 -6
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"]
},
{
+115 -133
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
View File
@@ -0,0 +1,5 @@
export * from "./eps";
export * from "./menu";
export * from "./module";
export * from "./svg";
export * from "./tag";
+14 -12
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);
}
+12
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);
}
});
}
-11
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);
}
}
+54
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>`
);
}
+32
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;
}
-507
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
View File
File diff suppressed because it is too large Load Diff
-952
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;
};
+22
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;
}
-106
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;
};
+12 -12
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>
+122 -122
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
View File
File diff suppressed because it is too large Load Diff
+39 -42
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"
}
}

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

-18
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>
-44
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

+12 -32
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");
}
+2 -4
View File
@@ -22,10 +22,8 @@ export const config = {
router: {
// 模式
mode: "history",
// 页面
pages: [],
// 视图 / 路由下的 children
views: []
// 首页组件
home: import("/$/demo/views/home/index.vue")
},
// 字体图标库
+8 -27
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()
};
}
+3 -1
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";
+97 -138
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 };
+135 -90
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 };
+5 -13
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",
+1 -1
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) {
+29 -3
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;
+15 -11
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
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;
}
+39 -35
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 };
-31
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;
+1 -2
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;
-3
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);
// 启动
-3
View File
@@ -1,3 +0,0 @@
// @ts-nocheck
const xhr = new window._XMLHttpRequest();
window.XMLHttpRequest.prototype.upload = xhr.upload;
+5 -1
View File
@@ -1,4 +1,8 @@
import "./resize";
import { resize } from "./resize";
window.onload = function () {
resize();
};
export * from "./theme";
export * from "./permission";
+2 -14
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]);
}
+5 -5
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();
}
+3 -1
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[] = [];
+70 -126
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>
+20 -36
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;
+1 -4
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"
+34 -46
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;
+82
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();
}
}
};
}
};
};
+1 -1
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);
},
+1 -1
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";
@@ -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;
}
}
@@ -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>
)
);
}
};
@@ -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;
@@ -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>
@@ -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>
@@ -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;
@@ -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;
@@ -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);
}
}
+1 -13
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>
+1 -13
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>
+1 -13
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>
+1 -13
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>
+1 -13
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>
@@ -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;
}
}
@@ -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>
@@ -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>
@@ -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>
+67 -78
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 {
+45
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";

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 763 B

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 753 B

After

Width:  |  Height:  |  Size: 753 B

Before

Width:  |  Height:  |  Size: 965 B

After

Width:  |  Height:  |  Size: 965 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

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