发布5.7.0

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -1,7 +1,7 @@
import { createWriteStream } from "fs"; import { createWriteStream } from "fs";
import prettier from "prettier"; import prettier from "prettier";
import { join } from "path"; import { join } from "path";
import { createDir } from "../../utils"; import { mkdirs } from "../../utils";
import rules from "./rules"; import rules from "./rules";
import { isFunction, isRegExp, isString } from "lodash"; import { isFunction, isRegExp, isString } from "lodash";
@ -108,7 +108,7 @@ const handler = {
function createComponent(item: any) { function createComponent(item: any) {
const { propertyName: prop, comment: label } = item; const { propertyName: prop, comment: label } = item;
let d = null; let d: any = null;
rules.forEach((r: any) => { rules.forEach((r: any) => {
const s = r.test.find((e: any) => { const s = r.test.find((e: any) => {
@ -172,7 +172,7 @@ function getPageName(router: string) {
router = router.substr(1, router.length); 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 = { const upsert: any = {
items: [] items: []
}; };
@ -310,13 +310,11 @@ export async function createMenu({ router, columns, prefix, api, module, filenam
</cl-crud> </cl-crud>
</template> </template>
<script lang="ts" setup> <script lang="ts" name="${getPageName(router)}" setup>
import { useCrud, useTable, useUpsert } from "@cool-vue/crud"; import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
const { service, named } = useCool(); const { service } = useCool();
named("${getPageName(router)}");
// cl-upsert 配置 // cl-upsert 配置
const Upsert = useUpsert(${JSON.stringify(upsert)}); const Upsert = useUpsert(${JSON.stringify(upsert)});
@ -335,6 +333,7 @@ const Crud = useCrud(
); );
</script>`; </script>`;
// 文件内容
const content = prettier.format(temp, { const content = prettier.format(temp, {
parser: "vue", parser: "vue",
useTabs: true, useTabs: true,
@ -347,14 +346,17 @@ const Crud = useCrud(
trailingComma: "none" 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" flags: "w"
}).write(content); }).write(content);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

9274
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "front-next", "name": "front-next",
"version": "5.6.2", "version": "5.7.0",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vite build", "build": "vite build",
@ -9,63 +9,60 @@
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix" "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix"
}, },
"dependencies": { "dependencies": {
"@cool-vue/crud": "^5.2.10", "@codemirror/lang-javascript": "^6.0.1",
"@element-plus/icons-vue": "^1.1.3", "@codemirror/theme-one-dark": "^6.0.0",
"@vueuse/core": "^8.2.5", "@cool-vue/crud": "^5.3.0",
"@element-plus/icons-vue": "^2.0.6",
"@vueuse/core": "^8.9.4",
"axios": "^0.27.2", "axios": "^0.27.2",
"codemirror": "^5.62.0", "codemirror": "^6.0.1",
"core-js": "^3.6.5", "core-js": "^3.23.5",
"echarts": "^5.0.2", "echarts": "^5.3.3",
"element-plus": "^2.2.5", "element-plus": "^2.2.9",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"js-beautify": "^1.13.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.12", "pinia": "^2.0.16",
"quill": "^1.3.7", "quill": "^1.3.7",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"store": "^2.0.12", "store": "^2.0.12",
"unocss": "^0.31.0", "unocss": "^0.44.3",
"vue": "^3.2.32", "vue": "^3.2.37",
"vue-echarts": "^6.0.2", "vue-codemirror": "^6.0.0",
"vue-router": "^4.0.14", "vue-echarts": "^6.2.3",
"vue-router": "^4.1.2",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"xlsx": "^0.16.9" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.182",
"@types/node": "^16.10.2", "@types/mockjs": "^1.0.6",
"@types/node": "^18.0.6",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/quill": "^2.0.9", "@types/quill": "^2.0.9",
"@types/store": "^2.0.2", "@types/store": "^2.0.2",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^4.20.0", "@typescript-eslint/parser": "^5.30.6",
"@unocss/preset-uno": "^0.31.0", "@unocss/preset-uno": "^0.44.3",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^3.0.1",
"@vitejs/plugin-vue-jsx": "^1.3.9", "@vitejs/plugin-vue-jsx": "^2.0.0",
"@vue/cli-plugin-babel": "^5.0.1", "@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.1", "@vue/cli-plugin-typescript": "^5.0.8",
"@vue/compiler-sfc": "^3.2.31", "@vue/compiler-sfc": "^3.2.37",
"@vue/composition-api": "^1.4.9", "@vue/composition-api": "^1.7.0",
"eslint": "^7.23.0", "eslint": "^8.20.0",
"eslint-config-prettier": "^8.1.0", "eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^7.13.0", "eslint-plugin-vue": "^9.2.0",
"iconv-lite": "^0.6.3", "magic-string": "^0.26.2",
"prettier": "^2.4.1", "prettier": "^2.7.1",
"sass": "^1.49.9", "sass": "^1.53.0",
"sass-loader": "^11.1.1", "sass-loader": "^13.0.2",
"svg-sprite-loader": "^6.0.2", "typescript": "^4.7.4",
"typescript": "^4.6.2", "vite": "^3.0.2",
"unplugin-vue-components": "^0.17.21", "vite-plugin-compression": "^0.5.1"
"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"
} }
} }

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,18 +1,5 @@
<template> <template>
<el-config-provider :locale="zhCn"> <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 /> <router-view />
</el-config-provider> </el-config-provider>
</template> </template>
@ -20,9 +7,4 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ElConfigProvider } from "element-plus"; import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn"; import zhCn from "element-plus/lib/locale/lang/zh-cn";
import { useBase } from "/$/base";
const { app } = useBase();
</script> </script>
<style lang="scss" src="./assets/css/index.scss"></style>

View File

@ -1,44 +0,0 @@
* {
padding: 0;
margin: 0;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
"微软雅黑", Arial, sans-serif;
}
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.3);
}
*::-webkit-scrollbar-track {
background: transparent;
}
#app {
height: 100vh;
width: 100vw;
overflow: hidden;
}
:root {
--view-bg-color: #f7f7f7;
}
a {
text-decoration: none;
}
input,
button {
outline: none;
}
input {
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px white inset;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

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

View File

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

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 { useRoute, useRouter } from "vue-router";
import { useService } from "../service"; import { useService } from "../service";
const service = useService();
export function useRefs() { export function useRefs() {
const refs: any = ref<any[]>([]); const refs: any = ref<any[]>([]);
@ -16,34 +19,12 @@ export function useRefs() {
return { refs, setRefs }; return { refs, setRefs };
} }
// 服务
const service = useService();
// 组件命名
function named(name: string) {
const { proxy }: any = getCurrentInstance();
proxy.$.type.name = name;
}
export function useCool() { export function useCool() {
const { refs, setRefs } = useRefs();
// 通信
const mitt = inject<any>("mitt");
// 路由
const route = useRoute();
// 路由器
const router = useRouter();
return { return {
route,
router,
refs,
setRefs,
service, service,
mitt, route: useRoute(),
named router: useRouter(),
mitt: inject("mitt") as Emitter<any>,
...useRefs()
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,35 @@
import { deepFiles, deepMerge, module } from "../utils"; import { deepMerge, basename } from "../utils";
import { BaseService } from "./base"; import { BaseService } from "./base";
import { useEps } from "./eps"; 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 request: new BaseService().request
}; };
@ -13,7 +39,7 @@ export function useService() {
// 模块内容 // 模块内容
module.list.forEach((e) => { module.list.forEach((e) => {
deepMerge(service, deepFiles(e.service || [])); deepMerge(service, deepFiles(e.services || []));
}); });
return service; return service;

View File

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

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

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

View File

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

View File

@ -1,31 +0,0 @@
// @ts-nocheck
interface Item {
name: string;
options: {
[key: string]: any;
};
value: any;
service?: any[];
pages?: any[];
views?: any[];
components?: {
[key: string]: any;
};
}
const module = {
get list(): Item[] {
return window.__modules__ || [];
},
set(list: Item[]) {
window.__modules__ = list;
},
get(name: string) {
return name ? window.__modules__.find((e) => e.name == name) : window.__modules__;
}
};
export default module;

3
src/env.d.ts vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,50 +4,34 @@
</svg> </svg>
</template> </template>
<script lang="ts"> <script lang="ts" name="cl-svg" setup>
import { computed, defineComponent, ref } from "vue"; import { computed, ref } from "vue";
import { isNumber } from "lodash"; import { isNumber } from "lodash";
export default defineComponent({ const props = defineProps({
name: "icon-svg", name: {
type: String
cool: {
global: true
}, },
className: {
props: { type: String
name: {
type: String
},
className: {
type: String
},
size: {
type: [String, Number]
}
}, },
size: {
setup(props) { type: [String, Number]
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
};
} }
}); });
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> </script>
<style scoped> <style lang="scss" scoped>
.icon-svg { .cl-svg {
width: 1em; width: 1em;
height: 1em; height: 1em;
vertical-align: -0.15em; vertical-align: -0.15em;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@
</el-option> </el-option>
</el-select> </el-select>
<el-button round @click="navTo">跳转</el-button> <el-button type="primary" round @click="navTo">跳转</el-button>
</div> </div>
<ul class="link"> <ul class="link">
@ -27,63 +27,45 @@
<el-button round @click="toLogin">返回登录页</el-button> <el-button round @click="toLogin">返回登录页</el-button>
</div> </div>
</template> </template>
<p class="copyright">Copyright © cool-admin-next 2023</p>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, ref } from "vue"; import { ref } from "vue";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { useBase } from "/$/base"; import { useBase } from "/$/base";
export default defineComponent({ defineProps({
props: { code: Number,
code: Number, desc: String
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
};
}
}); });
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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -123,13 +105,7 @@ export default defineComponent({
.el-button { .el-button {
margin-left: 15px; margin-left: 15px;
background-color: var(--color-primary);
border-color: var(--color-primary);
color: #fff;
padding: 0 30px; padding: 0 30px;
letter-spacing: 1px;
height: 36px;
line-height: 36px;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1,46 @@
* {
padding: 0;
margin: 0;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
"微软雅黑", Arial, sans-serif;
}
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.3);
}
*::-webkit-scrollbar-track {
background: transparent;
}
#app {
height: 100vh;
width: 100vw;
overflow: hidden;
}
:root {
--view-bg-color: #f7f7f7;
}
a {
text-decoration: none;
}
input,
button {
outline: none;
}
input {
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px white inset;
}
}
@import "./theme.scss"; @import "./theme.scss";

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

View File

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 763 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 753 B

After

Width:  |  Height:  |  Size: 753 B

View File

Before

Width:  |  Height:  |  Size: 965 B

After

Width:  |  Height:  |  Size: 965 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

View File

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

View File

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 826 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 800 B

View File

Before

Width:  |  Height:  |  Size: 984 B

After

Width:  |  Height:  |  Size: 984 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 910 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

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