发布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/max-attributes-per-line": "off",
"vue/multiline-html-element-content-newline": "off",
"vue/multi-word-component-names": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/attribute-hyphenation": "off",
"vue/html-self-closing": "off",

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

9274
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

3
src/env.d.ts vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 853 B

After

Width:  |  Height:  |  Size: 853 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

View File

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 763 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 753 B

After

Width:  |  Height:  |  Size: 753 B

View File

Before

Width:  |  Height:  |  Size: 965 B

After

Width:  |  Height:  |  Size: 965 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

View File

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

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