test 1
16
.eslintrc.js
@ -58,19 +58,7 @@ module.exports = {
|
|||||||
"vue/multiline-html-element-content-newline": "off",
|
"vue/multiline-html-element-content-newline": "off",
|
||||||
"vue/singleline-html-element-content-newline": "off",
|
"vue/singleline-html-element-content-newline": "off",
|
||||||
"vue/attribute-hyphenation": "off",
|
"vue/attribute-hyphenation": "off",
|
||||||
// "vue/html-self-closing": "off",
|
"vue/html-self-closing": "off",
|
||||||
"vue/require-default-prop": "off",
|
"vue/require-default-prop": "off"
|
||||||
"vue/html-self-closing": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
html: {
|
|
||||||
void: "always",
|
|
||||||
normal: "never",
|
|
||||||
component: "always"
|
|
||||||
},
|
|
||||||
svg: "always",
|
|
||||||
math: "always"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
62
.vscode/crud.code-snippets
vendored
@ -3,8 +3,8 @@
|
|||||||
"prefix": "cl-crud-ts",
|
"prefix": "cl-crud-ts",
|
||||||
"body": [
|
"body": [
|
||||||
"<template>",
|
"<template>",
|
||||||
" <cl-crud :ref=\"setRefs('crud')\" @load=\"onLoad\">",
|
" <cl-crud ref=\"Crud\">",
|
||||||
" <el-row type=\"flex\" align=\"middle\">",
|
" <el-row>",
|
||||||
" <!-- 刷新按钮 -->",
|
" <!-- 刷新按钮 -->",
|
||||||
" <cl-refresh-btn />",
|
" <cl-refresh-btn />",
|
||||||
" <!-- 新增按钮 -->",
|
" <!-- 新增按钮 -->",
|
||||||
@ -18,55 +18,47 @@
|
|||||||
"",
|
"",
|
||||||
" <el-row>",
|
" <el-row>",
|
||||||
" <!-- 数据表格 -->",
|
" <!-- 数据表格 -->",
|
||||||
" <cl-table :ref=\"setRefs('table')\" v-bind=\"table\" />",
|
" <cl-table ref=\"Table\" />",
|
||||||
" </el-row>",
|
" </el-row>",
|
||||||
"",
|
"",
|
||||||
" <el-row type=\"flex\">",
|
" <el-row>",
|
||||||
" <cl-flex1 />",
|
" <cl-flex1 />",
|
||||||
" <!-- 分页控件 -->",
|
" <!-- 分页控件 -->",
|
||||||
" <cl-pagination />",
|
" <cl-pagination />",
|
||||||
" </el-row>",
|
" </el-row>",
|
||||||
"",
|
"",
|
||||||
" <!-- 新增、编辑 -->",
|
" <!-- 新增、编辑 -->",
|
||||||
" <cl-upsert :ref=\"setRefs('upsert')\" v-bind=\"upsert\" />",
|
" <cl-upsert ref=\"Upsert\" />",
|
||||||
" </cl-crud>",
|
" </cl-crud>",
|
||||||
"</template>",
|
"</template>",
|
||||||
"",
|
"",
|
||||||
"<script lang=\"ts\">",
|
"<script lang=\"ts\" setup>",
|
||||||
"import { defineComponent, reactive } from \"vue\";",
|
"import { useCrud, useTable, useUpsert } from \"@cool-vue/crud\";",
|
||||||
"import { CrudLoad, Upsert, Table } from \"@cool-vue/crud/types\";",
|
|
||||||
"import { useCool } from \"/@/cool\";",
|
"import { useCool } from \"/@/cool\";",
|
||||||
"",
|
"",
|
||||||
"export default defineComponent({",
|
"const { service, named } = useCool();",
|
||||||
" setup() {",
|
|
||||||
" const { refs, setRefs, service } = useCool();",
|
|
||||||
"",
|
"",
|
||||||
" // 新增、编辑配置",
|
"named(\"菜单名称\");",
|
||||||
" const upsert = reactive<Upsert>({",
|
|
||||||
" items: []",
|
|
||||||
" });",
|
|
||||||
"",
|
"",
|
||||||
" // 表格配置",
|
"// cl-upsert 配置",
|
||||||
" const table = reactive<Table>({",
|
"const Upsert = useUpsert({",
|
||||||
" columns: []",
|
" items: []",
|
||||||
" });",
|
|
||||||
"",
|
|
||||||
" // crud 加载",
|
|
||||||
" function onLoad({ ctx, app }: CrudLoad) {",
|
|
||||||
" // 绑定 service",
|
|
||||||
" ctx.service(service.xx).done();",
|
|
||||||
" app.refresh();",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return {",
|
|
||||||
" refs,",
|
|
||||||
" setRefs,",
|
|
||||||
" upsert,",
|
|
||||||
" table,",
|
|
||||||
" onLoad",
|
|
||||||
" };",
|
|
||||||
" }",
|
|
||||||
"});",
|
"});",
|
||||||
|
"",
|
||||||
|
"// cl-table 配置",
|
||||||
|
"const Table = useTable({",
|
||||||
|
" columns: []",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
"// cl-crud 配置",
|
||||||
|
"const Crud = useCrud(",
|
||||||
|
" {",
|
||||||
|
" service: service.demo.goods",
|
||||||
|
" },",
|
||||||
|
" (app) => {",
|
||||||
|
" app.refresh();",
|
||||||
|
" }",
|
||||||
|
");",
|
||||||
"</script>",
|
"</script>",
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
|
|||||||
65
build/cool/index.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Plugin } from "vite";
|
||||||
|
import { parseJson } from "./utils";
|
||||||
|
import { getModules } from "./lib/modules";
|
||||||
|
import { createEps, getEps } from "./lib/eps";
|
||||||
|
import { createMenu } from "./lib/menu";
|
||||||
|
|
||||||
|
export const cool = (): Plugin | null => {
|
||||||
|
return {
|
||||||
|
name: "vite-cool",
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use(async (req, res, next) => {
|
||||||
|
function done(data: any) {
|
||||||
|
res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
|
||||||
|
res.end(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义
|
||||||
|
if (req.url.includes("__cool")) {
|
||||||
|
const body = await parseJson(req);
|
||||||
|
let next: any = null;
|
||||||
|
|
||||||
|
switch (req.url) {
|
||||||
|
// 快速创建菜单
|
||||||
|
case "/__cool_createMenu":
|
||||||
|
next = createMenu(body);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 获取模块列表
|
||||||
|
case "/__cool_modules":
|
||||||
|
next = getModules();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 创建描述文件
|
||||||
|
case "/__cool_eps":
|
||||||
|
next = createEps(body);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
next.then((data: any) => {
|
||||||
|
done({
|
||||||
|
code: 1000,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}).catch((message: string) => {
|
||||||
|
done({
|
||||||
|
code: 1001,
|
||||||
|
message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
config() {
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__EPS__: getEps()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
219
build/cool/lib/eps/index.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import prettier from "prettier";
|
||||||
|
import { isEmpty, last } from "lodash";
|
||||||
|
import { createDir, firstUpperCase, readFile, toCamel } from "../../utils";
|
||||||
|
import { createWriteStream } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
// 临时目录路径
|
||||||
|
const tempPath = join(__dirname, "../../temp");
|
||||||
|
|
||||||
|
// 创建描述文件
|
||||||
|
export async function createEps({ list, service }: any) {
|
||||||
|
const t0 = [
|
||||||
|
[
|
||||||
|
`
|
||||||
|
declare interface Crud {
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
* @returns Promise<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;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const t1 = [`declare type Service = {`, `request(data: RequestOptions): Promise<any>;`];
|
||||||
|
|
||||||
|
// 处理数据
|
||||||
|
function deep(d: any, k?: string) {
|
||||||
|
if (!k) k = "";
|
||||||
|
|
||||||
|
for (const i in d) {
|
||||||
|
const name = k + toCamel(firstUpperCase(i.replace(/[:]/g, "")));
|
||||||
|
|
||||||
|
if (d[i].namespace) {
|
||||||
|
// 查找配置
|
||||||
|
const item = list.find((e: any) => (e.prefix || "").includes(d[i].namespace));
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
const t = [
|
||||||
|
`declare interface ${name} ${item.extendCrud ? " extends Crud" : ""} {`
|
||||||
|
];
|
||||||
|
|
||||||
|
t1.push(`${i}: ${name};`);
|
||||||
|
|
||||||
|
// 插入方法
|
||||||
|
if (item.api) {
|
||||||
|
// 权限列表
|
||||||
|
const permission: string[] = [];
|
||||||
|
|
||||||
|
item.api.forEach((a: any) => {
|
||||||
|
// 方法名
|
||||||
|
const n = toCamel(a.name || last(a.path.split("/"))).replace(
|
||||||
|
/[:\/-]/g,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
if (n) {
|
||||||
|
// 参数类型
|
||||||
|
let q: string[] = [];
|
||||||
|
|
||||||
|
// 参数列表
|
||||||
|
const { parameters = [] } = a.dts || {};
|
||||||
|
|
||||||
|
parameters.forEach((p: any) => {
|
||||||
|
if (p.description) {
|
||||||
|
q.push(`\n/** ${p.description} */\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.name.includes(":")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = `${p.name}${p.required ? "" : "?"}`;
|
||||||
|
const b = `${p.schema.type || "string"}`;
|
||||||
|
|
||||||
|
q.push(`${a}: ${b},`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isEmpty(q)) {
|
||||||
|
q = ["any"];
|
||||||
|
} else {
|
||||||
|
q.unshift("{");
|
||||||
|
q.push("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回类型
|
||||||
|
let res = "";
|
||||||
|
|
||||||
|
switch (a.path) {
|
||||||
|
case "/page":
|
||||||
|
res = "PageResponse";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res = "any";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 描述
|
||||||
|
t.push("\n");
|
||||||
|
t.push("/**\n");
|
||||||
|
t.push(` * ${a.summary || n}\n`);
|
||||||
|
t.push(` * @returns Promise<${res}>\n`);
|
||||||
|
t.push(" */\n");
|
||||||
|
|
||||||
|
t.push(
|
||||||
|
`${n}(data${q.length == 1 ? "?" : ""}: ${q.join(
|
||||||
|
""
|
||||||
|
)}): Promise<${res}>;`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
permission.push(`${n}: string;`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加权限
|
||||||
|
t.push("\n");
|
||||||
|
t.push("/**\n");
|
||||||
|
t.push(" * 权限\n");
|
||||||
|
t.push(" */\n");
|
||||||
|
t.push(`permission: { ${permission.join("\n")} }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
t.push("}");
|
||||||
|
t0.push(t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t1.push(`${i}: {`);
|
||||||
|
deep(d[i], name);
|
||||||
|
t1.push(`},`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deep(service);
|
||||||
|
t1.push("}");
|
||||||
|
|
||||||
|
// 追加
|
||||||
|
t0.push(t1);
|
||||||
|
|
||||||
|
// 文本内容
|
||||||
|
const content = prettier.format(t0.map((e) => e.join("")).join("\n\n"), {
|
||||||
|
parser: "typescript",
|
||||||
|
useTabs: true,
|
||||||
|
tabWidth: 4,
|
||||||
|
endOfLine: "lf",
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
printWidth: 100,
|
||||||
|
trailingComma: "none"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建 temp 目录
|
||||||
|
createDir(tempPath);
|
||||||
|
|
||||||
|
// 创建 service 描述文件
|
||||||
|
createWriteStream(join(tempPath, "service.d.ts"), {
|
||||||
|
flags: "w"
|
||||||
|
}).write(content);
|
||||||
|
|
||||||
|
// 创建 eps 文件
|
||||||
|
createWriteStream(join(tempPath, "eps.json"), {
|
||||||
|
flags: "w"
|
||||||
|
}).write(
|
||||||
|
JSON.stringify(
|
||||||
|
list.map((e: any) => {
|
||||||
|
return [e.prefix, e.api.map((a: any) => [a.method || "", a.path, a.name || ""])];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取描述
|
||||||
|
export function getEps() {
|
||||||
|
return JSON.stringify(readFile(join(tempPath, "eps.json")));
|
||||||
|
}
|
||||||
@ -1,12 +1,9 @@
|
|||||||
import { Plugin } from "vite";
|
import { createWriteStream } from "fs";
|
||||||
import prettier from "prettier";
|
import prettier from "prettier";
|
||||||
import fs from "fs";
|
import { join } from "path";
|
||||||
import path from "path";
|
import { createDir } from "../../utils";
|
||||||
|
import rules from "./rules";
|
||||||
import { isFunction, isRegExp, isString } from "lodash";
|
import { isFunction, isRegExp, isString } from "lodash";
|
||||||
import rules from "../config/rules";
|
|
||||||
|
|
||||||
// 根路径
|
|
||||||
const coolPath = path.join(__dirname, `../../src/cool`);
|
|
||||||
|
|
||||||
// 格式化
|
// 格式化
|
||||||
function format(data: any) {
|
function format(data: any) {
|
||||||
@ -107,23 +104,6 @@ const handler = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 解析body
|
|
||||||
function parseJson(req: any) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let d = "";
|
|
||||||
req.on("data", function (chunk: Buffer) {
|
|
||||||
d += chunk;
|
|
||||||
});
|
|
||||||
req.on("end", function () {
|
|
||||||
try {
|
|
||||||
resolve(JSON.parse(d));
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建组件
|
// 创建组件
|
||||||
function createComponent(item: any) {
|
function createComponent(item: any) {
|
||||||
const { propertyName: prop, comment: label } = item;
|
const { propertyName: prop, comment: label } = item;
|
||||||
@ -218,7 +198,7 @@ function datetimeMerge({ columns, item }: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建文件
|
// 创建文件
|
||||||
function createVue({ router, columns, prefix, api, module, filename }: any): void {
|
export async function createMenu({ router, columns, prefix, api, module, filename }: any): void {
|
||||||
const upsert: any = {
|
const upsert: any = {
|
||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
@ -352,7 +332,7 @@ export default defineComponent({
|
|||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格配置
|
// cl-table 配置
|
||||||
const table = reactive<Table>(${JSON.stringify(table)});
|
const table = reactive<Table>(${JSON.stringify(table)});
|
||||||
|
|
||||||
// crud 加载
|
// crud 加载
|
||||||
@ -385,48 +365,13 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// views 目录是否存在
|
// views 目录是否存在
|
||||||
const dir = path.join(coolPath, `modules/${module}/views`);
|
const dir = join(__dirname, `../../src/modules/${module}/views`);
|
||||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
|
||||||
|
// 创建目录
|
||||||
|
createDir(dir);
|
||||||
|
|
||||||
// 创建文件
|
// 创建文件
|
||||||
fs.createWriteStream(path.join(dir, `${filename}.vue`), {
|
createWriteStream(join(dir, `${filename}.vue`), {
|
||||||
flags: "w"
|
flags: "w"
|
||||||
}).write(content);
|
}).write(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cool = (): Plugin | null => {
|
|
||||||
return {
|
|
||||||
name: "vite-cool",
|
|
||||||
configureServer(server) {
|
|
||||||
server.middlewares.use(async (req, res, next) => {
|
|
||||||
function done(data) {
|
|
||||||
res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
|
|
||||||
res.end(JSON.stringify(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.url.includes("/__cool_createMenu")) {
|
|
||||||
try {
|
|
||||||
const body: any = await parseJson(req);
|
|
||||||
await createVue(body);
|
|
||||||
done({
|
|
||||||
code: 1000
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
done({
|
|
||||||
code: 1001,
|
|
||||||
message: e.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (req.url.includes("/__cool_modules")) {
|
|
||||||
const dirs = fs.readdirSync(path.join(coolPath, "modules"));
|
|
||||||
done({
|
|
||||||
code: 1000,
|
|
||||||
data: dirs.filter((e) => !e.includes("."))
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -73,7 +73,7 @@ export default [
|
|||||||
{
|
{
|
||||||
test: ["date"],
|
test: ["date"],
|
||||||
table: {
|
table: {
|
||||||
name: "cl-date",
|
name: "cl-date-text",
|
||||||
props: {
|
props: {
|
||||||
format: "YYYY-MM-DD"
|
format: "YYYY-MM-DD"
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ export default [
|
|||||||
{
|
{
|
||||||
test: ["dates", "dateRange", "dateScope"],
|
test: ["dates", "dateRange", "dateScope"],
|
||||||
table: {
|
table: {
|
||||||
name: "cl-date",
|
name: "cl-date-text",
|
||||||
props: {
|
props: {
|
||||||
format: "YYYY-MM-DD"
|
format: "YYYY-MM-DD"
|
||||||
}
|
}
|
||||||
11
build/cool/lib/modules/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
export function getModules() {
|
||||||
|
try {
|
||||||
|
const dirs = fs.readdirSync(join(__dirname, "../../src/modules"));
|
||||||
|
return Promise.resolve(dirs.filter((e) => !e.includes(".")));
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
1
build/cool/temp/eps.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[["/admin/base/comm",[["post","/personUpdate",""],["get","/uploadMode",""],["get","/permmenu",""],["get","/person",""],["post","/upload",""],["post","/logout",""],["","/list",""],["","/page",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]],["/admin/base/open",[["get","/refreshToken",""],["get","/captcha",""],["post","/login",""],["get","/html",""],["get","/eps",""],["","/list",""],["","/page",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]],["/admin/base/sys/department",[["post","/delete",""],["post","/update",""],["post","/order",""],["post","/list",""],["post","/add",""],["","/page",""],["","/info",""]]],["/admin/base/sys/log",[["post","/setKeep",""],["get","/getKeep",""],["post","/clear",""],["post","/page",""],["","/list",""],["","/info",""],["","/update",""],["","/delete",""],["","/add",""]]],["/admin/base/sys/menu",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/base/sys/param",[["post","/delete",""],["post","/update",""],["get","/html",""],["get","/info",""],["post","/page",""],["post","/add",""],["","/list",""]]],["/admin/base/sys/role",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/base/sys/user",[["post","/delete",""],["post","/update",""],["post","/move",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/demo/goods",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/page",""],["post","/list",""],["post","/add",""]]],["/admin/space/info",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/space/type",[["post","/delete",""],["post","/update",""],["get","/info",""],["post","/list",""],["post","/page",""],["post","/add",""]]],["/admin/task/info",[["post","/delete",""],["post","/update",""],["post","/start",""],["post","/once",""],["post","/stop",""],["get","/info",""],["post","/page",""],["get","/log",""],["post","/add",""],["","/list",""]]]]
|
||||||
723
build/cool/temp/service.d.ts
vendored
Normal file
@ -0,0 +1,723 @@
|
|||||||
|
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 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 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 };
|
||||||
|
space: { info: SpaceInfo; type: SpaceType };
|
||||||
|
task: { info: TaskInfo };
|
||||||
|
};
|
||||||
46
build/cool/utils/index.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
// 首字母大写
|
||||||
|
export function firstUpperCase(value: string): string {
|
||||||
|
return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
|
||||||
|
return $1.toUpperCase() + $2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 横杠转驼峰
|
||||||
|
export function toCamel(str: string): string {
|
||||||
|
return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
|
||||||
|
return $1 + $2.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建目录
|
||||||
|
export function createDir(path: string) {
|
||||||
|
if (!fs.existsSync(path)) fs.mkdirSync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取文件
|
||||||
|
export function readFile(name: string) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(name, "utf8");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析body
|
||||||
|
export function parseJson(req: any) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let d = "";
|
||||||
|
req.on("data", function (chunk: Buffer) {
|
||||||
|
d += chunk;
|
||||||
|
});
|
||||||
|
req.on("end", function () {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(d));
|
||||||
|
} catch {
|
||||||
|
resolve({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
46
package.json
@ -10,51 +10,57 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cool-vue/crud": "^1.0.6",
|
"@cool-vue/crud": "^1.0.6",
|
||||||
"array.prototype.flat": "^1.2.4",
|
"@element-plus/icons-vue": "^1.1.3",
|
||||||
"axios": "^0.21.1",
|
"@types/quill": "^2.0.9",
|
||||||
"clipboard": "^2.0.8",
|
"axios": "^0.26.1",
|
||||||
|
"clipboard": "^2.0.10",
|
||||||
"codemirror": "^5.62.0",
|
"codemirror": "^5.62.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"echarts": "^5.0.2",
|
"echarts": "^5.0.2",
|
||||||
"element-plus": "^1.1.0-beta.20",
|
"element-plus": "^2.1.7",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"js-beautify": "^1.13.5",
|
"js-beautify": "^1.13.5",
|
||||||
"mitt": "^2.1.0",
|
"lodash": "^4.17.21",
|
||||||
|
"mitt": "^3.0.0",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.0.12",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
"socket.io-client": "^4.1.2",
|
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"vue": "^3.2.20",
|
"vue": "^3.2.31",
|
||||||
"vue-echarts": "^6.0.0-rc.3",
|
"vue-echarts": "^6.0.2",
|
||||||
"vue-router": "^4.0.5",
|
"vue-router": "^4.0.14",
|
||||||
"vuedraggable": "^4.0.1",
|
"vuedraggable": "^4.1.0",
|
||||||
"vuex": "^4.0.0-0",
|
|
||||||
"xlsx": "^0.16.9"
|
"xlsx": "^0.16.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/node": "^16.10.2",
|
"@types/node": "^16.10.2",
|
||||||
|
"@types/nprogress": "^0.2.0",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
||||||
"@typescript-eslint/parser": "^4.20.0",
|
"@typescript-eslint/parser": "^4.20.0",
|
||||||
"@vitejs/plugin-vue": "1.9.2",
|
"@vitejs/plugin-vue": "^2.2.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^1.1.6",
|
"@vitejs/plugin-vue-jsx": "^1.3.8",
|
||||||
"@vue/compiler-sfc": "3.2.19",
|
"@vue/cli-plugin-babel": "^5.0.1",
|
||||||
|
"@vue/cli-plugin-typescript": "^5.0.1",
|
||||||
|
"@vue/compiler-sfc": "^3.2.31",
|
||||||
"@vue/composition-api": "^1.0.0-rc.13",
|
"@vue/composition-api": "^1.0.0-rc.13",
|
||||||
"eslint": "^7.23.0",
|
"eslint": "^7.23.0",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"eslint-config-prettier": "^8.1.0",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"eslint-plugin-vue": "^7.13.0",
|
"eslint-plugin-vue": "^7.13.0",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.4.1",
|
||||||
"sass": "^1.42.1",
|
"sass": "^1.49.9",
|
||||||
"sass-loader": "^11.1.1",
|
"sass-loader": "^11.1.1",
|
||||||
"svg-sprite-loader": "^6.0.2",
|
"svg-sprite-loader": "^6.0.2",
|
||||||
"typescript": "4.4.3",
|
"typescript": "^4.6.2",
|
||||||
"unplugin-vue-components": "0.15.4",
|
"unplugin-vue-components": "^0.17.21",
|
||||||
"vite": "2.6.7",
|
"vite": "^2.8.6",
|
||||||
"vite-plugin-compression": "^0.3.5",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"vite-plugin-dts": "^0.9.9",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-mock": "^2.9.6",
|
||||||
"vite-plugin-style-import": "^1.0.1",
|
"vite-plugin-style-import": "^1.0.1",
|
||||||
"vite-svg-loader": "^2.1.0"
|
"vite-svg-loader": "^2.1.0"
|
||||||
|
|||||||
15
src/App.vue
@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-config-provider :locale="locale">
|
<el-config-provider :locale="locale">
|
||||||
<div class="preload" v-if="loading">
|
<div class="preload" v-if="app.loading">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p class="name">{{ app.name }}</p>
|
<p class="name">{{ app.info.name }}</p>
|
||||||
<div class="loading"></div>
|
<div class="loading"></div>
|
||||||
<p class="title">正在加载菜单...</p>
|
<p class="title">正在加载菜单...</p>
|
||||||
<p class="sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
|
<p class="sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a href="https://cool-js.com/" target="_blank"> https://cool-js.com </a>
|
<a href="https://cool-js.com" target="_blank"> https://cool-js.com </a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -18,10 +18,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { ElConfigProvider } from "element-plus";
|
import { ElConfigProvider } from "element-plus";
|
||||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||||
import { useCool } from "/@/cool";
|
import { useBaseStore } from "/$/base";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -29,14 +29,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const { store, app } = useCool();
|
|
||||||
const locale = zhCn;
|
const locale = zhCn;
|
||||||
const loading = computed(() => store.getters.appLoading);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
loading,
|
...useBaseStore()
|
||||||
app
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@ -1,53 +0,0 @@
|
|||||||
import store from "store";
|
|
||||||
import { getUrlParam } from "/@/cool/utils";
|
|
||||||
import { MenuItem } from "/$/base/types";
|
|
||||||
|
|
||||||
// 路由模式
|
|
||||||
const routerMode: String = "history";
|
|
||||||
|
|
||||||
// 开发模式
|
|
||||||
const isDev: Boolean = import.meta.env.MODE === "development";
|
|
||||||
|
|
||||||
// Host
|
|
||||||
const host: String = "https://show.cool-admin.com";
|
|
||||||
|
|
||||||
// 请求地址
|
|
||||||
const baseUrl: String = (function () {
|
|
||||||
let proxy = getUrlParam("proxy");
|
|
||||||
|
|
||||||
if (proxy) {
|
|
||||||
store.set("proxy", proxy);
|
|
||||||
} else {
|
|
||||||
proxy = store.get("proxy") || "dev";
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDev ? `/${proxy}/admin` : `/api/admin`;
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Socket
|
|
||||||
const socketUrl: String = (isDev ? `${host}` : "") + "/socket";
|
|
||||||
|
|
||||||
// 阿里字体图标库 https://at.alicdn.com/t/**.css
|
|
||||||
const iconfontUrl = ``;
|
|
||||||
|
|
||||||
// 程序配置参数
|
|
||||||
const app: any = store.get("__app__") || {
|
|
||||||
name: "COOL-ADMIN",
|
|
||||||
|
|
||||||
conf: {
|
|
||||||
showAMenu: false, // 是否显示一级菜单栏
|
|
||||||
showRouteNav: true, // 是否显示路由导航栏
|
|
||||||
showProcess: true, // 是否显示页面进程栏
|
|
||||||
customMenu: false // 自定义菜单
|
|
||||||
},
|
|
||||||
|
|
||||||
theme: {
|
|
||||||
color: "", // 主题色
|
|
||||||
url: "" // 主题样式地址
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 自定义菜单列表
|
|
||||||
const menuList: MenuItem[] = [];
|
|
||||||
|
|
||||||
export { routerMode, baseUrl, socketUrl, iconfontUrl, app, isDev, menuList };
|
|
||||||
48
src/cool/bootstrap.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { createPinia } from "pinia";
|
||||||
|
import { App } from "vue";
|
||||||
|
import { useModule } from "./module";
|
||||||
|
import { router, addViews } from "./router";
|
||||||
|
import { useBaseStore } from "/$/base";
|
||||||
|
import mitt from "mitt";
|
||||||
|
import VueECharts from "vue-echarts";
|
||||||
|
import ElementPlus from "element-plus";
|
||||||
|
import "element-plus/theme-chalk/src/index.scss";
|
||||||
|
|
||||||
|
export async function bootstrap(Vue: App) {
|
||||||
|
// 缓存
|
||||||
|
Vue.use(createPinia());
|
||||||
|
|
||||||
|
// ui库
|
||||||
|
Vue.use(ElementPlus);
|
||||||
|
|
||||||
|
// 事件通讯
|
||||||
|
Vue.provide("mitt", mitt());
|
||||||
|
|
||||||
|
// 可视图表
|
||||||
|
Vue.component("v-chart", VueECharts);
|
||||||
|
|
||||||
|
// 基础
|
||||||
|
const { app, user, menu } = useBaseStore();
|
||||||
|
|
||||||
|
// 加载模块
|
||||||
|
useModule(Vue);
|
||||||
|
|
||||||
|
// 取缓存视图
|
||||||
|
addViews(menu.routes);
|
||||||
|
|
||||||
|
// 路由
|
||||||
|
Vue.use(router);
|
||||||
|
|
||||||
|
// 开启
|
||||||
|
app.showLoading();
|
||||||
|
|
||||||
|
if (user.token) {
|
||||||
|
// 获取用户信息
|
||||||
|
user.get();
|
||||||
|
|
||||||
|
// 获取菜单权限
|
||||||
|
await menu.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.hideLoading();
|
||||||
|
}
|
||||||
58
src/cool/config.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { getUrlParam } from "./utils";
|
||||||
|
import storage from "./utils/storage";
|
||||||
|
|
||||||
|
// 路由模式
|
||||||
|
export const routerMode: String = "history";
|
||||||
|
|
||||||
|
// 开发模式
|
||||||
|
export const isDev: Boolean = import.meta.env.MODE === "development";
|
||||||
|
|
||||||
|
// Host
|
||||||
|
export const host: String = "https://show.cool-admin.com";
|
||||||
|
|
||||||
|
// 请求地址
|
||||||
|
export const baseUrl: String = (function () {
|
||||||
|
let proxy = getUrlParam("proxy");
|
||||||
|
|
||||||
|
if (proxy) {
|
||||||
|
storage.set("proxy", proxy);
|
||||||
|
} else {
|
||||||
|
proxy = storage.get("proxy") || "dev";
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDev ? `/${proxy}` : `/api`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 字体图标库
|
||||||
|
export const iconfont = [];
|
||||||
|
|
||||||
|
// 程序配置参数
|
||||||
|
export const app: any = {
|
||||||
|
name: "COOL-ADMIN",
|
||||||
|
|
||||||
|
// 自定义菜单列表
|
||||||
|
menuList: [],
|
||||||
|
|
||||||
|
// 主题
|
||||||
|
theme: {
|
||||||
|
// 主色
|
||||||
|
color: "",
|
||||||
|
// 样式地址
|
||||||
|
url: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 忽略规则
|
||||||
|
export const ignore = {
|
||||||
|
// 不显示请求进度条
|
||||||
|
NProgress: ["/sys/info/record"],
|
||||||
|
// 页面不需要登录验证
|
||||||
|
token: ["/login", "/401", "/403", "/404", "/500", "/502"]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 测试
|
||||||
|
export const test = {
|
||||||
|
token: "",
|
||||||
|
mock: false,
|
||||||
|
eps: true
|
||||||
|
};
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import router from "/@/router";
|
|
||||||
import store from "/@/store";
|
|
||||||
import { service } from "./service";
|
|
||||||
import { useRouter } from "./router";
|
|
||||||
import { useModule } from "./module";
|
|
||||||
|
|
||||||
async function bootstrap(app: any) {
|
|
||||||
app.config.globalProperties.service = store.service = service;
|
|
||||||
app.provide("service", service);
|
|
||||||
|
|
||||||
useRouter();
|
|
||||||
useModule(app);
|
|
||||||
|
|
||||||
router.$plugin?.addViews(store.getters.routes || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function usePermission(list: any[]) {
|
|
||||||
function deep(d: any) {
|
|
||||||
if (d.permission) {
|
|
||||||
d._permission = {};
|
|
||||||
for (const i in d.permission) {
|
|
||||||
d._permission[i] =
|
|
||||||
list.findIndex((e: string) =>
|
|
||||||
e.replace(/:/g, "/").includes(`${d.namespace}/${i}`)
|
|
||||||
) >= 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const i in d) {
|
|
||||||
deep(d[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deep(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { service, bootstrap, usePermission };
|
|
||||||
export { BaseService, Service, Permission, useEps } from "./service";
|
|
||||||
export * from "./hook";
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
import { modules as mods } from "/@/cool/modules";
|
|
||||||
import store from "/@/store";
|
|
||||||
import router from "/@/router";
|
|
||||||
import { deepMerge, isFunction, isObject, isEmpty } from "../utils";
|
|
||||||
import { deepFiles } from "../service";
|
|
||||||
|
|
||||||
// 模块列表
|
|
||||||
const modules: any[] = [...mods];
|
|
||||||
|
|
||||||
function useModule(app: any) {
|
|
||||||
// 安装模块
|
|
||||||
function install(mod: any) {
|
|
||||||
const { store: _store, service, directives, components, pages, views, name } = mod;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 注册vuex模块
|
|
||||||
if (_store) {
|
|
||||||
for (const i in _store) {
|
|
||||||
store.registerModule(`${name}-${i}`, _store[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册请求服务
|
|
||||||
if (service) {
|
|
||||||
// @ts-ignore
|
|
||||||
deepMerge(store.service, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册组件
|
|
||||||
if (components) {
|
|
||||||
for (const i in components) {
|
|
||||||
if (components[i]) {
|
|
||||||
if (components[i].cool?.global || i.indexOf("cl-") === 0) {
|
|
||||||
app.component(components[i].name, components[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册指令
|
|
||||||
if (directives) {
|
|
||||||
for (const i in directives) {
|
|
||||||
app.directive(i, directives[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册页面
|
|
||||||
if (pages) {
|
|
||||||
pages.forEach((e: any) => {
|
|
||||||
router.addRoute(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册视图
|
|
||||||
if (views) {
|
|
||||||
views.forEach((e: any) => {
|
|
||||||
if (!e.meta) {
|
|
||||||
e.meta = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.path) {
|
|
||||||
router.$plugin?.addViews([e]);
|
|
||||||
} else {
|
|
||||||
console.error(`[${name}-views]:缺少 path 参数`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`模块 ${name} 异常`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扫描文件
|
|
||||||
const files = import.meta.globEager("/src/cool/modules/**/*");
|
|
||||||
|
|
||||||
for (const i in files) {
|
|
||||||
const [, , , , name, fn, cname] = i.split("/");
|
|
||||||
const value: any = files[i].default;
|
|
||||||
const fname: string = (cname || "").split(".")[0];
|
|
||||||
|
|
||||||
if (name == "index.ts") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function next(d: any) {
|
|
||||||
// 配置参数入口
|
|
||||||
if (fn == "config.ts") {
|
|
||||||
d.options = value || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模块入口
|
|
||||||
if (fn == "index.ts") {
|
|
||||||
if (value) {
|
|
||||||
// 阻止往下加载
|
|
||||||
d.isLoaded = true;
|
|
||||||
|
|
||||||
// 之前
|
|
||||||
d._beforeFn = (e: any) => {
|
|
||||||
if (e.components) {
|
|
||||||
for (const i in e.components) {
|
|
||||||
// 全局注册
|
|
||||||
e.components[i].cool = {
|
|
||||||
global: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
d.value = value;
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他功能
|
|
||||||
switch (fn) {
|
|
||||||
case "service":
|
|
||||||
d._services.push({
|
|
||||||
path: i.replace(`/src/cool/modules/${name}/service`, `${name}`),
|
|
||||||
value: new value()
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "pages":
|
|
||||||
case "views":
|
|
||||||
if (value.cool) {
|
|
||||||
d[fn].push({
|
|
||||||
...value.cool.route,
|
|
||||||
component: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "components":
|
|
||||||
d.components[value.name] = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "store":
|
|
||||||
d.store[fname] = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "directives":
|
|
||||||
d.directives[fname] = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item: any = modules.find((e) => e.name === name);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
if (!item.isLoaded) {
|
|
||||||
next(item);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
modules.push(
|
|
||||||
next({
|
|
||||||
name,
|
|
||||||
options: {},
|
|
||||||
directives: {},
|
|
||||||
components: {},
|
|
||||||
pages: [],
|
|
||||||
views: [],
|
|
||||||
store: {},
|
|
||||||
_services: [],
|
|
||||||
_local: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模块安装
|
|
||||||
modules.forEach((e: any) => {
|
|
||||||
if (!isEmpty(e._services)) {
|
|
||||||
e.service = deepFiles(e._services);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (e._beforeFn) {
|
|
||||||
e._beforeFn(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
install(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 缓存模块
|
|
||||||
store.commit("SET_MODULE", modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useModule };
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import { ElMessage } from "element-plus";
|
|
||||||
import store from "/@/store";
|
|
||||||
import router, { ignore } from "/@/router";
|
|
||||||
import { cloneDeep, storage } from "../utils";
|
|
||||||
|
|
||||||
const views = import.meta.globEager("/src/**/views/**/*.vue");
|
|
||||||
|
|
||||||
for (const i in views) {
|
|
||||||
views[i.slice(5)] = views[i];
|
|
||||||
delete views[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
function useRouter() {
|
|
||||||
router.$plugin = {
|
|
||||||
addViews: (list: Array<any>, options: any) => {
|
|
||||||
if (!options) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse route config
|
|
||||||
list.forEach((e: any) => {
|
|
||||||
const d: any = cloneDeep(e);
|
|
||||||
|
|
||||||
// avoid router repeat
|
|
||||||
d.name = d.router;
|
|
||||||
|
|
||||||
if (!d.component) {
|
|
||||||
const url = d.viewPath;
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
if (
|
|
||||||
/^(http[s]?:\/\/)([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i.test(
|
|
||||||
url
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
d.meta.iframeUrl = url;
|
|
||||||
d.component = () => import(`/$/base/pages/iframe/index.vue`);
|
|
||||||
} else {
|
|
||||||
d.component = () => Promise.resolve(views[url]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d.redirect = "/404";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch add route
|
|
||||||
router.addRoute("index", d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
router.beforeEach((to: any, from: any, next: any) => {
|
|
||||||
const { token, browser } = store.getters;
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
if (to.path.indexOf("/login") === 0) {
|
|
||||||
// 登录成功且 token 未过期,回到首页
|
|
||||||
if (!storage.isExpired("token")) {
|
|
||||||
return next("/");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 添加路由进程
|
|
||||||
store.commit("ADD_PROCESS", {
|
|
||||||
keepAlive: to.meta?.keepAlive,
|
|
||||||
label: to.meta?.label || to.name,
|
|
||||||
value: to.fullPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!ignore.token.some((e: string) => to.path.indexOf(e) === 0)) {
|
|
||||||
return next("/login");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// H5 下关闭左侧菜单
|
|
||||||
if (browser && browser.isMini) {
|
|
||||||
store.commit("COLLAPSE_MENU", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
let lock = false;
|
|
||||||
|
|
||||||
router.onError((err: any) => {
|
|
||||||
if (!lock) {
|
|
||||||
lock = true;
|
|
||||||
|
|
||||||
ElMessage.error(`页面不存在或者未配置`);
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
lock = false;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useRouter };
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import request from "/@/service/request";
|
|
||||||
import { baseUrl, isDev } from "/@/config/env";
|
|
||||||
|
|
||||||
export default class BaseService {
|
|
||||||
constructor(options: any = {}) {
|
|
||||||
const crud: any = {
|
|
||||||
page: "page",
|
|
||||||
list: "list",
|
|
||||||
info: "info",
|
|
||||||
add: "add",
|
|
||||||
delete: "delete",
|
|
||||||
update: "update"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options?.namespace) {
|
|
||||||
this.namespace = options?.namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.permission) this.permission = {};
|
|
||||||
|
|
||||||
for (const i in crud) {
|
|
||||||
if (this.namespace) {
|
|
||||||
this.permission[i] = this.namespace.replace(/\//g, ":") + ":" + crud[i];
|
|
||||||
} else {
|
|
||||||
this.permission[i] = crud[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request(options: any = {}) {
|
|
||||||
if (!options.params) options.params = {};
|
|
||||||
|
|
||||||
let ns = "";
|
|
||||||
|
|
||||||
// 是否 mock 模式
|
|
||||||
if (!this.mock) {
|
|
||||||
if (isDev) {
|
|
||||||
ns = this.proxy || baseUrl;
|
|
||||||
} else {
|
|
||||||
ns = this.proxy ? this.url : baseUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拼接前缀
|
|
||||||
if (this.namespace) {
|
|
||||||
ns += "/" + this.namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 http
|
|
||||||
if (options.url.indexOf("http") !== 0) {
|
|
||||||
options.url = ns + options.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return request(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
list(data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/list",
|
|
||||||
method: "POST",
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
page(data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/page",
|
|
||||||
method: "POST",
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
info(params: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/info",
|
|
||||||
params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update(data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/update",
|
|
||||||
method: "POST",
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/delete",
|
|
||||||
method: "POST",
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
add(data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/add",
|
|
||||||
method: "POST",
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import { isObject } from "../utils";
|
|
||||||
|
|
||||||
export function Permission(value: string) {
|
|
||||||
return function (target: any, key: any, descriptor: any) {
|
|
||||||
if (!target.permission) {
|
|
||||||
target.permission = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
target.permission[key] = (
|
|
||||||
(target.namespace ? target.namespace + "/" : "") + value
|
|
||||||
).replace(/\//g, ":");
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
return descriptor;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Service(value: any) {
|
|
||||||
return function (target: any) {
|
|
||||||
// 命名
|
|
||||||
if (typeof value == "string") {
|
|
||||||
target.prototype.namespace = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复杂项
|
|
||||||
if (isObject(value)) {
|
|
||||||
const { proxy, namespace, url, mock } = value;
|
|
||||||
const item = __PROXY_LIST__[proxy];
|
|
||||||
|
|
||||||
if (proxy && !item) {
|
|
||||||
console.error(`${proxy} 指向的地址不存在!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.prototype.namespace = namespace;
|
|
||||||
target.prototype.mock = mock;
|
|
||||||
|
|
||||||
if (proxy) {
|
|
||||||
target.prototype.proxy = proxy;
|
|
||||||
target.prototype.url = url || item ? item.target : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
import BaseService from "./base";
|
|
||||||
import { Service, Permission } from "./decorator";
|
|
||||||
import { basename } from "../utils";
|
|
||||||
|
|
||||||
function deepFiles(list: any[]) {
|
|
||||||
const modules: any = {};
|
|
||||||
|
|
||||||
list.forEach((e) => {
|
|
||||||
const arr: any[] = e.path.split("/");
|
|
||||||
const parents: any[] = arr.slice(0, arr.length - 1);
|
|
||||||
const name: string = basename(e.path).replace(".ts", "");
|
|
||||||
|
|
||||||
let curr: any = modules;
|
|
||||||
let prev: any = null;
|
|
||||||
let key: any = null;
|
|
||||||
|
|
||||||
parents.forEach((k) => {
|
|
||||||
if (!curr[k]) {
|
|
||||||
curr[k] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = curr;
|
|
||||||
curr = curr[k];
|
|
||||||
key = k;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (name == "index") {
|
|
||||||
prev[key] = e.value;
|
|
||||||
} else {
|
|
||||||
curr[name] = e.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useService() {
|
|
||||||
const files = import.meta.globEager("/src/service/**/*.ts");
|
|
||||||
const d: any = [];
|
|
||||||
|
|
||||||
for (const i in files) {
|
|
||||||
if (!i.includes("request.ts")) {
|
|
||||||
const value = files[i].default;
|
|
||||||
d.push({
|
|
||||||
path: i.replace("/src/service/", ""),
|
|
||||||
value: new value()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const s = deepFiles(d);
|
|
||||||
s.request = new BaseService().request;
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
const service = useService();
|
|
||||||
|
|
||||||
function useEps() {
|
|
||||||
return service.base.common
|
|
||||||
.eps()
|
|
||||||
.then((res: any) => {
|
|
||||||
for (const i in res) {
|
|
||||||
res[i].forEach((e: any) => {
|
|
||||||
// 分隔路径
|
|
||||||
const arr = e.prefix
|
|
||||||
.replace(/\//, "")
|
|
||||||
.replace("admin", "")
|
|
||||||
.split("/")
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
function deep(d: any, i: number) {
|
|
||||||
const k = arr[i];
|
|
||||||
|
|
||||||
if (k) {
|
|
||||||
// 是否最后一个
|
|
||||||
if (arr[i + 1]) {
|
|
||||||
if (!d[k]) {
|
|
||||||
d[k] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
deep(d[k], i + 1);
|
|
||||||
} else {
|
|
||||||
// 本地不存在则创建实例
|
|
||||||
if (!d[k]) {
|
|
||||||
d[k] = new BaseService({
|
|
||||||
namespace: e.prefix.replace("/admin/", "")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建方法
|
|
||||||
e.api.forEach((a: any) => {
|
|
||||||
const n = a.path.replace("/", "");
|
|
||||||
|
|
||||||
if (
|
|
||||||
![
|
|
||||||
"add",
|
|
||||||
"info",
|
|
||||||
"update",
|
|
||||||
"page",
|
|
||||||
"list",
|
|
||||||
"delete"
|
|
||||||
].includes(n)
|
|
||||||
) {
|
|
||||||
// 设置权限
|
|
||||||
d[k].permission[n] = (
|
|
||||||
(d[k].namespace ? d[k].namespace + "/" : "") + n
|
|
||||||
).replace(/\//g, ":");
|
|
||||||
|
|
||||||
// 本地不存在则创建
|
|
||||||
if (!d[k][n]) {
|
|
||||||
d[k][n] = function (data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: a.path,
|
|
||||||
method: a.method,
|
|
||||||
[a.method.toLocaleLowerCase() == "post"
|
|
||||||
? "data"
|
|
||||||
: "params"]: data
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deep(service, 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
console.error("Eps error", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { BaseService, Service, Permission, service, deepFiles, useService, useEps };
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { onBeforeUpdate, ref, inject, computed } from "vue";
|
import { onBeforeUpdate, ref, inject, getCurrentInstance } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useStore } from "vuex";
|
import { useService } from "../service";
|
||||||
|
|
||||||
export function useRefs() {
|
export function useRefs() {
|
||||||
const refs: any = ref<any[]>([]);
|
const refs: any = ref<any[]>([]);
|
||||||
@ -16,34 +16,34 @@ export function useRefs() {
|
|||||||
return { refs, setRefs };
|
return { refs, setRefs };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 服务
|
||||||
|
const service = useService();
|
||||||
|
|
||||||
|
// 组件命名
|
||||||
|
function named(name: string) {
|
||||||
|
const { proxy }: any = getCurrentInstance();
|
||||||
|
proxy.$.type.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
export function useCool() {
|
export function useCool() {
|
||||||
const { refs, setRefs } = useRefs();
|
const { refs, setRefs } = useRefs();
|
||||||
const service = inject<any>("service");
|
|
||||||
|
// 通信
|
||||||
const mitt = inject<any>("mitt");
|
const mitt = inject<any>("mitt");
|
||||||
const store = useStore();
|
|
||||||
|
// 路由
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
// 路由器
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const app = computed(() => store.getters.app);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
store,
|
|
||||||
route,
|
route,
|
||||||
router,
|
router,
|
||||||
refs,
|
refs,
|
||||||
setRefs,
|
setRefs,
|
||||||
service,
|
service,
|
||||||
mitt,
|
mitt,
|
||||||
app
|
named
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useModule() {
|
|
||||||
const store = useStore();
|
|
||||||
const moduleList = computed(() => store.getters.moduleList);
|
|
||||||
const modules = computed(() => store.getters.modules);
|
|
||||||
|
|
||||||
return {
|
|
||||||
moduleList,
|
|
||||||
modules
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1,2 +1,5 @@
|
|||||||
export * from "./core";
|
export * from "./service";
|
||||||
export * from "./modules";
|
export * from "./bootstrap";
|
||||||
|
export * from "./hook";
|
||||||
|
export * from "./router";
|
||||||
|
export { storage } from "./utils";
|
||||||
|
|||||||
161
src/cool/module/index.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import modules from "/@/modules";
|
||||||
|
import { router, addViews } from "../router";
|
||||||
|
import { isFunction, isObject, filename } from "../utils";
|
||||||
|
import { App } from "vue";
|
||||||
|
import module from "../utils/module";
|
||||||
|
|
||||||
|
// 扫描文件
|
||||||
|
const files = import.meta.globEager("/src/modules/**/*");
|
||||||
|
|
||||||
|
// 模块列表
|
||||||
|
const list: any[] = [...modules];
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
for (const i in files) {
|
||||||
|
// 模块名
|
||||||
|
const [, , , name, action] = i.split("/");
|
||||||
|
|
||||||
|
if (name == "index.ts") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件内容
|
||||||
|
let value: any = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
value = files[i].default;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err, i);
|
||||||
|
value = files[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件名
|
||||||
|
const fname: string = filename(i);
|
||||||
|
|
||||||
|
// 配置参数
|
||||||
|
function next(d: any) {
|
||||||
|
// 配置参数入口
|
||||||
|
if (action == "config.ts") {
|
||||||
|
d.options = value || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他功能
|
||||||
|
switch (action) {
|
||||||
|
case "service":
|
||||||
|
const s = new value();
|
||||||
|
|
||||||
|
d.service.push({
|
||||||
|
path: s.namespace,
|
||||||
|
value: s
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "pages":
|
||||||
|
case "views":
|
||||||
|
if (value.cool) {
|
||||||
|
d[action].push({
|
||||||
|
...value.cool.route,
|
||||||
|
component: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "components":
|
||||||
|
d.components[value.name] = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "directives":
|
||||||
|
d.directives[fname] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否存在
|
||||||
|
const item: any = list.find((e) => e.name === name);
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
if (!item.isLoaded) {
|
||||||
|
next(item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.push(
|
||||||
|
next({
|
||||||
|
name,
|
||||||
|
options: {},
|
||||||
|
directives: {},
|
||||||
|
components: {},
|
||||||
|
pages: [],
|
||||||
|
views: [],
|
||||||
|
service: []
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.set(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
|
export function useModule(app: App) {
|
||||||
|
// 模块安装
|
||||||
|
list.forEach((e: any) => {
|
||||||
|
if (isObject(e.value)) {
|
||||||
|
if (isFunction(e.value.install)) {
|
||||||
|
Object.assign(e, e.value.install(app, e.options));
|
||||||
|
} else {
|
||||||
|
Object.assign(e, e.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 注册组件
|
||||||
|
if (e.components) {
|
||||||
|
for (const i in e.components) {
|
||||||
|
if (e.components[i]) {
|
||||||
|
if (e.components[i].cool?.global || i.indexOf("cl-") === 0) {
|
||||||
|
app.component(e.components[i].name, e.components[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册指令
|
||||||
|
if (e.directives) {
|
||||||
|
for (const i in e.directives) {
|
||||||
|
app.directive(i, e.directives[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册页面
|
||||||
|
if (e.pages) {
|
||||||
|
e.pages.forEach((e: any) => {
|
||||||
|
router.addRoute(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册视图
|
||||||
|
if (e.views) {
|
||||||
|
e.views.forEach((e: any) => {
|
||||||
|
if (!e.meta) {
|
||||||
|
e.meta = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.path) {
|
||||||
|
addViews([e]);
|
||||||
|
} else {
|
||||||
|
console.error(`[${name}-views]:缺少 path 参数`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`模块 ${name} 异常`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import { iconList } from "./theme";
|
|
||||||
import "./resize";
|
|
||||||
|
|
||||||
export { iconList };
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import store from "/@/store";
|
|
||||||
|
|
||||||
const lock: any = {
|
|
||||||
menuCollapse: null,
|
|
||||||
showAMenu: null
|
|
||||||
};
|
|
||||||
|
|
||||||
function resize() {
|
|
||||||
// 更新数据
|
|
||||||
store.commit("SET_BROWSER");
|
|
||||||
|
|
||||||
const { browser, menuCollapse, app } = store.getters;
|
|
||||||
|
|
||||||
if (browser.isMini) {
|
|
||||||
// 小屏幕下隐藏一级菜单
|
|
||||||
if (lock.showAMenu === null) {
|
|
||||||
lock.showAMenu = app.conf.showAMenu;
|
|
||||||
store.commit("UPDATE_APP", {
|
|
||||||
conf: {
|
|
||||||
showAMenu: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 小屏幕下收起左侧菜单
|
|
||||||
if (lock.menuCollapse === null) {
|
|
||||||
lock.menuCollapse = menuCollapse;
|
|
||||||
store.commit("COLLAPSE_MENU", true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 大屏幕下显示一级菜单
|
|
||||||
if (lock.showAMenu !== null) {
|
|
||||||
store.commit("UPDATE_APP", {
|
|
||||||
conf: {
|
|
||||||
showAMenu: lock.showAMenu
|
|
||||||
}
|
|
||||||
});
|
|
||||||
lock.showAMenu = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 大屏幕下展开左侧菜单
|
|
||||||
if (lock.menuCollapse !== null) {
|
|
||||||
store.commit("COLLAPSE_MENU", lock.menuCollapse);
|
|
||||||
lock.menuCollapse = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = function () {
|
|
||||||
window.addEventListener("resize", resize);
|
|
||||||
resize();
|
|
||||||
};
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-loading="loading" class="cl-dept-check">
|
|
||||||
<p v-if="title">{{ title }}</p>
|
|
||||||
|
|
||||||
<div class="cl-dept-check__search">
|
|
||||||
<el-input v-model="keyword" placeholder="输入关键字进行过滤" size="small" />
|
|
||||||
<el-switch
|
|
||||||
v-model="form.relevance"
|
|
||||||
:active-value="1"
|
|
||||||
:inactive-value="0"
|
|
||||||
@change="onCheckStrictlyChange"
|
|
||||||
/>是否关联上下级
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="visible" class="cl-dept-check__tree">
|
|
||||||
<el-tree
|
|
||||||
:ref="setRefs('tree')"
|
|
||||||
highlight-current
|
|
||||||
node-key="id"
|
|
||||||
show-checkbox
|
|
||||||
:data="list"
|
|
||||||
:props="{
|
|
||||||
label: 'name',
|
|
||||||
children: 'children'
|
|
||||||
}"
|
|
||||||
:default-checked-keys="checked"
|
|
||||||
:filter-node-method="filterNode"
|
|
||||||
:check-strictly="!form.relevance"
|
|
||||||
@check-change="onCheckChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { deepTree } from "/@/cool/utils";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { defineComponent, inject, nextTick, onMounted, ref, watch } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-dept-check",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
title: String
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { service, refs, setRefs } = useCool();
|
|
||||||
|
|
||||||
// 表单值
|
|
||||||
const form = inject<any>("form");
|
|
||||||
|
|
||||||
// 树形列表
|
|
||||||
const list = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 已选列表
|
|
||||||
const checked = ref<any>([]);
|
|
||||||
|
|
||||||
// 关键字搜素
|
|
||||||
const keyword = ref<string>("");
|
|
||||||
|
|
||||||
// 加载中
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
const visible = ref<boolean>(true);
|
|
||||||
|
|
||||||
// 刷新已选列表
|
|
||||||
function refreshTree(val: any[]) {
|
|
||||||
checked.value = val || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新树形列表
|
|
||||||
function refresh() {
|
|
||||||
service.base.sys.department
|
|
||||||
.list()
|
|
||||||
.then((res: any[]) => {
|
|
||||||
list.value = deepTree(res);
|
|
||||||
refreshTree(props.modelValue);
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤节点
|
|
||||||
function filterNode(val: string, data: any) {
|
|
||||||
if (!val) return true;
|
|
||||||
return data.name.includes(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否关联上下级
|
|
||||||
function onCheckStrictlyChange() {
|
|
||||||
visible.value = false;
|
|
||||||
checked.value = [];
|
|
||||||
emit("update:modelValue", []);
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
visible.value = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听选择
|
|
||||||
function onCheckChange() {
|
|
||||||
if (refs.value.tree) {
|
|
||||||
emit("update:modelValue", refs.value.tree.getCheckedKeys());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听过滤
|
|
||||||
watch(keyword, (val: string) => {
|
|
||||||
refs.value.tree.filter(val);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 刷新树
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val: any[]) => {
|
|
||||||
refreshTree(val);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
form,
|
|
||||||
list,
|
|
||||||
checked,
|
|
||||||
keyword,
|
|
||||||
loading,
|
|
||||||
visible,
|
|
||||||
refresh,
|
|
||||||
filterNode,
|
|
||||||
onCheckStrictlyChange,
|
|
||||||
onCheckChange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cl-dept-check {
|
|
||||||
&__search {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.el-input {
|
|
||||||
flex: 1;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-switch {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__tree {
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
max-height: 200px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
.cl-slider-menu {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu {
|
|
||||||
border-right: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
.el-sub-menu__title,
|
|
||||||
&-item {
|
|
||||||
&.is-active,
|
|
||||||
&:hover {
|
|
||||||
background-color: $color-primary !important;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-sub-menu__title,
|
|
||||||
&-item,
|
|
||||||
&__title {
|
|
||||||
color: #eee;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 50px;
|
|
||||||
|
|
||||||
.icon-svg {
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 0 15px 0 5px;
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 12px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--collapse {
|
|
||||||
.el-sub-menu__title {
|
|
||||||
.icon-svg {
|
|
||||||
margin-left: 2px;
|
|
||||||
font-size: 19px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__popup {
|
|
||||||
.icon-svg {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-slider-menu__submenu {
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
&.el-menu {
|
|
||||||
&--vertical {
|
|
||||||
.el-sub-menu {
|
|
||||||
&__title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon-svg {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon-svg {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import { computed, defineComponent, h, ref, watch } from "vue";
|
|
||||||
import "./index.scss";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-menu-slider",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { router, route, store } = useCool();
|
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
const visible = ref<boolean>(true);
|
|
||||||
// 菜单列表
|
|
||||||
const menuList = computed(() => store.getters.menuList);
|
|
||||||
// 菜单是否折叠
|
|
||||||
const menuCollapse = computed(() => store.getters.menuCollapse);
|
|
||||||
// 浏览器信息
|
|
||||||
const browser: any = computed(() => store.getters.browser);
|
|
||||||
|
|
||||||
// 页面跳转
|
|
||||||
function toView(url: string) {
|
|
||||||
if (url != route.path) {
|
|
||||||
router.push(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新菜单
|
|
||||||
function refresh() {
|
|
||||||
visible.value = false;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
visible.value = true;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听菜单变化
|
|
||||||
watch(menuList, refresh);
|
|
||||||
|
|
||||||
return {
|
|
||||||
route,
|
|
||||||
visible,
|
|
||||||
menuList,
|
|
||||||
menuCollapse,
|
|
||||||
browser,
|
|
||||||
toView,
|
|
||||||
refresh
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
render(ctx: any) {
|
|
||||||
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,
|
|
||||||
"popper-class": "cl-slider-menu__popup"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: () => {
|
|
||||||
return ctx.menuCollapse && index == 1 ? (
|
|
||||||
<icon-svg name={e.icon}></icon-svg>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
<icon-svg name={e.icon}></icon-svg>
|
|
||||||
<span>{e.name}</span>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
default() {
|
|
||||||
return deepMenu(e.children, index + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
html = h(
|
|
||||||
<el-menu-item></el-menu-item>,
|
|
||||||
{
|
|
||||||
index: e.path,
|
|
||||||
key: e.id
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title() {
|
|
||||||
return <span>{e.name}</span>;
|
|
||||||
},
|
|
||||||
default() {
|
|
||||||
return <icon-svg name={e.icon}></icon-svg>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const children = deepMenu(ctx.menuList, 1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
ctx.visible && (
|
|
||||||
<div class="cl-slider-menu">
|
|
||||||
<el-menu
|
|
||||||
default-active={ctx.route.path}
|
|
||||||
background-color="transparent"
|
|
||||||
collapse-transition={false}
|
|
||||||
collapse={ctx.browser.isMini ? false : ctx.menuCollapse}
|
|
||||||
onSelect={ctx.toView}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</el-menu>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,255 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-process">
|
|
||||||
<div class="app-process__left hidden-xs-only" @click="toScroll(true)">
|
|
||||||
<i class="el-icon-arrow-left"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :ref="setRefs('scroller')" class="app-process__scroller">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in list"
|
|
||||||
:key="index"
|
|
||||||
:ref="setRefs(`item-${index}`)"
|
|
||||||
class="app-process__item"
|
|
||||||
:class="{ active: item.active }"
|
|
||||||
:data-index="index"
|
|
||||||
@click="onTap(item, Number(index))"
|
|
||||||
@contextmenu.stop.prevent="openCM($event, item)"
|
|
||||||
>
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
<i
|
|
||||||
v-if="index > 0"
|
|
||||||
class="el-icon-close"
|
|
||||||
@mousedown.stop="onDel(Number(index))"
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="app-process__right hidden-xs-only" @click="toScroll(false)">
|
|
||||||
<i class="el-icon-arrow-right"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, reactive, watch } from "vue";
|
|
||||||
import { last } from "/@/cool/utils";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { ContextMenu } from "@cool-vue/crud";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "cl-process",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, store, route, router }: any = useCool();
|
|
||||||
|
|
||||||
// 参数配置
|
|
||||||
const menu = reactive<any>({
|
|
||||||
current: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 数据列表
|
|
||||||
const list = computed(() => store.getters.processList);
|
|
||||||
|
|
||||||
// 跳转
|
|
||||||
function toPath() {
|
|
||||||
const active = list.value.find((e: any) => e.active);
|
|
||||||
|
|
||||||
if (!active) {
|
|
||||||
const next = last(list.value);
|
|
||||||
router.push(next ? next.value : "/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动到
|
|
||||||
function scrollTo(left: number) {
|
|
||||||
refs.value.scroller.scrollTo({
|
|
||||||
left,
|
|
||||||
behavior: "smooth"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 左右移动
|
|
||||||
function toScroll(f: boolean) {
|
|
||||||
scrollTo(refs.value.scroller.scrollLeft + (f ? -100 : 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调整滚动位置
|
|
||||||
function adScroll(index: number) {
|
|
||||||
const el = refs.value[`item-${index}`];
|
|
||||||
|
|
||||||
if (el) {
|
|
||||||
scrollTo(el.offsetLeft + el.clientWidth - refs.value.scroller.clientWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择
|
|
||||||
function onTap(item: any, index: number) {
|
|
||||||
adScroll(index);
|
|
||||||
router.push(item.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
function onDel(index: number) {
|
|
||||||
store.commit("DEL_PROCESS", index);
|
|
||||||
toPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右键菜单
|
|
||||||
function openCM(e: any, item: any) {
|
|
||||||
ContextMenu.open(e, {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "关闭当前",
|
|
||||||
hidden: item.value !== route.path,
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
onDel(list.value.findIndex((e: any) => e.value == item.value));
|
|
||||||
done();
|
|
||||||
toPath();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "关闭其他",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
store.commit(
|
|
||||||
"SET_PROCESS",
|
|
||||||
list.value.filter(
|
|
||||||
(e: any) => e.value == item.value || e.value == "/"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
toPath();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "关闭所有",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
store.commit(
|
|
||||||
"SET_PROCESS",
|
|
||||||
list.value.filter((e: any) => e.value == "/")
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
toPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => route.path,
|
|
||||||
function (val) {
|
|
||||||
adScroll(list.value.findIndex((e: any) => e.value === val) || 0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
menu,
|
|
||||||
list,
|
|
||||||
onTap,
|
|
||||||
onDel,
|
|
||||||
toPath,
|
|
||||||
toScroll,
|
|
||||||
adScroll,
|
|
||||||
scrollTo,
|
|
||||||
openCM
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-process {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&__left,
|
|
||||||
&__right {
|
|
||||||
background-color: #fff;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
padding: 0 2px;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__left {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__right {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__scroller {
|
|
||||||
width: 100%;
|
|
||||||
flex: 1;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 3px;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
padding: 0 10px;
|
|
||||||
background-color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-right: 10px;
|
|
||||||
color: #909399;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.el-icon-close {
|
|
||||||
width: 14px;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
span {
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
width: auto;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
background-color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-loading="loading" class="cl-role-perms">
|
|
||||||
<p v-if="title">{{ title }}</p>
|
|
||||||
|
|
||||||
<el-input v-model="keyword" placeholder="输入关键字进行过滤" size="small" />
|
|
||||||
|
|
||||||
<div class="scroller">
|
|
||||||
<el-tree
|
|
||||||
ref="treeRef"
|
|
||||||
highlight-current
|
|
||||||
node-key="id"
|
|
||||||
show-checkbox
|
|
||||||
:data="list"
|
|
||||||
:props="{
|
|
||||||
label: 'name',
|
|
||||||
children: 'children'
|
|
||||||
}"
|
|
||||||
:default-checked-keys="checked"
|
|
||||||
:filter-node-method="filterNode"
|
|
||||||
@check-change="save"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, ref, watch } from "vue";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { deepTree } from "/@/cool/utils";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-role-perms",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
title: String
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { service } = useCool();
|
|
||||||
|
|
||||||
// 树形列表
|
|
||||||
const list = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 已选列表
|
|
||||||
const checked = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 搜索关键字
|
|
||||||
const keyword = ref<string>("");
|
|
||||||
|
|
||||||
// 加载中
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
|
||||||
// el-tree 组件
|
|
||||||
const treeRef = ref<any>({});
|
|
||||||
|
|
||||||
// 刷新树
|
|
||||||
function refreshTree(val: any[]) {
|
|
||||||
if (!val) {
|
|
||||||
checked.value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ids: any[] = [];
|
|
||||||
|
|
||||||
// 处理半选状态
|
|
||||||
const fn = (list: any[]) => {
|
|
||||||
list.forEach((e) => {
|
|
||||||
if (e.children) {
|
|
||||||
fn(e.children);
|
|
||||||
} else {
|
|
||||||
ids.push(Number(e.id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fn(list.value);
|
|
||||||
|
|
||||||
checked.value = ids.filter((id) => (val || []).includes(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新列表
|
|
||||||
function refresh() {
|
|
||||||
service.base.sys.menu
|
|
||||||
.list()
|
|
||||||
.then((res: any[]) => {
|
|
||||||
list.value = deepTree(res);
|
|
||||||
refreshTree(props.modelValue);
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤节点
|
|
||||||
function filterNode(val: string, data: any) {
|
|
||||||
if (!val) return true;
|
|
||||||
return data.name.includes(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存
|
|
||||||
function save() {
|
|
||||||
// 选中的节点
|
|
||||||
const checked = treeRef.value.getCheckedKeys();
|
|
||||||
// 半选中的节点
|
|
||||||
const halfChecked = treeRef.value.getHalfCheckedKeys();
|
|
||||||
|
|
||||||
emit("update:modelValue", [...checked, ...halfChecked]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤监听
|
|
||||||
watch(keyword, (val: string) => {
|
|
||||||
treeRef.value.filter(val);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 刷新监听
|
|
||||||
watch(() => props.modelValue, refreshTree);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
list,
|
|
||||||
checked,
|
|
||||||
keyword,
|
|
||||||
loading,
|
|
||||||
treeRef,
|
|
||||||
filterNode,
|
|
||||||
save
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scroller {
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
max-height: 200px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-select v-model="value" v-bind="props" multiple @change="onChange">
|
|
||||||
<el-option v-for="(item, index) in list" :key="index" :value="item.id" :label="item.name" />
|
|
||||||
</el-select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, ref, watch } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { isArray } from "/@/cool/utils";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-role-select",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: [String, Number, Array],
|
|
||||||
props: Object
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
// 请求服务
|
|
||||||
const { service } = useCool();
|
|
||||||
|
|
||||||
// 数据列表
|
|
||||||
const list = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 绑定值
|
|
||||||
const value = ref<any>();
|
|
||||||
|
|
||||||
// 绑定值回调
|
|
||||||
function onChange(val: any) {
|
|
||||||
emit("update:modelValue", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析值
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val: any) => {
|
|
||||||
value.value = (isArray(val) ? val : [val]).filter(Boolean);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
list.value = await service.base.sys.role.list();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
list,
|
|
||||||
value,
|
|
||||||
onChange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="cl-switch">
|
|
||||||
<el-switch
|
|
||||||
:ref="setRefs('switch')"
|
|
||||||
:model-value="status"
|
|
||||||
:disabled="disabled"
|
|
||||||
:loading="loading"
|
|
||||||
:width="width"
|
|
||||||
:inline-prompt="inlinePrompt"
|
|
||||||
:active-icon="activeIcon"
|
|
||||||
:inactive-icon="inactiveIcon"
|
|
||||||
:active-text="activeText"
|
|
||||||
:inactive-text="inactiveText"
|
|
||||||
:active-value="activeValue"
|
|
||||||
:inactive-value="inactiveValue"
|
|
||||||
:active-color="activeColor"
|
|
||||||
:inactive-color="inactiveColor"
|
|
||||||
:border-color="borderColor"
|
|
||||||
:string="string"
|
|
||||||
:validate-event="validateEvent"
|
|
||||||
:before-change="beforeChange"
|
|
||||||
@change="onChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { defineComponent, inject, ref, watch } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-switch",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
scope: null,
|
|
||||||
column: null,
|
|
||||||
modelValue: [Boolean, String, Number],
|
|
||||||
disabled: Boolean,
|
|
||||||
loading: Boolean,
|
|
||||||
width: Number,
|
|
||||||
inlinePrompt: Boolean,
|
|
||||||
activeIcon: String,
|
|
||||||
inactiveIcon: String,
|
|
||||||
activeText: String,
|
|
||||||
inactiveText: String,
|
|
||||||
activeValue: {
|
|
||||||
type: [Boolean, String, Number],
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
inactiveValue: {
|
|
||||||
type: [Boolean, String, Number],
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
activeColor: String,
|
|
||||||
inactiveColor: String,
|
|
||||||
borderColor: String,
|
|
||||||
string: String,
|
|
||||||
validateEvent: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
beforeChange: Function
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue", "change"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { refs, setRefs } = useCool();
|
|
||||||
const crud = inject<any>("crud");
|
|
||||||
|
|
||||||
// 状态
|
|
||||||
const status = ref<any>(props.modelValue);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val: any) => {
|
|
||||||
status.value = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function focus() {
|
|
||||||
refs.value.switch.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(val: boolean | string | number) {
|
|
||||||
crud.service
|
|
||||||
.update({
|
|
||||||
...props.scope,
|
|
||||||
[props.column.property]: val
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
emit("update:modelValue", val);
|
|
||||||
emit("change", val);
|
|
||||||
status.value = val;
|
|
||||||
ElMessage.success("更新成功");
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
focus,
|
|
||||||
onChange,
|
|
||||||
status
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import store from "/@/store";
|
|
||||||
|
|
||||||
function parse(value: any) {
|
|
||||||
const permission = store.getters.permission;
|
|
||||||
|
|
||||||
if (typeof value == "string") {
|
|
||||||
return value ? permission.some((e: any) => e.includes(value.replace(/\s/g, ""))) : false;
|
|
||||||
} else {
|
|
||||||
return Boolean(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPerm(value: any) {
|
|
||||||
if (!value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.prototype.toString.call(value) === "[object Object]") {
|
|
||||||
if (value.or) {
|
|
||||||
return value.or.some(parse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.and) {
|
|
||||||
return value.and.some((e: any) => !parse(e)) ? false : true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parse(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function change(el: any, binding: any) {
|
|
||||||
el.style.display = checkPerm(binding.value) ? el.getAttribute("_display") : "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
beforeMount(el: any, binding: any) {
|
|
||||||
el.setAttribute("_display", el.style.display || "");
|
|
||||||
change(el, binding);
|
|
||||||
},
|
|
||||||
updated: change
|
|
||||||
};
|
|
||||||
|
|
||||||
export { checkPerm };
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { checkPerm } from "./directives/permission";
|
|
||||||
import { iconList } from "./common";
|
|
||||||
import "./static/css/index.scss";
|
|
||||||
|
|
||||||
export { iconList, checkPerm };
|
|
||||||
@ -1,224 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-login">
|
|
||||||
<div class="box">
|
|
||||||
<img class="logo" src="../../static/images/logo.png" alt="" />
|
|
||||||
<p class="desc">{{ app.name }}是一款快速开发后台权限管理系统</p>
|
|
||||||
|
|
||||||
<el-form label-position="top" class="form" size="medium" :disabled="saving">
|
|
||||||
<el-form-item label="用户名">
|
|
||||||
<el-input
|
|
||||||
v-model="form.username"
|
|
||||||
placeholder="请输入用户名"
|
|
||||||
maxlength="20"
|
|
||||||
auto-complete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="密码">
|
|
||||||
<el-input
|
|
||||||
v-model="form.password"
|
|
||||||
type="password"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
maxlength="20"
|
|
||||||
auto-complete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="验证码" class="captcha">
|
|
||||||
<el-input
|
|
||||||
v-model="form.verifyCode"
|
|
||||||
placeholder="请输入图片验证码"
|
|
||||||
maxlength="4"
|
|
||||||
auto-complete="off"
|
|
||||||
@keyup.enter="toLogin"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<captcha
|
|
||||||
:ref="setRefs('captcha')"
|
|
||||||
v-model="form.captchaId"
|
|
||||||
class="value"
|
|
||||||
@change="
|
|
||||||
() => {
|
|
||||||
form.verifyCode = '';
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-button round size="small" class="submit-btn" :loading="saving" @click="toLogin"
|
|
||||||
>登录</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive, ref } from "vue";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import Captcha from "./components/captcha.vue";
|
|
||||||
import { useEps, useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
cool: {
|
|
||||||
route: {
|
|
||||||
path: "/login"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
|
||||||
Captcha
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, store, router, app }: any = useCool();
|
|
||||||
|
|
||||||
const saving = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 登录表单数据
|
|
||||||
const form = reactive({
|
|
||||||
username: "",
|
|
||||||
password: "",
|
|
||||||
captchaId: "",
|
|
||||||
verifyCode: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
// 登录
|
|
||||||
async function toLogin() {
|
|
||||||
if (!form.username) {
|
|
||||||
return ElMessage.warning("用户名不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!form.password) {
|
|
||||||
return ElMessage.warning("密码不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!form.verifyCode) {
|
|
||||||
return ElMessage.warning("图片验证码不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
saving.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 登录
|
|
||||||
await store.dispatch("userLogin", form);
|
|
||||||
|
|
||||||
// 用户信息
|
|
||||||
await store.dispatch("userInfo");
|
|
||||||
|
|
||||||
// 读取Eps
|
|
||||||
await useEps();
|
|
||||||
|
|
||||||
// 权限菜单
|
|
||||||
const [first] = await store.dispatch("permMenu");
|
|
||||||
|
|
||||||
if (!first) {
|
|
||||||
ElMessage.error("该账号没有权限");
|
|
||||||
} else {
|
|
||||||
router.push("/");
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
ElMessage.error(err);
|
|
||||||
refs.value.captcha.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
saving.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
form,
|
|
||||||
saving,
|
|
||||||
toLogin,
|
|
||||||
app
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.page-login {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
position: relative;
|
|
||||||
background-color: #2f3447;
|
|
||||||
|
|
||||||
.box {
|
|
||||||
display: flex;
|
|
||||||
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: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desc {
|
|
||||||
color: #ccc;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form {
|
|
||||||
width: 300px;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
.el-form-item {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-input {
|
|
||||||
.el-input__inner {
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 0.5px solid #999;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0 5px;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #ccc;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #fff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:-webkit-autofill {
|
|
||||||
-webkit-text-fill-color: #fff !important;
|
|
||||||
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
|
|
||||||
transition: background-color 50000s ease-in-out 0s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.captcha {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.value {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1px;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
|
||||||
margin-top: 40px;
|
|
||||||
padding: 9px 40px;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import { BaseService, Service } from "/@/cool";
|
|
||||||
|
|
||||||
@Service("base/comm")
|
|
||||||
class Common extends BaseService {
|
|
||||||
/**
|
|
||||||
* 文件上传模式
|
|
||||||
*/
|
|
||||||
uploadMode() {
|
|
||||||
return this.request({
|
|
||||||
url: "/uploadMode"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件上传,如果模式是 cloud,返回对应参数
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* @memberof CommonService
|
|
||||||
*/
|
|
||||||
upload(params: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/upload",
|
|
||||||
method: "POST",
|
|
||||||
params
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户退出
|
|
||||||
*/
|
|
||||||
userLogout() {
|
|
||||||
return this.request({
|
|
||||||
url: "/logout",
|
|
||||||
method: "POST"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户信息
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* @memberof CommonService
|
|
||||||
*/
|
|
||||||
userInfo() {
|
|
||||||
return this.request({
|
|
||||||
url: "/person"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户信息修改
|
|
||||||
*
|
|
||||||
* @param {*} params
|
|
||||||
* @returns
|
|
||||||
* @memberof CommonService
|
|
||||||
*/
|
|
||||||
userUpdate(params: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/personUpdate",
|
|
||||||
method: "POST",
|
|
||||||
data: {
|
|
||||||
...params
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限信息
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* @memberof CommonService
|
|
||||||
*/
|
|
||||||
permMenu() {
|
|
||||||
return this.request({
|
|
||||||
url: "/permmenu"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据接口
|
|
||||||
*/
|
|
||||||
eps() {
|
|
||||||
return this.request({
|
|
||||||
url: "/eps"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Common;
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import { BaseService, Service } from "/@/cool";
|
|
||||||
|
|
||||||
@Service("base/open")
|
|
||||||
class Open extends BaseService {
|
|
||||||
/**
|
|
||||||
* 用户登录
|
|
||||||
*
|
|
||||||
* @param {*} { username, password, captchaId, verifyCode }
|
|
||||||
* @returns
|
|
||||||
* @memberof CommonService
|
|
||||||
*/
|
|
||||||
userLogin({ username, password, captchaId, verifyCode }: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/login",
|
|
||||||
method: "POST",
|
|
||||||
data: {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
captchaId,
|
|
||||||
verifyCode
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片验证码 svg
|
|
||||||
*
|
|
||||||
* @param {*} { height, width }
|
|
||||||
* @returns
|
|
||||||
* @memberof CommonService
|
|
||||||
*/
|
|
||||||
captcha({ height, width }: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/captcha",
|
|
||||||
params: {
|
|
||||||
height,
|
|
||||||
width
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新 token
|
|
||||||
* @param {string} token
|
|
||||||
*/
|
|
||||||
refreshToken(token: string) {
|
|
||||||
return this.request({
|
|
||||||
url: "/refreshToken",
|
|
||||||
params: {
|
|
||||||
refreshToken: token
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Open;
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
import store from "store";
|
|
||||||
import { deepMerge, getBrowser } from "/@/cool/utils";
|
|
||||||
import { app } from "/@/config/env";
|
|
||||||
import { useEps } from "/@/cool";
|
|
||||||
|
|
||||||
const browser = getBrowser();
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
info: {
|
|
||||||
...app
|
|
||||||
},
|
|
||||||
browser,
|
|
||||||
collapse: browser.isMini ? true : false,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
// 程序加载
|
|
||||||
appLoading: (state: any) => state.loading,
|
|
||||||
// 应用配置
|
|
||||||
app: (state: any) => state.info,
|
|
||||||
// 浏览器信息
|
|
||||||
browser: (state: any) => state.browser,
|
|
||||||
// 左侧菜单是否收起
|
|
||||||
menuCollapse: (state: any) => state.collapse
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
async appLoad({ getters, dispatch, commit }: any) {
|
|
||||||
if (getters.token) {
|
|
||||||
commit("SHOW_LOADING");
|
|
||||||
|
|
||||||
// 读取Eps
|
|
||||||
await useEps();
|
|
||||||
|
|
||||||
// 读取菜单权限
|
|
||||||
await dispatch("permMenu");
|
|
||||||
|
|
||||||
// 获取用户信息
|
|
||||||
dispatch("userInfo");
|
|
||||||
|
|
||||||
commit("HIDE_LOADING");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
SHOW_LOADING(state: any) {
|
|
||||||
state.loading = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
HIDE_LOADING(state: any) {
|
|
||||||
state.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 设置浏览器信息
|
|
||||||
SET_BROWSER(state: any) {
|
|
||||||
state.browser = getBrowser();
|
|
||||||
},
|
|
||||||
|
|
||||||
// 收起左侧菜单
|
|
||||||
COLLAPSE_MENU(state: any, val = false) {
|
|
||||||
state.collapse = val;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 更新应用配置
|
|
||||||
UPDATE_APP(state: any, val: any) {
|
|
||||||
deepMerge(state.info, val);
|
|
||||||
store.set("__app__", state.info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
@ -1,154 +0,0 @@
|
|||||||
import { ElMessage } from "element-plus";
|
|
||||||
import storage from "store";
|
|
||||||
import store from "/@/store";
|
|
||||||
import router from "/@/router";
|
|
||||||
import { deepTree, revDeepTree, isArray, isEmpty } from "/@/cool/utils";
|
|
||||||
import { menuList } from "/@/config/env";
|
|
||||||
import { revisePath } from "../utils";
|
|
||||||
import { MenuItem } from "../types";
|
|
||||||
import { usePermission } from "/@/cool";
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
// 视图路由,type=1
|
|
||||||
routes: storage.get("viewRoutes") || [],
|
|
||||||
// 树形菜单
|
|
||||||
group: storage.get("menuGroup") || [],
|
|
||||||
// showAMenu 模式下,顶级菜单的序号
|
|
||||||
index: 0,
|
|
||||||
// 左侧菜单
|
|
||||||
menu: [],
|
|
||||||
// 权限列表
|
|
||||||
permission: storage.get("permission") || []
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
// 树形菜单列表
|
|
||||||
menuGroup: (state: any) => state.group,
|
|
||||||
// 左侧菜单
|
|
||||||
menuList: (state: any) => state.menu,
|
|
||||||
// 视图路由
|
|
||||||
routes: (state: any) => state.routes,
|
|
||||||
// 权限列表
|
|
||||||
permission: (state: any) => state.permission
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
// 设置菜单、权限
|
|
||||||
permMenu({ commit, state, getters }: any) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const next = (res: any) => {
|
|
||||||
if (!isArray(res.menus)) {
|
|
||||||
res.menus = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isArray(res.perms)) {
|
|
||||||
res.perms = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const routes = res.menus
|
|
||||||
.filter((e: MenuItem) => e.type != 2)
|
|
||||||
.map((e: MenuItem) => {
|
|
||||||
return {
|
|
||||||
id: e.id,
|
|
||||||
parentId: e.parentId,
|
|
||||||
path: revisePath(e.router || String(e.id)),
|
|
||||||
viewPath: e.viewPath,
|
|
||||||
type: e.type,
|
|
||||||
name: e.name,
|
|
||||||
icon: e.icon,
|
|
||||||
orderNum: e.orderNum,
|
|
||||||
isShow: isEmpty(e.isShow) ? true : e.isShow,
|
|
||||||
meta: {
|
|
||||||
label: e.name,
|
|
||||||
keepAlive: e.keepAlive
|
|
||||||
},
|
|
||||||
children: []
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 转成树形菜单
|
|
||||||
const menuGroup = deepTree(routes);
|
|
||||||
|
|
||||||
// 设置权限
|
|
||||||
commit("SET_PERMIESSION", res.perms);
|
|
||||||
// 设置菜单组
|
|
||||||
commit("SET_MENU_GROUP", menuGroup);
|
|
||||||
// 设置视图路由
|
|
||||||
commit(
|
|
||||||
"SET_VIEW_ROUTES",
|
|
||||||
routes.filter((e: MenuItem) => e.type == 1)
|
|
||||||
);
|
|
||||||
// 设置菜单
|
|
||||||
commit("SET_MENU_LIST", state.index);
|
|
||||||
|
|
||||||
resolve(menuGroup);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监测自定义菜单
|
|
||||||
if (!getters.app.conf.customMenu) {
|
|
||||||
store.service.base.common
|
|
||||||
.permMenu()
|
|
||||||
.then((res: any) => {
|
|
||||||
next(res);
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error("菜单加载异常");
|
|
||||||
console.error(err);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next({
|
|
||||||
menus: revDeepTree(menuList)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
// 设置树形菜单列表
|
|
||||||
SET_MENU_GROUP(state: any, list: MenuItem[]) {
|
|
||||||
state.group = list;
|
|
||||||
storage.set("menuGroup", list);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 设置视图路由
|
|
||||||
SET_VIEW_ROUTES(state: any, list: MenuItem[]) {
|
|
||||||
router.$plugin?.addViews(list);
|
|
||||||
|
|
||||||
state.routes = list;
|
|
||||||
storage.set("viewRoutes", list);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 设置左侧菜单
|
|
||||||
SET_MENU_LIST(state: any, index: number) {
|
|
||||||
const { showAMenu } = store.getters.app.conf;
|
|
||||||
|
|
||||||
if (isEmpty(index)) {
|
|
||||||
index = state.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showAMenu) {
|
|
||||||
const { children = [] } = state.group[index] || {};
|
|
||||||
|
|
||||||
state.index = index;
|
|
||||||
state.menu = children;
|
|
||||||
} else {
|
|
||||||
state.menu = state.group;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 设置权限
|
|
||||||
SET_PERMIESSION(state: any, list: Array<any>) {
|
|
||||||
state.permission = list;
|
|
||||||
storage.set("permission", list);
|
|
||||||
usePermission(list);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
const state = {
|
|
||||||
info: {},
|
|
||||||
list: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
// 模块信息
|
|
||||||
modules: (state: any) => state.info,
|
|
||||||
// 模块列表
|
|
||||||
moduleList: (state: any) => state.list
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
SET_MODULE(state: any, list: Array<any>) {
|
|
||||||
const d: any = {};
|
|
||||||
|
|
||||||
list.forEach((e: any) => {
|
|
||||||
d[e.name] = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
state.list = list;
|
|
||||||
state.info = d;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
const fMenu = {
|
|
||||||
label: "首页",
|
|
||||||
value: "/",
|
|
||||||
active: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
list: [fMenu]
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
// 页面进程列表
|
|
||||||
processList: (state: any) => state.list
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
ADD_PROCESS(state: any, item: any) {
|
|
||||||
const index = state.list.findIndex(
|
|
||||||
(e: any) => e.value.split("?")[0] === item.value.split("?")[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
state.list.map((e: any) => {
|
|
||||||
e.active = e.value == item.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
if (item.value == "/") {
|
|
||||||
item.label = fMenu.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.label) {
|
|
||||||
state.list.push({
|
|
||||||
...item,
|
|
||||||
active: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.list[index].active = true;
|
|
||||||
state.list[index].label = item.label;
|
|
||||||
state.list[index].value = item.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
DEL_PROCESS(state: any, index: number) {
|
|
||||||
if (index != 0) {
|
|
||||||
state.list.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
SET_PROCESS(state: any, list: Array<any>) {
|
|
||||||
state.list = list;
|
|
||||||
},
|
|
||||||
|
|
||||||
RESET_PROCESS(state: any) {
|
|
||||||
state.list = [fMenu];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import { storage, href } from "/@/cool/utils";
|
|
||||||
import store from "/@/store";
|
|
||||||
import { Token } from "../types";
|
|
||||||
|
|
||||||
const state: any = {
|
|
||||||
// 授权标识
|
|
||||||
token: storage.get("token") || null,
|
|
||||||
// 用户信息
|
|
||||||
info: storage.get("userInfo") || {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
userInfo: (state: any) => state.info,
|
|
||||||
token: (state: any) => state.token
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
// 用户登录
|
|
||||||
userLogin({ commit }: any, form: any): Promise<any> {
|
|
||||||
return store.service.base.open.userLogin(form).then((res: Token) => {
|
|
||||||
commit("SET_TOKEN", res);
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 用户退出
|
|
||||||
async userLogout({ dispatch }: any): Promise<any> {
|
|
||||||
await store.service.base.common.userLogout();
|
|
||||||
return dispatch("userRemove");
|
|
||||||
},
|
|
||||||
|
|
||||||
// 用户信息
|
|
||||||
userInfo({ commit }: any): Promise<any> {
|
|
||||||
return store.service.base.common.userInfo().then((res: any) => {
|
|
||||||
commit("SET_USERINFO", res);
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 用户移除
|
|
||||||
userRemove({ commit }: any) {
|
|
||||||
commit("CLEAR_USER");
|
|
||||||
commit("CLEAR_TOKEN");
|
|
||||||
commit("RESET_PROCESS");
|
|
||||||
commit("SET_MENU_GROUP", []);
|
|
||||||
commit("SET_VIEW_ROUTES", []);
|
|
||||||
commit("SET_MENU_LIST", 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 刷新token
|
|
||||||
refreshToken({ commit, dispatch }: any) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
store.service.base.open
|
|
||||||
.refreshToken(storage.get("refreshToken"))
|
|
||||||
.then((res: any) => {
|
|
||||||
commit("SET_TOKEN", res);
|
|
||||||
resolve(res.token);
|
|
||||||
})
|
|
||||||
.catch((err: Error) => {
|
|
||||||
dispatch("userRemove");
|
|
||||||
href("/login");
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
// 设置用户信息
|
|
||||||
SET_USERINFO(state: any, val: any) {
|
|
||||||
state.info = val;
|
|
||||||
storage.set("userInfo", val);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 设置授权标识
|
|
||||||
SET_TOKEN(state: any, { token, expire, refreshToken, refreshExpire }: Token) {
|
|
||||||
// 请求的唯一标识
|
|
||||||
state.token = token;
|
|
||||||
storage.set("token", token, expire);
|
|
||||||
|
|
||||||
// 刷新 token 的唯一标识
|
|
||||||
storage.set("refreshToken", refreshToken, refreshExpire);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 移除授权标识
|
|
||||||
CLEAR_TOKEN(state: any) {
|
|
||||||
state.token = null;
|
|
||||||
storage.remove("token");
|
|
||||||
storage.remove("refreshToken");
|
|
||||||
},
|
|
||||||
|
|
||||||
// 移除用户信息
|
|
||||||
CLEAR_USER(state: any) {
|
|
||||||
state.info = {};
|
|
||||||
storage.remove("userInfo");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
31
src/cool/modules/base/types/index.d.ts
vendored
@ -1,31 +0,0 @@
|
|||||||
export declare interface Token {
|
|
||||||
expire: number;
|
|
||||||
refreshExpire: number;
|
|
||||||
refreshToken: string;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare enum MenuType {
|
|
||||||
"目录" = 0,
|
|
||||||
"菜单" = 1,
|
|
||||||
"权限" = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface MenuItem {
|
|
||||||
id: number;
|
|
||||||
parentId: number;
|
|
||||||
path: string;
|
|
||||||
router?: string;
|
|
||||||
viewPath?: string;
|
|
||||||
type: MenuType;
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
orderNum: number;
|
|
||||||
isShow: number;
|
|
||||||
keepAlive?: number;
|
|
||||||
meta?: {
|
|
||||||
label: string;
|
|
||||||
keepAlive: number;
|
|
||||||
};
|
|
||||||
children?: MenuItem[];
|
|
||||||
}
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
<template>
|
|
||||||
<cl-crud :ref="setRefs('crud')" @load="onLoad">
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-refresh-btn />
|
|
||||||
|
|
||||||
<el-button
|
|
||||||
v-permission="service.base.sys.log.permission.clear"
|
|
||||||
size="mini"
|
|
||||||
type="danger"
|
|
||||||
@click="clear"
|
|
||||||
>
|
|
||||||
清空
|
|
||||||
</el-button>
|
|
||||||
|
|
||||||
<cl-filter label="日志保存天数">
|
|
||||||
<el-input-number
|
|
||||||
v-model="day"
|
|
||||||
controls-position="right"
|
|
||||||
size="mini"
|
|
||||||
:max="10000"
|
|
||||||
:min="1"
|
|
||||||
@blur="saveDay"
|
|
||||||
/>
|
|
||||||
</cl-filter>
|
|
||||||
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-search-key placeholder="请输入请求地址, 参数,ip地址" />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<cl-table v-bind="table" />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-pagination />
|
|
||||||
</el-row>
|
|
||||||
</cl-crud>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive, ref } from "vue";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { CrudLoad, Table } from "@cool-vue/crud/types";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "sys-log",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, service }: any = useCool();
|
|
||||||
|
|
||||||
// 天数
|
|
||||||
const day = ref<number>(1);
|
|
||||||
|
|
||||||
// cl-table 配置
|
|
||||||
const table = reactive<Table>({
|
|
||||||
"context-menu": ["refresh"],
|
|
||||||
props: {
|
|
||||||
"default-sort": {
|
|
||||||
prop: "createTime",
|
|
||||||
order: "descending"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
type: "index",
|
|
||||||
label: "#",
|
|
||||||
width: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "userId",
|
|
||||||
label: "用户ID"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "昵称",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "action",
|
|
||||||
label: "请求地址",
|
|
||||||
minWidth: 200,
|
|
||||||
showOverflowTooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "params",
|
|
||||||
label: "参数",
|
|
||||||
align: "center",
|
|
||||||
minWidth: 200,
|
|
||||||
showOverflowTooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "ip",
|
|
||||||
label: "ip",
|
|
||||||
minWidth: 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "ipAddr",
|
|
||||||
label: "ip地址",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "createTime",
|
|
||||||
label: "创建时间",
|
|
||||||
minWidth: 150,
|
|
||||||
sortable: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// crud 加载
|
|
||||||
function onLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(service.base.sys.log).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存天数
|
|
||||||
function saveDay() {
|
|
||||||
service.base.sys.log.setKeep({ value: day.value }).then(() => {
|
|
||||||
ElMessage.success("保存成功");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空日志
|
|
||||||
function clear() {
|
|
||||||
ElMessageBox.confirm("是否要清空日志", "提示", {
|
|
||||||
type: "warning"
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
service.base.sys.log
|
|
||||||
.clear()
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success("清空成功");
|
|
||||||
refs.value.crud.refresh();
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取天数
|
|
||||||
service.base.sys.log.getKeep().then((res: number) => {
|
|
||||||
day.value = Number(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
service,
|
|
||||||
refs,
|
|
||||||
day,
|
|
||||||
table,
|
|
||||||
setRefs,
|
|
||||||
onLoad,
|
|
||||||
saveDay,
|
|
||||||
clear
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,398 +0,0 @@
|
|||||||
<template>
|
|
||||||
<cl-crud :ref="setRefs('crud')" :on-refresh="onRefresh" @load="onLoad">
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-refresh-btn />
|
|
||||||
<cl-add-btn />
|
|
||||||
<cl-menu-quick @success="refresh()" v-if="isDev" />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<cl-table :ref="setRefs('table')" v-bind="table" @row-click="onRowClick">
|
|
||||||
<!-- 名称 -->
|
|
||||||
<template #column-name="{ scope }">
|
|
||||||
<span>{{ scope.row.name }}</span>
|
|
||||||
<el-tag
|
|
||||||
v-if="!scope.row.isShow"
|
|
||||||
size="mini"
|
|
||||||
effect="dark"
|
|
||||||
type="danger"
|
|
||||||
style="margin-left: 10px"
|
|
||||||
>隐藏</el-tag
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 图标 -->
|
|
||||||
<template #column-icon="{ scope }">
|
|
||||||
<icon-svg :name="scope.row.icon" size="16px" style="margin-top: 5px" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 权限 -->
|
|
||||||
<template #column-perms="{ scope }">
|
|
||||||
<el-tag
|
|
||||||
v-for="(item, index) in scope.row.permList"
|
|
||||||
:key="index"
|
|
||||||
size="mini"
|
|
||||||
effect="dark"
|
|
||||||
style="margin: 2px; letter-spacing: 0.5px"
|
|
||||||
>{{ item }}</el-tag
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 路由 -->
|
|
||||||
<template #column-router="{ scope }">
|
|
||||||
<el-link v-if="scope.row.type == 1" type="primary" :href="scope.row.router">{{
|
|
||||||
scope.row.router
|
|
||||||
}}</el-link>
|
|
||||||
<span v-else>{{ scope.row.router }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 路由缓存 -->
|
|
||||||
<template #column-keepAlive="{ scope }">
|
|
||||||
<template v-if="scope.row.type == 1">
|
|
||||||
<i v-if="scope.row.keepAlive" class="el-icon-check"></i>
|
|
||||||
<i v-else class="el-icon-close"></i>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 行新增 -->
|
|
||||||
<template #slot-add="{ scope }">
|
|
||||||
<el-button
|
|
||||||
v-if="scope.row.type != 2"
|
|
||||||
type="text"
|
|
||||||
size="mini"
|
|
||||||
@click="upsertAppend(scope.row)"
|
|
||||||
>新增</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</cl-table>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-pagination :props="{ layout: 'total' }" />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 编辑 -->
|
|
||||||
<cl-upsert v-bind="upsert" />
|
|
||||||
</cl-crud>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { deepTree } from "/@/cool/utils";
|
|
||||||
import { defineComponent, reactive } from "vue";
|
|
||||||
import { CrudLoad, Table, Upsert, RefreshOp } from "@cool-vue/crud/types";
|
|
||||||
import { isDev } from "/@/config/env";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "sys-menu",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, service, router } = useCool();
|
|
||||||
|
|
||||||
// crud 加载
|
|
||||||
function onLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(service.base.sys.menu).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新监听
|
|
||||||
function onRefresh(_: any, { render }: RefreshOp) {
|
|
||||||
service.base.sys.menu.list().then((list: any[]) => {
|
|
||||||
list.map((e) => {
|
|
||||||
e.permList = e.perms ? e.perms.split(",") : [];
|
|
||||||
});
|
|
||||||
|
|
||||||
render(deepTree(list), {
|
|
||||||
total: list.length
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 行点击展开
|
|
||||||
function onRowClick(row: any, column: any) {
|
|
||||||
if (column.property && row.children) {
|
|
||||||
refs.value.table.toggleRowExpansion(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 子集新增
|
|
||||||
function upsertAppend({ type, id }: any) {
|
|
||||||
refs.value.crud.rowAppend({
|
|
||||||
parentId: id,
|
|
||||||
type: type + 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置权限
|
|
||||||
function setPermission({ id }: any) {
|
|
||||||
refs.value.crud.rowAppend({
|
|
||||||
parentId: id,
|
|
||||||
type: 2
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳转
|
|
||||||
function toUrl(url: string) {
|
|
||||||
router.push(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新
|
|
||||||
function refresh() {
|
|
||||||
refs.value.crud.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
const table = reactive<Table>({
|
|
||||||
props: {
|
|
||||||
"row-key": "id"
|
|
||||||
},
|
|
||||||
"context-menu": [
|
|
||||||
(row: any) => {
|
|
||||||
return {
|
|
||||||
label: "新增",
|
|
||||||
hidden: row.type == 2,
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
upsertAppend(row);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
"update",
|
|
||||||
"delete",
|
|
||||||
(row: any) => {
|
|
||||||
return {
|
|
||||||
label: "权限",
|
|
||||||
hidden: row.type != 1,
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
setPermission(row);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
],
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "名称",
|
|
||||||
align: "left",
|
|
||||||
width: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "icon",
|
|
||||||
label: "图标",
|
|
||||||
width: 80
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "type",
|
|
||||||
label: "类型",
|
|
||||||
width: 100,
|
|
||||||
dict: [
|
|
||||||
{
|
|
||||||
label: "目录",
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "菜单",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "权限",
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "router",
|
|
||||||
label: "节点路由",
|
|
||||||
minWidth: 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "keepAlive",
|
|
||||||
label: "路由缓存",
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "viewPath",
|
|
||||||
label: "文件路径",
|
|
||||||
minWidth: 200,
|
|
||||||
showOverflowTooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "perms",
|
|
||||||
label: "权限",
|
|
||||||
headerAlign: "center",
|
|
||||||
minWidth: 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "orderNum",
|
|
||||||
label: "排序号",
|
|
||||||
width: 90
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "updateTime",
|
|
||||||
label: "更新时间",
|
|
||||||
sortable: "custom",
|
|
||||||
width: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "操作",
|
|
||||||
type: "op",
|
|
||||||
buttons: ["slot-add", "edit", "delete"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// 新增、编辑配置
|
|
||||||
const upsert = reactive<Upsert>({
|
|
||||||
dialog: {
|
|
||||||
width: "800px"
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
prop: "type",
|
|
||||||
value: 0,
|
|
||||||
label: "节点类型",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "el-radio-group",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "目录",
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "菜单",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "权限",
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "节点名称",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请输入节点名称"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "parentId",
|
|
||||||
label: "上级节点",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "cl-menu-tree"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "router",
|
|
||||||
label: "节点路由",
|
|
||||||
span: 24,
|
|
||||||
hidden: ({ scope }: any) => scope.type != 1,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请输入节点路由,如:/test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "keepAlive",
|
|
||||||
value: true,
|
|
||||||
label: "路由缓存",
|
|
||||||
span: 24,
|
|
||||||
hidden: ({ scope }: any) => scope.type != 1,
|
|
||||||
component: {
|
|
||||||
name: "el-radio-group",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "开启",
|
|
||||||
value: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "关闭",
|
|
||||||
value: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "isShow",
|
|
||||||
label: "是否显示",
|
|
||||||
span: 24,
|
|
||||||
value: true,
|
|
||||||
hidden: ({ scope }: any) => scope.type == 2,
|
|
||||||
flex: false,
|
|
||||||
component: {
|
|
||||||
name: "el-switch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "viewPath",
|
|
||||||
label: "文件路径",
|
|
||||||
span: 24,
|
|
||||||
hidden: ({ scope }: any) => scope.type != 1,
|
|
||||||
component: {
|
|
||||||
name: "cl-menu-file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "icon",
|
|
||||||
label: "节点图标",
|
|
||||||
span: 24,
|
|
||||||
hidden: ({ scope }: any) => scope.type == 2,
|
|
||||||
component: {
|
|
||||||
name: "cl-menu-icons"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "orderNum",
|
|
||||||
label: "排序号",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "el-input-number",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写排序号",
|
|
||||||
min: 0,
|
|
||||||
max: 99,
|
|
||||||
"controls-position": "right"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "perms",
|
|
||||||
label: "权限",
|
|
||||||
span: 24,
|
|
||||||
hidden: ({ scope }: any) => scope.type != 2,
|
|
||||||
component: {
|
|
||||||
name: "cl-menu-perms"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
table,
|
|
||||||
upsert,
|
|
||||||
setRefs,
|
|
||||||
onLoad,
|
|
||||||
onRefresh,
|
|
||||||
onRowClick,
|
|
||||||
upsertAppend,
|
|
||||||
setPermission,
|
|
||||||
toUrl,
|
|
||||||
refresh,
|
|
||||||
isDev
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,218 +0,0 @@
|
|||||||
<template>
|
|
||||||
<cl-crud @load="onLoad">
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-refresh-btn />
|
|
||||||
<cl-add-btn />
|
|
||||||
<cl-multi-delete-btn />
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-search-key />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<cl-table v-bind="table" />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-pagination />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<cl-upsert :ref="setRefs('upsert')" v-bind="upsert" @opened="onUpsertOpen">
|
|
||||||
<template #slot-content="{ scope }">
|
|
||||||
<div v-for="(item, index) in tab.list" :key="index" class="editor">
|
|
||||||
<template v-if="tab.index == index">
|
|
||||||
<el-button class="change-btn" size="mini" @click="changeTab(item.to)">{{
|
|
||||||
item.label
|
|
||||||
}}</el-button>
|
|
||||||
|
|
||||||
<component :is="item.component" v-model="scope.data" height="300px" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</cl-upsert>
|
|
||||||
</cl-crud>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ElMessageBox } from "element-plus";
|
|
||||||
import { defineComponent, nextTick, reactive } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { CrudLoad, Table, Upsert } from "@cool-vue/crud/types";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "sys-param",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, service } = useCool();
|
|
||||||
|
|
||||||
// 选项卡
|
|
||||||
const tab = reactive<any>({
|
|
||||||
index: null,
|
|
||||||
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "切换富文本编辑器",
|
|
||||||
to: 1,
|
|
||||||
component: "cl-codemirror"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "切换代码编辑器",
|
|
||||||
to: 0,
|
|
||||||
component: "cl-editor-quill"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
const table = reactive<Table>({
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
type: "selection",
|
|
||||||
width: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "名称",
|
|
||||||
prop: "name",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "keyName",
|
|
||||||
prop: "keyName",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "数据",
|
|
||||||
prop: "data",
|
|
||||||
minWidth: 150,
|
|
||||||
showOverflowTooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "备注",
|
|
||||||
prop: "remark",
|
|
||||||
minWidth: 200,
|
|
||||||
showOverflowTooltip: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "操作",
|
|
||||||
type: "op"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// 新增编辑配置
|
|
||||||
const upsert = reactive<Upsert>({
|
|
||||||
dialog: {
|
|
||||||
width: "1000px"
|
|
||||||
},
|
|
||||||
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "名称",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请输入名称"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "名称不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "keyName",
|
|
||||||
label: "keyName",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请输入Key"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "Key不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "data",
|
|
||||||
label: "数据",
|
|
||||||
component: {
|
|
||||||
name: "slot-content"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "remark",
|
|
||||||
label: "备注",
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请输入备注",
|
|
||||||
rows: 3,
|
|
||||||
type: "textarea"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// crud 加载
|
|
||||||
function onLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(service.base.sys.param).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换编辑器
|
|
||||||
function changeTab(i: number) {
|
|
||||||
ElMessageBox.confirm("切换编辑器会清空输入内容,是否继续?", "提示", {
|
|
||||||
type: "warning"
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
tab.index = i;
|
|
||||||
refs.value.upsert.setForm("data", "");
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听打开
|
|
||||||
function onUpsertOpen(isEdit: boolean, data: any) {
|
|
||||||
tab.index = null;
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
if (isEdit) {
|
|
||||||
tab.index = /<*>/g.test(data.data) ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
tab.index = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
tab,
|
|
||||||
table,
|
|
||||||
upsert,
|
|
||||||
setRefs,
|
|
||||||
onLoad,
|
|
||||||
changeTab,
|
|
||||||
onUpsertOpen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.change-btn {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
bottom: 10px;
|
|
||||||
z-index: 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor {
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,263 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="view-plugin">
|
|
||||||
<cl-crud ref="crud" :on-refresh="onRefresh" @load="onLoad">
|
|
||||||
<el-row type="flex" align="middle">
|
|
||||||
<!-- 刷新按钮 -->
|
|
||||||
<cl-refresh-btn />
|
|
||||||
<cl-flex1 />
|
|
||||||
<!-- 关键字搜索 -->
|
|
||||||
<cl-search-key />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<!-- 数据表格 -->
|
|
||||||
<cl-table v-bind="table">
|
|
||||||
<template #column-enable="{ scope }">
|
|
||||||
<el-switch
|
|
||||||
v-model="scope.row._enable"
|
|
||||||
size="mini"
|
|
||||||
:disabled="!perms.enable"
|
|
||||||
@change="onEnableChange($event, scope.row)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 配置按钮 -->
|
|
||||||
<template #slot-conf="{ scope }">
|
|
||||||
<el-button
|
|
||||||
v-if="scope.row.view && perms.edit"
|
|
||||||
type="text"
|
|
||||||
size="mini"
|
|
||||||
@click="openConf(scope.row)"
|
|
||||||
>配置</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</cl-table>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-flex1 />
|
|
||||||
<!-- 分页控件 -->
|
|
||||||
<cl-pagination layout="total" />
|
|
||||||
</el-row>
|
|
||||||
</cl-crud>
|
|
||||||
|
|
||||||
<!-- 表单 -->
|
|
||||||
<cl-form :ref="setRefs('form')" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { defineComponent, reactive } from "vue";
|
|
||||||
import { checkPerm } from "/$/base";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { CrudLoad, RefreshOp, Table } from "@cool-vue/crud/types";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "plugin",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, service } = useCool();
|
|
||||||
|
|
||||||
// 编辑权限
|
|
||||||
const { config, getConfig, enable } = service.base.plugin.info.permission;
|
|
||||||
|
|
||||||
const perms = reactive<any>({
|
|
||||||
edit: checkPerm({
|
|
||||||
and: [config, getConfig]
|
|
||||||
}),
|
|
||||||
enable: checkPerm(enable)
|
|
||||||
});
|
|
||||||
|
|
||||||
// crud 加载
|
|
||||||
function onLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(service.base.plugin.info)
|
|
||||||
.set("dict", {
|
|
||||||
api: {
|
|
||||||
page: "list"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新钩子
|
|
||||||
function onRefresh(params: any, { next, render }: RefreshOp) {
|
|
||||||
next(params).then((res: any) => {
|
|
||||||
const list = res.map((e: any) => {
|
|
||||||
e._enable = e.enable ? true : false;
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
|
|
||||||
render(list, {
|
|
||||||
total: res.length
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开启、关闭
|
|
||||||
function onEnableChange(val: boolean, item: any) {
|
|
||||||
service.base.plugin.info
|
|
||||||
.enable({
|
|
||||||
namespace: item.namespace,
|
|
||||||
enable: val
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success(val ? "开启成功" : "关闭成功");
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开配置
|
|
||||||
async function openConf({ name, namespace, view }: any) {
|
|
||||||
const form = await service.base.plugin.info.getConfig({
|
|
||||||
namespace
|
|
||||||
});
|
|
||||||
|
|
||||||
let items = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
items = JSON.parse(view);
|
|
||||||
} catch (e) {
|
|
||||||
items = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
refs.value.form.open({
|
|
||||||
title: `${name}配置`,
|
|
||||||
items,
|
|
||||||
form,
|
|
||||||
on: {
|
|
||||||
submit: (data: any, { close, done }: any) => {
|
|
||||||
service.base.plugin.info
|
|
||||||
.config({
|
|
||||||
namespace,
|
|
||||||
config: data
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success("保存成功");
|
|
||||||
close();
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
const table = reactive<Table>({
|
|
||||||
props: {
|
|
||||||
"default-sort": {
|
|
||||||
prop: "createTime",
|
|
||||||
order: "descending"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"context-menu": [
|
|
||||||
"refresh",
|
|
||||||
(scope: any) => {
|
|
||||||
return {
|
|
||||||
label: "配置",
|
|
||||||
hidden: !perms.edit || !scope.view,
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
openConf(scope);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
],
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
label: "名称",
|
|
||||||
prop: "name",
|
|
||||||
minWidth: 140
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "作者",
|
|
||||||
prop: "author",
|
|
||||||
minWidth: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "联系方式",
|
|
||||||
prop: "contact",
|
|
||||||
showOverflowTooltip: true,
|
|
||||||
minWidth: 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "功能描述",
|
|
||||||
prop: "description",
|
|
||||||
showOverflowTooltip: true,
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "版本号",
|
|
||||||
prop: "version",
|
|
||||||
minWidth: 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "是否启用",
|
|
||||||
prop: "enable",
|
|
||||||
minWidth: 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "命名空间",
|
|
||||||
prop: "namespace",
|
|
||||||
minWidth: 110
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
width: 150,
|
|
||||||
dict: [
|
|
||||||
{
|
|
||||||
label: "缺少配置",
|
|
||||||
value: 0,
|
|
||||||
type: "warning"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "可用",
|
|
||||||
value: 1,
|
|
||||||
type: "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "配置错误",
|
|
||||||
value: 2,
|
|
||||||
type: "danger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "未知错误",
|
|
||||||
value: 3,
|
|
||||||
type: "danger"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "创建时间",
|
|
||||||
prop: "createTime",
|
|
||||||
width: 150,
|
|
||||||
sortable: "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "op",
|
|
||||||
width: 120,
|
|
||||||
buttons: ["slot-conf"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
perms,
|
|
||||||
table,
|
|
||||||
setRefs,
|
|
||||||
onLoad,
|
|
||||||
onRefresh,
|
|
||||||
onEnableChange,
|
|
||||||
openConf
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
<template>
|
|
||||||
<cl-crud @load="onLoad">
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-refresh-btn />
|
|
||||||
<cl-add-btn />
|
|
||||||
<cl-multi-delete-btn />
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-search-key />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<cl-table v-bind="table" />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-pagination />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<cl-upsert v-model="form" v-bind="upsert" />
|
|
||||||
</cl-crud>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { CrudLoad, Table, Upsert } from "@cool-vue/crud/types";
|
|
||||||
import { defineComponent, reactive } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "sys-role",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { service } = useCool();
|
|
||||||
|
|
||||||
// 表单值
|
|
||||||
const form = reactive<any>({
|
|
||||||
relevance: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
// 新增、编辑配置
|
|
||||||
const upsert = reactive<Upsert>({
|
|
||||||
dialog: {
|
|
||||||
width: "800px"
|
|
||||||
},
|
|
||||||
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "名称",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写名称"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "名称不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "label",
|
|
||||||
label: "标识",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写标识"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "标识不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "remark",
|
|
||||||
label: "备注",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写备注",
|
|
||||||
type: "textarea",
|
|
||||||
rows: 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "功能权限",
|
|
||||||
prop: "menuIdList",
|
|
||||||
value: [],
|
|
||||||
component: {
|
|
||||||
name: "cl-role-perms"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "数据权限",
|
|
||||||
prop: "departmentIdList",
|
|
||||||
value: [],
|
|
||||||
component: {
|
|
||||||
name: "cl-dept-check"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
const table = reactive<Table>({
|
|
||||||
props: {
|
|
||||||
"default-sort": {
|
|
||||||
prop: "createTime",
|
|
||||||
order: "descending"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
type: "selection",
|
|
||||||
width: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "名称",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "label",
|
|
||||||
label: "标识",
|
|
||||||
minWidth: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "remark",
|
|
||||||
label: "备注",
|
|
||||||
showOverflowTooltip: true,
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "createTime",
|
|
||||||
label: "创建时间",
|
|
||||||
sortable: "custom",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "updateTime",
|
|
||||||
label: "更新时间",
|
|
||||||
sortable: "custom",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "操作",
|
|
||||||
type: "op"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// crud 加载
|
|
||||||
function onLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(service.base.sys.role).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
form,
|
|
||||||
upsert,
|
|
||||||
table,
|
|
||||||
onLoad
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,576 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="system-user">
|
|
||||||
<div class="pane">
|
|
||||||
<!-- 组织架构 -->
|
|
||||||
<div class="dept" :class="[isExpand ? '_expand' : '_collapse']">
|
|
||||||
<cl-dept-tree
|
|
||||||
@row-click="onDeptRowClick"
|
|
||||||
@user-add="onDeptUserAdd"
|
|
||||||
@list-change="onDeptListChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 成员列表 -->
|
|
||||||
<div class="user">
|
|
||||||
<div class="header">
|
|
||||||
<div class="icon" @click="deptExpand">
|
|
||||||
<i class="el-icon-arrow-left" v-if="isExpand"></i>
|
|
||||||
<i class="el-icon-arrow-right" v-else></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span>成员列表</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<cl-crud :ref="setRefs('crud')" :on-refresh="onRefresh" @load="onLoad">
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-refresh-btn />
|
|
||||||
<cl-add-btn />
|
|
||||||
<cl-multi-delete-btn />
|
|
||||||
<el-button
|
|
||||||
v-permission="service.base.sys.user.permission.move"
|
|
||||||
size="mini"
|
|
||||||
type="success"
|
|
||||||
:disabled="selects.ids.length == 0"
|
|
||||||
@click="toMove()"
|
|
||||||
>转移</el-button
|
|
||||||
>
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-search-key />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row>
|
|
||||||
<cl-table
|
|
||||||
:ref="setRefs('table')"
|
|
||||||
v-bind="table"
|
|
||||||
@selection-change="onSelectionChange"
|
|
||||||
>
|
|
||||||
<!-- 头像 -->
|
|
||||||
<template #column-headImg="{ scope }">
|
|
||||||
<cl-avatar
|
|
||||||
shape="square"
|
|
||||||
size="medium"
|
|
||||||
:src="scope.row.headImg"
|
|
||||||
:style="{ margin: 'auto' }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 权限 -->
|
|
||||||
<template #column-roleName="{ scope }">
|
|
||||||
<el-tag
|
|
||||||
v-for="(item, index) in scope.row.roleNameList"
|
|
||||||
:key="index"
|
|
||||||
disable-transitions
|
|
||||||
size="small"
|
|
||||||
effect="dark"
|
|
||||||
style="margin: 2px"
|
|
||||||
>{{ item }}</el-tag
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 单个转移 -->
|
|
||||||
<template #slot-move-btn="{ scope }">
|
|
||||||
<el-button
|
|
||||||
v-permission="service.base.sys.user.permission.move"
|
|
||||||
type="text"
|
|
||||||
size="mini"
|
|
||||||
@click="toMove(scope.row)"
|
|
||||||
>转移</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</cl-table>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex">
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-pagination />
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<cl-upsert
|
|
||||||
:ref="setRefs('upsert')"
|
|
||||||
v-bind="upsert"
|
|
||||||
:on-submit="onUpsertSubmit"
|
|
||||||
/>
|
|
||||||
</cl-crud>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 部门移动 -->
|
|
||||||
<cl-dept-move :ref="setRefs('dept-move')" @success="refresh({ page: 1 })" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, reactive, ref, watch } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import { Table, Upsert } from "@cool-vue/crud/types";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "sys-user",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, store, service } = useCool();
|
|
||||||
|
|
||||||
// 是否展开
|
|
||||||
const isExpand = ref<boolean>(true);
|
|
||||||
|
|
||||||
// 选择项
|
|
||||||
const selects = reactive<any>({
|
|
||||||
dept: {},
|
|
||||||
ids: []
|
|
||||||
});
|
|
||||||
|
|
||||||
// 部门列表
|
|
||||||
const dept = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 表格配置
|
|
||||||
const table = reactive<Table>({
|
|
||||||
props: {
|
|
||||||
"default-sort": {
|
|
||||||
prop: "createTime",
|
|
||||||
order: "descending"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
type: "selection",
|
|
||||||
width: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "headImg",
|
|
||||||
label: "头像"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "姓名",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "username",
|
|
||||||
label: "用户名",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "nickName",
|
|
||||||
label: "昵称",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "departmentName",
|
|
||||||
label: "部门名称",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "roleName",
|
|
||||||
label: "角色",
|
|
||||||
headerAlign: "center",
|
|
||||||
minWidth: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "phone",
|
|
||||||
label: "手机号码",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "remark",
|
|
||||||
label: "备注",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "status",
|
|
||||||
label: "状态",
|
|
||||||
minWidth: 120,
|
|
||||||
dict: [
|
|
||||||
{
|
|
||||||
label: "启用",
|
|
||||||
value: 1,
|
|
||||||
type: "success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "禁用",
|
|
||||||
value: 0,
|
|
||||||
type: "danger"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "createTime",
|
|
||||||
label: "创建时间",
|
|
||||||
sortable: "custom",
|
|
||||||
minWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "op",
|
|
||||||
buttons: ["slot-move-btn", "edit", "delete"],
|
|
||||||
width: 160
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// 新增、编辑配置
|
|
||||||
const upsert = reactive<Upsert>({
|
|
||||||
dialog: {
|
|
||||||
width: "800px"
|
|
||||||
},
|
|
||||||
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
prop: "headImg",
|
|
||||||
label: "头像",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "cl-upload",
|
|
||||||
props: {
|
|
||||||
text: "选择头像",
|
|
||||||
icon: "el-icon-picture"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "name",
|
|
||||||
label: "姓名",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写姓名"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "姓名不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "nickName",
|
|
||||||
label: "昵称",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写昵称"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "昵称不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "username",
|
|
||||||
label: "用户名",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写用户名"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: "用户名不能为空"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "password",
|
|
||||||
label: "密码",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写密码",
|
|
||||||
type: "password"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
min: 6,
|
|
||||||
max: 16,
|
|
||||||
message: "密码长度在 6 到 16 个字符"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "roleIdList",
|
|
||||||
label: "角色",
|
|
||||||
span: 24,
|
|
||||||
value: [],
|
|
||||||
component: {
|
|
||||||
name: "cl-role-select",
|
|
||||||
props: {
|
|
||||||
props: {
|
|
||||||
"multiple-limit": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "角色不能为空"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "phone",
|
|
||||||
label: "手机号码",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写手机号码"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "email",
|
|
||||||
label: "邮箱",
|
|
||||||
span: 12,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写邮箱"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "remark",
|
|
||||||
label: "备注",
|
|
||||||
span: 24,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
props: {
|
|
||||||
placeholder: "请填写备注",
|
|
||||||
type: "textarea",
|
|
||||||
rows: 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "status",
|
|
||||||
label: "状态",
|
|
||||||
value: 1,
|
|
||||||
component: {
|
|
||||||
name: "el-radio-group",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "开启",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "关闭",
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// 浏览器信息
|
|
||||||
const browser = computed(() => store.getters.browser);
|
|
||||||
|
|
||||||
// 监听屏幕大小变化
|
|
||||||
watch(
|
|
||||||
() => browser.value.isMini,
|
|
||||||
(val: boolean) => {
|
|
||||||
isExpand.value = !val;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// crud 加载
|
|
||||||
function onLoad({ ctx, app }: any) {
|
|
||||||
ctx.service(service.base.sys.user).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新列表
|
|
||||||
function refresh(params: any) {
|
|
||||||
refs.value.crud.refresh(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新监听
|
|
||||||
async function onRefresh(params: any, { next, render }: any) {
|
|
||||||
const { list } = await next(params);
|
|
||||||
|
|
||||||
render(
|
|
||||||
list.map((e: any) => {
|
|
||||||
if (e.roleName) {
|
|
||||||
e.roleNameList = e.roleName.split(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
e.status = Boolean(e.status);
|
|
||||||
|
|
||||||
return e;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交钩子
|
|
||||||
function onUpsertSubmit(_: boolean, data: any, { next }: any) {
|
|
||||||
let departmentId = data.departmentId;
|
|
||||||
|
|
||||||
if (!departmentId) {
|
|
||||||
departmentId = selects.dept.id;
|
|
||||||
|
|
||||||
if (!departmentId) {
|
|
||||||
departmentId = dept.value[0].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next({
|
|
||||||
...data,
|
|
||||||
departmentId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多选监听
|
|
||||||
function onSelectionChange(selection: any[]) {
|
|
||||||
selects.ids = selection.map((e) => e.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 部门选择监听
|
|
||||||
function onDeptRowClick({ item, ids }: any) {
|
|
||||||
selects.dept = item;
|
|
||||||
|
|
||||||
refresh({
|
|
||||||
page: 1,
|
|
||||||
departmentIds: ids
|
|
||||||
});
|
|
||||||
|
|
||||||
// 收起
|
|
||||||
if (browser.value.isMini) {
|
|
||||||
isExpand.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 部门下新增成员
|
|
||||||
function onDeptUserAdd(item: any) {
|
|
||||||
refs.value.crud.rowAppend({
|
|
||||||
departmentId: item.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 部门列表监听
|
|
||||||
function onDeptListChange(list: any[]) {
|
|
||||||
dept.value = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否显示部门
|
|
||||||
function deptExpand() {
|
|
||||||
isExpand.value = !isExpand.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动成员
|
|
||||||
async function toMove(e?: any) {
|
|
||||||
let ids = [];
|
|
||||||
|
|
||||||
if (!e) {
|
|
||||||
ids = selects.ids;
|
|
||||||
} else {
|
|
||||||
ids = [e.id];
|
|
||||||
}
|
|
||||||
|
|
||||||
refs.value["dept-move"].toMove(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
service,
|
|
||||||
refs,
|
|
||||||
isExpand,
|
|
||||||
selects,
|
|
||||||
dept,
|
|
||||||
table,
|
|
||||||
upsert,
|
|
||||||
browser,
|
|
||||||
setRefs,
|
|
||||||
onLoad,
|
|
||||||
refresh,
|
|
||||||
onRefresh,
|
|
||||||
onUpsertSubmit,
|
|
||||||
onSelectionChange,
|
|
||||||
onDeptRowClick,
|
|
||||||
onDeptUserAdd,
|
|
||||||
onDeptListChange,
|
|
||||||
deptExpand,
|
|
||||||
toMove
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.system-user {
|
|
||||||
.pane {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dept {
|
|
||||||
height: 100%;
|
|
||||||
width: 300px;
|
|
||||||
max-width: calc(100% - 50px);
|
|
||||||
background-color: #fff;
|
|
||||||
transition: width 0.3s;
|
|
||||||
margin-right: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&._collapse {
|
|
||||||
margin-right: 0;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
|
||||||
width: calc(100% - 310px);
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 40px;
|
|
||||||
position: relative;
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 14px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #fff;
|
|
||||||
height: 40px;
|
|
||||||
width: 80px;
|
|
||||||
line-height: 40px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dept,
|
|
||||||
.user {
|
|
||||||
overflow: hidden;
|
|
||||||
.container {
|
|
||||||
height: calc(100% - 40px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
|
||||||
.dept {
|
|
||||||
width: calc(100% - 100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,288 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="cl-chat__wrap">
|
|
||||||
<!-- 聊天窗口 -->
|
|
||||||
<cl-dialog
|
|
||||||
v-model="visible"
|
|
||||||
:title="title"
|
|
||||||
:height="height"
|
|
||||||
:width="width"
|
|
||||||
:props="{
|
|
||||||
modal: true,
|
|
||||||
customClass: 'cl-chat__dialog',
|
|
||||||
'append-to-body': true,
|
|
||||||
'close-on-click-modal': false
|
|
||||||
}"
|
|
||||||
:controls="['slot-session', 'cl-flex1', 'fullscreen', 'close']"
|
|
||||||
>
|
|
||||||
<div class="cl-chat">
|
|
||||||
<!-- 会话列表 -->
|
|
||||||
<chat-session />
|
|
||||||
|
|
||||||
<div class="cl-chat__detail">
|
|
||||||
<!-- 消息列表 -->
|
|
||||||
<chat-message />
|
|
||||||
|
|
||||||
<template v-if="session">
|
|
||||||
<!-- 输入框 -->
|
|
||||||
<chat-input />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #slot-session>
|
|
||||||
<button v-if="session">
|
|
||||||
<i v-if="sessionVisible" class="el-icon-notebook-2" @click="closeSession()"></i>
|
|
||||||
<i v-else class="el-icon-arrow-left" @click="openSession()"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</cl-dialog>
|
|
||||||
|
|
||||||
<!-- MP3 -->
|
|
||||||
<div class="mp3">
|
|
||||||
<audio :ref="setRefs('sound')" :src="NotifyMp3" controls style="display: none"></audio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, h, onUnmounted, provide, ref } from "vue";
|
|
||||||
import { ElNotification } from "element-plus";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
// import io from "socket.io-client";
|
|
||||||
// import { socketUrl } from "/@/config/env";
|
|
||||||
import Session from "./session.vue";
|
|
||||||
import Message from "./message.vue";
|
|
||||||
import Input from "./input.vue";
|
|
||||||
import { parseContent } from "../utils";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import NotifyMp3 from "../static/notify.mp3";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-chat",
|
|
||||||
|
|
||||||
components: {
|
|
||||||
"chat-session": Session,
|
|
||||||
"chat-message": Message,
|
|
||||||
"chat-input": Input
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: "650px"
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: "1000px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["message"],
|
|
||||||
|
|
||||||
setup(_, { emit }) {
|
|
||||||
const { refs, setRefs, service, mitt, store } = useCool();
|
|
||||||
|
|
||||||
// 当前会话
|
|
||||||
const session = computed(() => store.getters.session);
|
|
||||||
|
|
||||||
// 会话列表是否可见
|
|
||||||
const sessionVisible = computed(() => store.getters.sessionVisible);
|
|
||||||
|
|
||||||
// 消息类型
|
|
||||||
const modes = ["text", "image", "emoji", "voice", "video"];
|
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
const visible = ref<boolean>(false);
|
|
||||||
|
|
||||||
// socket 实例
|
|
||||||
const socket: any = null;
|
|
||||||
|
|
||||||
// 对话框标题
|
|
||||||
const title = computed(() => {
|
|
||||||
return session.value ? `与 ${session.value.nickname} 聊天中` : "聊天对话框";
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打开
|
|
||||||
function open() {
|
|
||||||
visible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭
|
|
||||||
function close() {
|
|
||||||
visible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开会话列表
|
|
||||||
function openSession() {
|
|
||||||
store.commit("OPEN_SESSION");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭会话列表
|
|
||||||
function closeSession() {
|
|
||||||
store.commit("CLOSE_SESSION");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息通知
|
|
||||||
function notification(msg: string) {
|
|
||||||
const { _text } = parseContent(JSON.parse(msg));
|
|
||||||
|
|
||||||
// 播放音乐
|
|
||||||
if (refs.value.sound) {
|
|
||||||
refs.value.sound.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!visible.value) {
|
|
||||||
// 页面消息提示
|
|
||||||
ElNotification({
|
|
||||||
title: "提示",
|
|
||||||
message: h("span", _text)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 浏览器消息通知
|
|
||||||
const NotificationInstance = Notification || window.Notification;
|
|
||||||
if (NotificationInstance) {
|
|
||||||
if (NotificationInstance.permission !== "denied") {
|
|
||||||
NotificationInstance.requestPermission(() => {
|
|
||||||
const n = new Notification("COOL-MALL", {
|
|
||||||
body: _text,
|
|
||||||
icon: "/favicon.ico"
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
n.close();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听消息
|
|
||||||
function onMessage(msg: string) {
|
|
||||||
// 回调
|
|
||||||
emit("message", msg);
|
|
||||||
|
|
||||||
// 消息通知
|
|
||||||
notification(msg);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { contentType, fromId, content, msgId } = JSON.parse(msg);
|
|
||||||
|
|
||||||
// 是否当前
|
|
||||||
const same = session.value && session.value.userId == fromId;
|
|
||||||
|
|
||||||
if (same) {
|
|
||||||
// 更新消息
|
|
||||||
store.commit("UPDATE_SESSION", {
|
|
||||||
contentType,
|
|
||||||
content
|
|
||||||
});
|
|
||||||
|
|
||||||
// 追加消息
|
|
||||||
store.commit("APPEND_MESSAGE_LIST", {
|
|
||||||
contentType,
|
|
||||||
content: JSON.parse(content),
|
|
||||||
type: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
mitt.emit("message.scrollToBottom");
|
|
||||||
|
|
||||||
// 阅读消息
|
|
||||||
service.chat.message.read({
|
|
||||||
ids: [msgId],
|
|
||||||
session: session.value.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找会话
|
|
||||||
const item = store.getters.sessionList.find((e: any) => e.userId == fromId);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
if (!same) {
|
|
||||||
item.serviceUnreadCount += 1;
|
|
||||||
}
|
|
||||||
// 更新消息
|
|
||||||
Object.assign(item, {
|
|
||||||
updateTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
contentType,
|
|
||||||
content
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 刷新会话列表
|
|
||||||
mitt.emit("session.refresh");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("消息格式异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载 socket
|
|
||||||
(function () {
|
|
||||||
// socket = io(`${socketUrl}?isAdmin=true&token=${store.getters.token}`);
|
|
||||||
// socket.on("connect", () => {
|
|
||||||
// console.log("socket connect");
|
|
||||||
// });
|
|
||||||
// socket.on("admin", msg => {
|
|
||||||
// onMessage(msg);
|
|
||||||
// });
|
|
||||||
// socket.on("error", err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// socket.on("disconnect", () => {
|
|
||||||
// console.log("disconnect connect");
|
|
||||||
// });
|
|
||||||
})();
|
|
||||||
|
|
||||||
// 共享参数
|
|
||||||
provide("chat", {
|
|
||||||
modes,
|
|
||||||
socket
|
|
||||||
});
|
|
||||||
|
|
||||||
// 销毁
|
|
||||||
onUnmounted(function () {
|
|
||||||
if (socket) {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
NotifyMp3,
|
|
||||||
refs,
|
|
||||||
session,
|
|
||||||
sessionVisible,
|
|
||||||
visible,
|
|
||||||
title,
|
|
||||||
setRefs,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
openSession,
|
|
||||||
closeSession,
|
|
||||||
onMessage
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.cl-chat__dialog {
|
|
||||||
.el-dialog__body {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-chat {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
padding: 5px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&__detail {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-popover
|
|
||||||
:visible="visible"
|
|
||||||
:width="popoverWidth"
|
|
||||||
placement="top"
|
|
||||||
trigger="click"
|
|
||||||
popper-class="popper-emoji"
|
|
||||||
>
|
|
||||||
<div class="tool-emoji">
|
|
||||||
<div class="tool-emoji__scroller scroller1">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in list"
|
|
||||||
:key="index"
|
|
||||||
class="tool-emoji__item"
|
|
||||||
@click="select(item)"
|
|
||||||
>
|
|
||||||
<img :src="item" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #reference>
|
|
||||||
<img src="../static/images/emoji.png" alt="" @click="open" />
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, ref } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
// 表情列表
|
|
||||||
const emoji = {
|
|
||||||
url: "https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/",
|
|
||||||
list: [
|
|
||||||
"angry-face.png",
|
|
||||||
"anguished-face.png",
|
|
||||||
"astonished-face.png",
|
|
||||||
"confounded-face.png",
|
|
||||||
"confused-face.png",
|
|
||||||
"crying-face.png",
|
|
||||||
"disappointed-but-relieved-face.png",
|
|
||||||
"disappointed-face.png",
|
|
||||||
"dizzy-face.png",
|
|
||||||
"drooling-face.png",
|
|
||||||
"expressionless-face.png",
|
|
||||||
"face-savouring-delicious-food.png",
|
|
||||||
"face-screaming-in-fear.png",
|
|
||||||
"face-throwing-a-kiss.png",
|
|
||||||
"face-with-cold-sweat.png",
|
|
||||||
"face-with-cowboy-hat.png",
|
|
||||||
"face-with-finger-covering-closed-lips.png",
|
|
||||||
"face-with-head-bandage.png",
|
|
||||||
"face-with-look-of-triumph.png",
|
|
||||||
"face-with-medical-mask.png",
|
|
||||||
"face-with-monocle.png",
|
|
||||||
"face-with-one-eyebrow-raised.png",
|
|
||||||
"face-with-open-mouth-and-cold-sweat.png",
|
|
||||||
"face-with-open-mouth-vomiting.png",
|
|
||||||
"face-with-open-mouth.png",
|
|
||||||
"face-with-party-horn-and-party-hat.png",
|
|
||||||
"face-with-pleading-eyes.png",
|
|
||||||
"face-with-rolling-eyes.png",
|
|
||||||
"face-with-stuck-out-tongue-and-tightly-closed-eyes.png",
|
|
||||||
"face-with-stuck-out-tongue-and-winking-eye.png",
|
|
||||||
"face-with-stuck-out-tongue.png",
|
|
||||||
"face-with-thermometer.png",
|
|
||||||
"face-with-uneven-eyes-and-wavy-mouth.png",
|
|
||||||
"face-without-mouth.png",
|
|
||||||
"fearful-face.png",
|
|
||||||
"flushed-face.png",
|
|
||||||
"freezing-face.png",
|
|
||||||
"frowning-face-with-open-mouth.png",
|
|
||||||
"grimacing-face.png",
|
|
||||||
"grinning-face-with-one-large-and-one-small-eye.png",
|
|
||||||
"grinning-face-with-smiling-eyes.png",
|
|
||||||
"grinning-face-with-star-eyes.png",
|
|
||||||
"grinning-face.png",
|
|
||||||
"hugging-face.png",
|
|
||||||
"hushed-face.png",
|
|
||||||
"imp.png",
|
|
||||||
"kissing-face-with-closed-eyes.png",
|
|
||||||
"kissing-face-with-smiling-eyes.png",
|
|
||||||
"kissing-face.png",
|
|
||||||
"loudly-crying-face.png",
|
|
||||||
"lying-face.png",
|
|
||||||
"money-mouth-face.png",
|
|
||||||
"nauseated-face.png",
|
|
||||||
"nerd-face.png",
|
|
||||||
"neutral-face.png",
|
|
||||||
"overheated-face.png",
|
|
||||||
"pensive-face.png",
|
|
||||||
"persevering-face.png",
|
|
||||||
"pouting-face.png",
|
|
||||||
"relieved-face.png",
|
|
||||||
"rolling-on-the-floor-laughing.png",
|
|
||||||
"serious-face-with-symbols-covering-mouth.png",
|
|
||||||
"shocked-face-with-exploding-head.png",
|
|
||||||
"sleeping-face.png",
|
|
||||||
"sleepy-face.png",
|
|
||||||
"slightly-frowning-face.png",
|
|
||||||
"slightly-smiling-face.png",
|
|
||||||
"smiling-face-with-halo.png",
|
|
||||||
"smiling-face-with-heart-shaped-eyes.png",
|
|
||||||
"smiling-face-with-horns.png",
|
|
||||||
"smiling-face-with-open-mouth-and-smiling-eyes.png",
|
|
||||||
"smiling-face-with-open-mouth-and-tightly-closed-eyes.png",
|
|
||||||
"smiling-face-with-open-mouth.png",
|
|
||||||
"smiling-face-with-smiling-eyes-and-hand-covering-mouth.png",
|
|
||||||
"smiling-face-with-smiling-eyes-and-three-hearts.png",
|
|
||||||
"smiling-face-with-smiling-eyes.png",
|
|
||||||
"smiling-face-with-sunglasses.png",
|
|
||||||
"smirking-face.png",
|
|
||||||
"sneezing-face.png",
|
|
||||||
"thinking-face.png",
|
|
||||||
"tired-face.png",
|
|
||||||
"upside-down-face.png",
|
|
||||||
"weary-face.png",
|
|
||||||
"white-frowning-face.png",
|
|
||||||
"white-smiling-face.png",
|
|
||||||
"winking-face.png",
|
|
||||||
"worried-face.png",
|
|
||||||
"zipper-mouth-face.png"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
emits: ["select"],
|
|
||||||
|
|
||||||
setup(_, { emit }) {
|
|
||||||
const { store } = useCool();
|
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
const visible = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 表情列表
|
|
||||||
const list = ref<any[]>(emoji.list.map((e) => emoji.url + e));
|
|
||||||
|
|
||||||
// 弹窗宽度
|
|
||||||
const popoverWidth = computed(() => {
|
|
||||||
const { width } = store.getters.browser;
|
|
||||||
return (width > 500 ? 500 : width) - 24;
|
|
||||||
});
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
visible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
visible.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function select(e: any) {
|
|
||||||
emit("select", e);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
visible,
|
|
||||||
list,
|
|
||||||
popoverWidth,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
select
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.popper-emoji {
|
|
||||||
padding: 5px !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.tool-emoji {
|
|
||||||
height: 250px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&__scroller {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: inline-block;
|
|
||||||
height: 25px;
|
|
||||||
width: 25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="icon-voice">
|
|
||||||
<icon-svg :name="`voice${index}`" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { defineProps, ref, watch } from "vue";
|
|
||||||
|
|
||||||
let timer: any = null;
|
|
||||||
|
|
||||||
const index = ref<any>("");
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
play: Boolean
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.play,
|
|
||||||
(val: boolean) => {
|
|
||||||
clearInterval(timer);
|
|
||||||
|
|
||||||
if (val) {
|
|
||||||
index.value = 1;
|
|
||||||
|
|
||||||
timer = setInterval(() => {
|
|
||||||
if (index.value == 1) {
|
|
||||||
index.value = "";
|
|
||||||
} else {
|
|
||||||
index.value += 1;
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
index.value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.icon-voice {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,216 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="cl-chat-input">
|
|
||||||
<!-- 工具栏 -->
|
|
||||||
<div class="cl-chat-input__opbar">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<!-- 表情 -->
|
|
||||||
<emoji @select="onEmojiSelect" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<!-- 图片上传 -->
|
|
||||||
<upload name="image" accept="image/*" @before-upload="append" @success="send">
|
|
||||||
<img src="../static/images/image.png" alt="" />
|
|
||||||
</upload>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<!-- 视频上传 -->
|
|
||||||
<upload name="video" accept="video/*" @before-upload="append" @success="send">
|
|
||||||
<img src="../static/images/video.png" alt="" />
|
|
||||||
</upload>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 输入框,发送按钮 -->
|
|
||||||
<div class="cl-chat-input__content">
|
|
||||||
<el-input
|
|
||||||
v-model="text"
|
|
||||||
placeholder="请描述您想咨询的问题"
|
|
||||||
type="textarea"
|
|
||||||
resize="none"
|
|
||||||
:rows="5"
|
|
||||||
@keyup.enter="onTextSend"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-button type="primary" size="mini" :disabled="!text" @click="onTextSend"
|
|
||||||
>发送</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, inject, nextTick, reactive, ref } from "vue";
|
|
||||||
import Emoji from "./emoji.vue";
|
|
||||||
import Upload from "./upload.vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
Emoji,
|
|
||||||
Upload
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { store, mitt } = useCool();
|
|
||||||
|
|
||||||
// 聊天信息
|
|
||||||
const chat = inject<any>("chat");
|
|
||||||
|
|
||||||
// 输入值
|
|
||||||
const text = ref<string>("");
|
|
||||||
|
|
||||||
// 表情
|
|
||||||
const emoji = reactive<any>({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// 追加消息
|
|
||||||
function append(data: any) {
|
|
||||||
store.commit("APPEND_MESSAGE_LIST", data);
|
|
||||||
mitt.emit("message.scrollToBottom");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
function send(data: any, isAppend?: boolean) {
|
|
||||||
const { id, userId } = store.getters.session;
|
|
||||||
|
|
||||||
// 格式化内容
|
|
||||||
data.content = JSON.stringify(data.content);
|
|
||||||
|
|
||||||
// 更新消息
|
|
||||||
store.commit("UPDATE_SESSION", data);
|
|
||||||
|
|
||||||
if (chat.socket) {
|
|
||||||
chat.socket.emit(`user@${userId}`, {
|
|
||||||
contentType: data.contentType,
|
|
||||||
type: 0,
|
|
||||||
content: data.content,
|
|
||||||
sessionId: id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAppend) {
|
|
||||||
append(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送文本内容
|
|
||||||
function onTextSend() {
|
|
||||||
if (text.value) {
|
|
||||||
if (text.value.replace(/\n/g, "") !== "") {
|
|
||||||
const data = {
|
|
||||||
type: 0,
|
|
||||||
contentType: 0,
|
|
||||||
content: {
|
|
||||||
text: text.value
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
send(data, true);
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
text.value = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片选择
|
|
||||||
function onImageSelect(res: any) {
|
|
||||||
send(
|
|
||||||
{
|
|
||||||
content: {
|
|
||||||
imageUrl: res.data
|
|
||||||
},
|
|
||||||
type: 0,
|
|
||||||
contentType: 1
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表情选择
|
|
||||||
function onEmojiSelect(url: string) {
|
|
||||||
emoji.visible = false;
|
|
||||||
send(
|
|
||||||
{
|
|
||||||
content: {
|
|
||||||
imageUrl: url
|
|
||||||
},
|
|
||||||
type: 0,
|
|
||||||
contentType: 2
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视频选择
|
|
||||||
function onVideoSelect(url: string) {
|
|
||||||
send(
|
|
||||||
{
|
|
||||||
content: {
|
|
||||||
videoUrl: url
|
|
||||||
},
|
|
||||||
type: 0,
|
|
||||||
contentType: 4
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
text,
|
|
||||||
emoji,
|
|
||||||
send,
|
|
||||||
append,
|
|
||||||
onTextSend,
|
|
||||||
onImageSelect,
|
|
||||||
onEmojiSelect,
|
|
||||||
onVideoSelect
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.cl-chat-input {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
&__opbar {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
margin-right: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 26px;
|
|
||||||
width: 26px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 15px;
|
|
||||||
bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,546 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-loading="!visible && loading" class="cl-chat-message" element-loading-text="消息加载中">
|
|
||||||
<div
|
|
||||||
:ref="setRefs('scroller')"
|
|
||||||
class="cl-chat-message__scroller scroller1"
|
|
||||||
:style="{
|
|
||||||
opacity: visible ? 1 : 0
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- 加载更多 -->
|
|
||||||
<div v-show="list.length > 0" class="cl-chat-message__more">
|
|
||||||
<el-button round size="mini" :loading="loading" @click="onLoadmore"
|
|
||||||
>加载更多</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 消息列表 -->
|
|
||||||
<div class="cl-chat-message__list">
|
|
||||||
<div
|
|
||||||
v-for="item in list"
|
|
||||||
:key="item.id || item.uid"
|
|
||||||
class="cl-chat-message__item"
|
|
||||||
:class="[item.type == 0 ? `is-right` : `is-left`, `is-${item.mode}`]"
|
|
||||||
>
|
|
||||||
<!-- 日期 -->
|
|
||||||
<div v-if="item._date" class="date">
|
|
||||||
<span>{{ item._date }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 内容 -->
|
|
||||||
<div class="main">
|
|
||||||
<!-- 头像 -->
|
|
||||||
<div class="avatar">
|
|
||||||
<img :src="item.avatarUrl" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="det">
|
|
||||||
<!-- 昵称 -->
|
|
||||||
<span class="name">{{ item.nickName }}</span>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-loading="item.loading"
|
|
||||||
class="content"
|
|
||||||
:element-loading-text="item.progress"
|
|
||||||
@click="onTap(item)"
|
|
||||||
>
|
|
||||||
<!-- 文本 -->
|
|
||||||
<template v-if="item.mode === 'text'">{{
|
|
||||||
item.content.text
|
|
||||||
}}</template>
|
|
||||||
|
|
||||||
<!-- 图片 -->
|
|
||||||
<template v-else-if="item.mode === 'image'">
|
|
||||||
<el-image
|
|
||||||
:key="item.uid"
|
|
||||||
:src="item.content.imageUrl"
|
|
||||||
:preview-src-list="[item.content.imageUrl]"
|
|
||||||
:z-index="3000"
|
|
||||||
:style="item.style"
|
|
||||||
>
|
|
||||||
<template #placeholder>
|
|
||||||
<img
|
|
||||||
:src="item.content.imageUrl"
|
|
||||||
:style="item.style"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-image>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 表情 -->
|
|
||||||
<template v-else-if="item.mode === 'emoji'">
|
|
||||||
<img :src="item.content.imageUrl" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 语音 -->
|
|
||||||
<template v-else-if="item.mode === 'voice'">
|
|
||||||
<icon-voice :play="item.isPlay" />
|
|
||||||
<span class="duration">{{ item.content.duration }}"</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 视频 -->
|
|
||||||
<template v-else-if="item.mode === 'video'">
|
|
||||||
<div class="item">
|
|
||||||
<video
|
|
||||||
:poster="item.content.videoUrl"
|
|
||||||
:src="item.content.videoUrl"
|
|
||||||
controls
|
|
||||||
></video>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 未知 -->
|
|
||||||
<template v-else>
|
|
||||||
<span>待扩展消息类型</span>
|
|
||||||
<i class="el-icon-warning-outline"></i>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 音频 -->
|
|
||||||
<div class="voice">
|
|
||||||
<audio ref="voice" style="display: none" :src="voice.url" controls></audio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, inject, nextTick, onUnmounted, reactive, ref } from "vue";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { isString } from "/@/cool/utils";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import IconVoice from "./icon-voice.vue";
|
|
||||||
|
|
||||||
import AvatarUrl from "../static/images/custom-avatar.png";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
IconVoice
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, service, store, mitt } = useCool();
|
|
||||||
|
|
||||||
// 聊天信息
|
|
||||||
const chat = inject<any>("chat");
|
|
||||||
|
|
||||||
// 当前会话信息
|
|
||||||
const session = computed(() => store.getters.session);
|
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
const visible = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 分页信息
|
|
||||||
const pagination = reactive<any>({
|
|
||||||
page: 1,
|
|
||||||
size: 20,
|
|
||||||
total: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// 语音
|
|
||||||
const voice = reactive<any>({
|
|
||||||
url: "",
|
|
||||||
timer: null
|
|
||||||
});
|
|
||||||
|
|
||||||
// 请求随机值
|
|
||||||
const refreshRd = ref<any>(null);
|
|
||||||
|
|
||||||
// 消息列表
|
|
||||||
const list = computed(() => {
|
|
||||||
const { userInfo, messageList } = store.getters;
|
|
||||||
|
|
||||||
let date = "";
|
|
||||||
|
|
||||||
return messageList.map((e: any) => {
|
|
||||||
// 时间间隔
|
|
||||||
const _date = date
|
|
||||||
? dayjs(e.createTime).isBefore(dayjs(date).add(1, "minute"))
|
|
||||||
? ""
|
|
||||||
: e.createTime
|
|
||||||
: e.createTime;
|
|
||||||
|
|
||||||
// 发送时间
|
|
||||||
date = e.createTime;
|
|
||||||
|
|
||||||
// 解析内容
|
|
||||||
if (isString(e)) {
|
|
||||||
e = JSON.parse(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容
|
|
||||||
const content = isString(e.content) ? JSON.parse(e.content) : e.content;
|
|
||||||
|
|
||||||
// 昵称
|
|
||||||
const nickName = e.type == 0 ? userInfo.nickName : session.value.nickname;
|
|
||||||
|
|
||||||
// 头像
|
|
||||||
const avatarUrl =
|
|
||||||
e.type == 0 ? userInfo.avatarUrl || AvatarUrl : session.value.headimgurl;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...e,
|
|
||||||
_date,
|
|
||||||
content,
|
|
||||||
avatarUrl,
|
|
||||||
nickName,
|
|
||||||
mode: chat.modes[e.contentType]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 点击
|
|
||||||
function onTap(item: any) {
|
|
||||||
// 播放语音
|
|
||||||
if (item.mode == "voice") {
|
|
||||||
store.getters.messageList.map((e: any) => {
|
|
||||||
e.isPlay = e.id == item.id ? e.isPlay : false;
|
|
||||||
});
|
|
||||||
|
|
||||||
item.isPlay = !item.isPlay;
|
|
||||||
|
|
||||||
if (item.isPlay) {
|
|
||||||
voice.url = item.content.voiceUrl;
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
refs.value.voice.play();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
refs.value.voice.pause();
|
|
||||||
item.isPlay = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(voice.timer);
|
|
||||||
|
|
||||||
voice.timer = setTimeout(() => {
|
|
||||||
item.isPlay = false;
|
|
||||||
}, item.content.duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 滚动到底部
|
|
||||||
function scrollToBottom() {
|
|
||||||
nextTick(() => {
|
|
||||||
if (refs.value.scroller) {
|
|
||||||
refs.value.scroller.scrollTo({
|
|
||||||
top: 99999,
|
|
||||||
behavior: visible.value ? "smooth" : "auto"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新列表
|
|
||||||
function refresh(params?: any) {
|
|
||||||
// 请求随机值
|
|
||||||
const rd = (refreshRd.value = Math.random());
|
|
||||||
|
|
||||||
// 请求参数
|
|
||||||
const data = {
|
|
||||||
...pagination,
|
|
||||||
...params,
|
|
||||||
sessionId: session.value.id,
|
|
||||||
order: "createTime",
|
|
||||||
sort: "desc"
|
|
||||||
};
|
|
||||||
|
|
||||||
// 加载动画
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
// 首页处理
|
|
||||||
if (data.page === 1) {
|
|
||||||
visible.value = false;
|
|
||||||
store.commit("CLEAR_MESSAGE_LIST");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完成
|
|
||||||
const done = () => {
|
|
||||||
loading.value = false;
|
|
||||||
visible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
service.chat.message
|
|
||||||
.page(data)
|
|
||||||
.then((res: any) => {
|
|
||||||
// 防止脏数据
|
|
||||||
if (rd != refreshRd.value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页信息
|
|
||||||
Object.assign(pagination, res.pagination);
|
|
||||||
|
|
||||||
// 追加数据
|
|
||||||
store.commit("PREPEND_MESSAGE_LIST", res.list);
|
|
||||||
|
|
||||||
if (data.page === 1) {
|
|
||||||
scrollToBottom();
|
|
||||||
|
|
||||||
// 首次滚动隐藏
|
|
||||||
setTimeout(done, 0);
|
|
||||||
} else {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载更多
|
|
||||||
function onLoadmore() {
|
|
||||||
refresh({ page: pagination.page + 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听列表刷新
|
|
||||||
mitt.on("message.refresh", refresh);
|
|
||||||
// 滚动到底部
|
|
||||||
mitt.on("message.scrollToBottom", scrollToBottom);
|
|
||||||
|
|
||||||
// 销毁
|
|
||||||
onUnmounted(function () {
|
|
||||||
// 移除语音播放
|
|
||||||
clearTimeout(voice.timer);
|
|
||||||
|
|
||||||
list.value.map((e: any) => {
|
|
||||||
e.isPlay = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 移除事件
|
|
||||||
mitt.off("message.refresh", refresh);
|
|
||||||
mitt.off("message.scrollToBottom", scrollToBottom);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
chat,
|
|
||||||
session,
|
|
||||||
loading,
|
|
||||||
visible,
|
|
||||||
pagination,
|
|
||||||
voice,
|
|
||||||
list,
|
|
||||||
setRefs,
|
|
||||||
onTap,
|
|
||||||
refresh,
|
|
||||||
onLoadmore,
|
|
||||||
scrollToBottom
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cl-chat-message {
|
|
||||||
height: calc(100% - 5px);
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
&__scroller {
|
|
||||||
height: calc(100% - 10px);
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 5px 0px 5px 5px;
|
|
||||||
padding: 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__more {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
.date {
|
|
||||||
text-align: center;
|
|
||||||
margin: 10px 0;
|
|
||||||
|
|
||||||
span {
|
|
||||||
background-color: #dadada;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 3px 5px 2px 5px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.det {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 60%;
|
|
||||||
|
|
||||||
.name {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-left {
|
|
||||||
.main {
|
|
||||||
.det {
|
|
||||||
margin-left: 10px;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-voice {
|
|
||||||
.content {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-right {
|
|
||||||
.main {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
|
|
||||||
.det {
|
|
||||||
margin-right: 10px;
|
|
||||||
align-items: flex-end;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
background-color: $color-primary;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-voice {
|
|
||||||
.content {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-text {
|
|
||||||
.content {
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 40px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-text,
|
|
||||||
&.is-voice {
|
|
||||||
.content {
|
|
||||||
padding: 10px;
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-emoji {
|
|
||||||
.content {
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-voice {
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 65px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-video {
|
|
||||||
.item {
|
|
||||||
video {
|
|
||||||
display: block;
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 300px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-image {
|
|
||||||
.main {
|
|
||||||
.det {
|
|
||||||
.content {
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
display: block;
|
|
||||||
border-radius: 6px;
|
|
||||||
max-width: 200px;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-undefined {
|
|
||||||
.main {
|
|
||||||
.det {
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
background-color: #f56c6c;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.el-icon-warning-outline {
|
|
||||||
font-size: 15px;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="cl-chat-notice" @click="openChatBox">
|
|
||||||
<el-badge :value="number" :hidden="number === 0">
|
|
||||||
<i class="el-icon-message-solid"></i>
|
|
||||||
</el-badge>
|
|
||||||
|
|
||||||
<!-- 聊天盒子 -->
|
|
||||||
<cl-chat :ref="setRefs('chat')" @message="updateNum" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onBeforeMount, ref } from "vue-demi";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-chat-notice",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs, service } = useCool();
|
|
||||||
|
|
||||||
const number = ref<number>(0);
|
|
||||||
|
|
||||||
function refresh() {
|
|
||||||
service.chat.session.unreadCount().then((res: any) => {
|
|
||||||
number.value = Number(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNum(isOpen: boolean) {
|
|
||||||
number.value += isOpen ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openChatBox() {
|
|
||||||
refs.value.chat.open();
|
|
||||||
number.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
number,
|
|
||||||
refresh,
|
|
||||||
updateNum,
|
|
||||||
openChatBox
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cl-chat-notice {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.el-icon-message-solid {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-badge {
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="cl-chat-session"
|
|
||||||
:class="{
|
|
||||||
'is-position': browser.isMini,
|
|
||||||
'is-show': sessionVisible
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- 关键字搜索 -->
|
|
||||||
<div class="cl-chat-session__search">
|
|
||||||
<el-input
|
|
||||||
v-model="keyWord"
|
|
||||||
placeholder="搜索"
|
|
||||||
prefix-icon="el-icon-search"
|
|
||||||
size="small"
|
|
||||||
clearable
|
|
||||||
@clear="onSearch"
|
|
||||||
@keyup.enter="onSearch"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 会话列表 -->
|
|
||||||
<ul v-loading="loading" class="cl-chat-session__list scroller1">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in list"
|
|
||||||
:key="index"
|
|
||||||
class="cl-chat-session__item"
|
|
||||||
:class="{
|
|
||||||
'is-active': session ? item.id == session.id : false
|
|
||||||
}"
|
|
||||||
@click="toDetail(item)"
|
|
||||||
@contextmenu.stop.prevent="openCM($event, item.id, Number(index))"
|
|
||||||
>
|
|
||||||
<!-- 头像 -->
|
|
||||||
<div class="avatar">
|
|
||||||
<el-badge
|
|
||||||
:value="item.serviceUnreadCount"
|
|
||||||
:hidden="item.serviceUnreadCount === 0"
|
|
||||||
:max="99"
|
|
||||||
>
|
|
||||||
<img :src="item.headimgurl" alt="" />
|
|
||||||
</el-badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 昵称,内容 -->
|
|
||||||
<div class="det">
|
|
||||||
<p class="name">{{ item.nickname }}</p>
|
|
||||||
<p class="content">{{ item.lastMessage }}</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, onUnmounted, reactive, ref } from "vue";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { isEmpty } from "/@/cool/utils";
|
|
||||||
import { ContextMenu } from "@cool-vue/crud";
|
|
||||||
import { parseContent } from "../utils";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const { store, service, mitt } = useCool();
|
|
||||||
|
|
||||||
// 当前会话信息
|
|
||||||
const session = computed(() => store.getters.session);
|
|
||||||
// 是否显示会话列表
|
|
||||||
const sessionVisible = computed(() => store.getters.sessionVisible);
|
|
||||||
// 浏览器信息
|
|
||||||
const browser = computed(() => store.getters.browser);
|
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 分页信息
|
|
||||||
const pagination = reactive<any>({
|
|
||||||
page: 1,
|
|
||||||
size: 100,
|
|
||||||
total: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// 关键字筛选
|
|
||||||
const keyWord = ref<string>("");
|
|
||||||
|
|
||||||
// 刷新列表
|
|
||||||
async function refresh(params?: any) {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
const res = await service.chat.session
|
|
||||||
.page({
|
|
||||||
...pagination,
|
|
||||||
keyWord: keyWord.value,
|
|
||||||
params,
|
|
||||||
order: "updateTime",
|
|
||||||
sort: "desc"
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
store.commit("SET_SESSION_LIST", res.list);
|
|
||||||
Object.assign(pagination, res.pagination);
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
loading.value = false;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索关键字
|
|
||||||
function onSearch() {
|
|
||||||
refresh({ page: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置会话
|
|
||||||
function setSession(item: any) {
|
|
||||||
if (item) {
|
|
||||||
store.commit("SET_SESSION", item);
|
|
||||||
mitt.emit("message.refresh", { page: 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 会话详情
|
|
||||||
function toDetail(item?: any) {
|
|
||||||
if (item) {
|
|
||||||
// 点击关闭弹窗
|
|
||||||
if (browser.value.isMini) store.commit("CLOSE_SESSION");
|
|
||||||
|
|
||||||
// 设置为当前会话
|
|
||||||
if (!session.value || session.value.id != item.id) {
|
|
||||||
setSession(item);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
store.commit("CLEAR_SESSION");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据列表
|
|
||||||
const list = computed(() => {
|
|
||||||
return store.getters.sessionList
|
|
||||||
.map((e: any) => {
|
|
||||||
const { _text } = parseContent(e);
|
|
||||||
return {
|
|
||||||
...e,
|
|
||||||
lastMessage: _text
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a: any, b: any) => {
|
|
||||||
return a.updateTime < b.updateTime ? 1 : -1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 右键菜单
|
|
||||||
function openCM(e: any, id: any, index: number) {
|
|
||||||
ContextMenu.open(e, {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "删除",
|
|
||||||
icon: "el-icon-delete",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
service.chat.session.delete({
|
|
||||||
ids: id
|
|
||||||
});
|
|
||||||
|
|
||||||
list.value.splice(index, 1);
|
|
||||||
|
|
||||||
if (id == session.value.id) {
|
|
||||||
toDetail();
|
|
||||||
}
|
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// PC 端下首次请求读取第一个消息
|
|
||||||
refresh().then((res: any) => {
|
|
||||||
if (!isEmpty(res.list) && !browser.value.isMini) {
|
|
||||||
setSession(res.list[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 事件监听
|
|
||||||
mitt.on("session.refresh", refresh);
|
|
||||||
|
|
||||||
// 销毁
|
|
||||||
onUnmounted(function () {
|
|
||||||
mitt.off("session.refresh", refresh);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
session,
|
|
||||||
sessionVisible,
|
|
||||||
browser,
|
|
||||||
list,
|
|
||||||
loading,
|
|
||||||
pagination,
|
|
||||||
keyWord,
|
|
||||||
openCM,
|
|
||||||
refresh,
|
|
||||||
onSearch,
|
|
||||||
toDetail
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cl-chat-session {
|
|
||||||
height: 100%;
|
|
||||||
width: 0;
|
|
||||||
transition: width 0.2s ease-in-out;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #fff;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.is-show {
|
|
||||||
width: 250px;
|
|
||||||
max-width: 100%;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-position {
|
|
||||||
position: absolute;
|
|
||||||
left: 5px;
|
|
||||||
top: 51px;
|
|
||||||
height: calc(100% - 56px);
|
|
||||||
z-index: 3000;
|
|
||||||
|
|
||||||
&.is-show {
|
|
||||||
width: calc(100% - 10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__search {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__list {
|
|
||||||
height: calc(100% - 52px);
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
list-style: none;
|
|
||||||
padding: 10px;
|
|
||||||
border-left: 5px solid #fff;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
margin-right: 12px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-badge {
|
|
||||||
&__content {
|
|
||||||
height: 14px;
|
|
||||||
line-height: 14px;
|
|
||||||
padding: 0 4px;
|
|
||||||
background-color: #fa5151;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.det {
|
|
||||||
flex: 1;
|
|
||||||
.name {
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
font-size: 12px;
|
|
||||||
margin-top: 5px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name,
|
|
||||||
.content {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
background-color: #eee;
|
|
||||||
border-color: $color-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #eee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__empty {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<cl-upload
|
|
||||||
:accept="accept"
|
|
||||||
list-type
|
|
||||||
:before-upload="onBeforeUpload"
|
|
||||||
:on-progress="onUploadProgress"
|
|
||||||
:on-success="onUploadSuccess"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
</cl-upload>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, inject } from "vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
name: String,
|
|
||||||
accept: String
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["before-upload", "success"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { store } = useCool();
|
|
||||||
|
|
||||||
// 聊天信息
|
|
||||||
const chat = inject<any>("chat");
|
|
||||||
|
|
||||||
// 上传前
|
|
||||||
function onBeforeUpload(file: any) {
|
|
||||||
// 先添加到列表中,等待上传
|
|
||||||
function next(options = {}) {
|
|
||||||
const data = {
|
|
||||||
content: {
|
|
||||||
[`${props.name}Url`]: ""
|
|
||||||
},
|
|
||||||
type: 0,
|
|
||||||
uid: file.uid,
|
|
||||||
loading: true,
|
|
||||||
progress: "0%",
|
|
||||||
contentType: chat.modes.indexOf(props.name),
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
|
|
||||||
emit("before-upload", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片预览
|
|
||||||
if (props.name == "image") {
|
|
||||||
const fileReader = new FileReader();
|
|
||||||
|
|
||||||
fileReader.onload = (e: any) => {
|
|
||||||
const imageUrl = e.target.result;
|
|
||||||
const image = new Image();
|
|
||||||
|
|
||||||
image.onload = () => {
|
|
||||||
let height = 0;
|
|
||||||
let width = 0;
|
|
||||||
|
|
||||||
if (image.width > 200) {
|
|
||||||
width = 200;
|
|
||||||
height = (image.height * 200) / image.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
next({
|
|
||||||
content: {
|
|
||||||
imageUrl
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
height: height + "px",
|
|
||||||
width: width + "px"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
image.src = imageUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
fileReader.readAsDataURL(file);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传中
|
|
||||||
function onUploadProgress(e: any, file: any) {
|
|
||||||
store.commit("UPDATE_MESSAGE", {
|
|
||||||
file,
|
|
||||||
data: {
|
|
||||||
progress: e.percent + "%"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传成功
|
|
||||||
function onUploadSuccess(res: any, file: any) {
|
|
||||||
store.commit("UPDATE_MESSAGE", {
|
|
||||||
file,
|
|
||||||
data: {
|
|
||||||
loading: false,
|
|
||||||
content: {
|
|
||||||
[`${props.name}Url`]: res.data
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback: (data: any) => {
|
|
||||||
emit("success", data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
onBeforeUpload,
|
|
||||||
onUploadProgress,
|
|
||||||
onUploadSuccess
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { BaseService, Service, Permission } from "/@/cool";
|
|
||||||
|
|
||||||
@Service({
|
|
||||||
namespace: "im/message",
|
|
||||||
mock: true
|
|
||||||
})
|
|
||||||
class ImMessage extends BaseService {
|
|
||||||
@Permission("read")
|
|
||||||
read(data: any) {
|
|
||||||
return this.request({
|
|
||||||
url: "/read",
|
|
||||||
method: "POST",
|
|
||||||
data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ImMessage;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { BaseService, Service, Permission } from "/@/cool";
|
|
||||||
|
|
||||||
@Service({
|
|
||||||
namespace: "im/session",
|
|
||||||
mock: true
|
|
||||||
})
|
|
||||||
class ImSession extends BaseService {
|
|
||||||
@Permission("unreadCount")
|
|
||||||
unreadCount() {
|
|
||||||
return this.request({
|
|
||||||
url: "/unreadCount"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ImSession;
|
|
||||||
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 845 B |
|
Before Width: | Height: | Size: 724 B |
@ -1,58 +0,0 @@
|
|||||||
import { isArray } from "/@/cool/utils";
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
list: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
messageList: (state: any) => state.list
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
// 设置列表
|
|
||||||
SET_MESSAGE_LIST(state: any, data: any[]) {
|
|
||||||
state.list = data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 追加数据
|
|
||||||
APPEND_MESSAGE_LIST(state: any, data: any) {
|
|
||||||
state.list.push(data);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 追加数据到头部
|
|
||||||
PREPEND_MESSAGE_LIST(state: any, data: any) {
|
|
||||||
const list = isArray(data) ? data : [data];
|
|
||||||
state.list.unshift(...list.reverse());
|
|
||||||
},
|
|
||||||
|
|
||||||
// 清空列表
|
|
||||||
CLEAR_MESSAGE_LIST(state: any) {
|
|
||||||
state.list = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
// 更新消息数据
|
|
||||||
UPDATE_MESSAGE(state: any, { file, data, callback }: any) {
|
|
||||||
let item = null;
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
item = state.list.find((e: any) => e.uid === file.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
if (data) {
|
|
||||||
Object.assign(item, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback) callback(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import { isBoolean } from "/@/cool/utils";
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
list: [],
|
|
||||||
current: null,
|
|
||||||
visible: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters = {
|
|
||||||
// 当前会话
|
|
||||||
session: (state: any) => state.current,
|
|
||||||
// 会话列表
|
|
||||||
sessionList: (state: any) => state.list,
|
|
||||||
// 是否显示会话列表
|
|
||||||
sessionVisible: (state: any) => state.visible
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {};
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
// 设置会话信息
|
|
||||||
SET_SESSION(state: any, data: any) {
|
|
||||||
state.current = data;
|
|
||||||
state.current.serviceUnreadCount = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 清空会话信息
|
|
||||||
CLEAR_SESSION(state: any) {
|
|
||||||
state.session = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 更新会话信息
|
|
||||||
UPDATE_SESSION(state: any, data: any) {
|
|
||||||
Object.assign(state.current, data);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 设置会话列表
|
|
||||||
SET_SESSION_LIST(state: any, data: any[]) {
|
|
||||||
state.list = data;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 清空会话列表
|
|
||||||
CLEAR_SESSION_LIST(state: any) {
|
|
||||||
state.list = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
// 打开会话列表
|
|
||||||
OPEN_SESSION(state: any, val: any) {
|
|
||||||
state.visible = isBoolean(val) ? val : !state.visible;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 关闭会话列表
|
|
||||||
CLOSE_SESSION(state: any) {
|
|
||||||
state.visible = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations
|
|
||||||
};
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { isObject } from "/@/cool/utils";
|
|
||||||
|
|
||||||
export function parseContent({ content, contentType }: any) {
|
|
||||||
const data = isObject(content) ? content : JSON.parse(content);
|
|
||||||
let text = "";
|
|
||||||
|
|
||||||
switch (contentType) {
|
|
||||||
case 0:
|
|
||||||
text = data.text;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
text = "[图片]";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
text = "[表情]";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
text = "[语音]";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
text = "[视频]";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
text = "[商品信息]";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
data._text = text;
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo-adv-search">
|
|
||||||
<cl-adv-btn />
|
|
||||||
<cl-adv-search :items="items" :op-list="opList" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { AdvSearchItem } from "@cool-vue/crud/types";
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const items = ref<AdvSearchItem[]>([
|
|
||||||
{
|
|
||||||
label: "昵称",
|
|
||||||
prop: "name",
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
attrs: {
|
|
||||||
placeholder: "搜索昵称"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
component: {
|
|
||||||
name: "el-radio-group",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "启用",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "禁用",
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const opList = ref<string[]>(["search", "reset", "clear", "close"]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
items,
|
|
||||||
opList
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo__context-menu" @contextmenu="open">
|
|
||||||
<el-button size="mini">打开右键菜单</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ContextMenu } from "@cool-vue/crud";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
function open(event: any) {
|
|
||||||
ContextMenu.open(event, {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "新增",
|
|
||||||
"suffix-icon": "el-icon-plus",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
ElMessage.success("点击了新增");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "编辑",
|
|
||||||
"suffix-icon": "el-icon-edit",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
ElMessage.success("点击了编辑");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "删除",
|
|
||||||
"suffix-icon": "el-icon-delete",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
ElMessage.error("点击了删除");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "二级",
|
|
||||||
"suffix-icon": "el-icon-right",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: "文本超出隐藏,有一天晚上",
|
|
||||||
ellipsis: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "禁用",
|
|
||||||
disabled: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "更多",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: "空空如也",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
open
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.demo__context-menu {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo__dialog">
|
|
||||||
<el-button size="mini" @click="open">打开对话框</el-button>
|
|
||||||
|
|
||||||
<cl-dialog
|
|
||||||
v-model="visible"
|
|
||||||
@open="onOpen"
|
|
||||||
@close="onClose"
|
|
||||||
@opened="onOpened"
|
|
||||||
@closed="onClosed"
|
|
||||||
>
|
|
||||||
<el-alert type="success" title="行云又被风吹散,见了依前是梦中" />
|
|
||||||
</cl-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const visible = ref<boolean>(false);
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
visible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpen() {
|
|
||||||
console.log("open");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpened() {
|
|
||||||
console.log("opened");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClose() {
|
|
||||||
console.log("close");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClosed() {
|
|
||||||
console.log("closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
visible,
|
|
||||||
open,
|
|
||||||
onOpen,
|
|
||||||
onOpened,
|
|
||||||
onClose,
|
|
||||||
onClosed
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.demo__dialog {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo-form">
|
|
||||||
<el-button size="mini" @click="open">打开表单</el-button>
|
|
||||||
|
|
||||||
<cl-form ref="formRef">
|
|
||||||
<!-- 内嵌crud -->
|
|
||||||
<template #slot-crud>
|
|
||||||
<cl-crud @load="onCrudLoad">
|
|
||||||
<cl-table
|
|
||||||
:auto-height="false"
|
|
||||||
:columns="[
|
|
||||||
{
|
|
||||||
label: '#',
|
|
||||||
type: 'index',
|
|
||||||
width: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '姓名',
|
|
||||||
prop: 'name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '存款',
|
|
||||||
prop: 'price'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '创建时间',
|
|
||||||
prop: 'createTime'
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</cl-crud>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 动态增减表单验证 -->
|
|
||||||
<template #slot-validate="{ scope }">
|
|
||||||
<el-form-item
|
|
||||||
v-for="(item, index) in scope.vads"
|
|
||||||
:key="index"
|
|
||||||
:prop="'vads.' + index + '.val'"
|
|
||||||
:rules="{ required: true, message: '请输入' }"
|
|
||||||
>
|
|
||||||
<el-input v-model="item.val" />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-button @click="addVad(scope.vads)">添加行</el-button>
|
|
||||||
</template>
|
|
||||||
</cl-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, resolveComponent, h } from "vue";
|
|
||||||
import { CrudLoad, FormItem, FormRef } from "@cool-vue/crud/types";
|
|
||||||
import { TestService } from "../../utils/service";
|
|
||||||
import Test from "./render/test.vue";
|
|
||||||
import Test2 from "./render/test2";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const formRef = ref<FormRef>();
|
|
||||||
|
|
||||||
function renderDivider(label: string) {
|
|
||||||
const el: any = resolveComponent("el-divider");
|
|
||||||
|
|
||||||
return h(
|
|
||||||
el,
|
|
||||||
{
|
|
||||||
"content-position": "left"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
default: () => label
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const items: FormItem[] = [
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
labelWidth: "0px"
|
|
||||||
},
|
|
||||||
component: () => {
|
|
||||||
return renderDivider("测试组件渲染");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: ".vue 组件",
|
|
||||||
value: 10,
|
|
||||||
prop: "vue",
|
|
||||||
component: Test
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "tsx",
|
|
||||||
prop: "tsx",
|
|
||||||
value: "Hello!",
|
|
||||||
component: Test2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
labelWidth: "0px"
|
|
||||||
},
|
|
||||||
component: () => {
|
|
||||||
return renderDivider("测试内嵌CRUD");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
labelWidth: "0px"
|
|
||||||
},
|
|
||||||
component: {
|
|
||||||
name: "slot-crud"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
labelWidth: "0px"
|
|
||||||
},
|
|
||||||
component: () => {
|
|
||||||
return renderDivider("测试验证规则");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: "vads",
|
|
||||||
value: [],
|
|
||||||
label: "动态增减表单验证",
|
|
||||||
component: {
|
|
||||||
name: "slot-validate"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
props: {
|
|
||||||
labelWidth: "0px"
|
|
||||||
},
|
|
||||||
component: () => {
|
|
||||||
return renderDivider("测试显隐");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "奇术",
|
|
||||||
prop: "qs",
|
|
||||||
value: [],
|
|
||||||
component: {
|
|
||||||
name: "el-select",
|
|
||||||
props: {
|
|
||||||
placeholder: "请选择奇术",
|
|
||||||
multiple: true
|
|
||||||
},
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "烟水还魂",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "雨恨云愁",
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "技能",
|
|
||||||
prop: "jn",
|
|
||||||
value: 1,
|
|
||||||
component: {
|
|
||||||
name: "el-select",
|
|
||||||
props: {
|
|
||||||
placeholder: "请选择技能"
|
|
||||||
},
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "飞羽箭",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "落星式",
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "五行",
|
|
||||||
prop: "wx",
|
|
||||||
value: 0,
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.jn == 1;
|
|
||||||
},
|
|
||||||
component: {
|
|
||||||
name: "el-radio-group",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "水",
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "火",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "雷",
|
|
||||||
value: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "风",
|
|
||||||
value: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "土",
|
|
||||||
value: 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "雨润",
|
|
||||||
prop: "s1",
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.wx != 0;
|
|
||||||
},
|
|
||||||
component: ({ h }: any) => {
|
|
||||||
return h("p", "以甘甜雨露的滋润使人精力充沛");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "风雪冰天",
|
|
||||||
prop: "s2",
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.wx != 0;
|
|
||||||
},
|
|
||||||
component: ({ h }: any) => {
|
|
||||||
return h("p", "召唤漫天风雪,对敌方造成巨大的杀伤力");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "三昧真火",
|
|
||||||
prop: "h",
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.wx != 1;
|
|
||||||
},
|
|
||||||
component: ({ h }: any) => {
|
|
||||||
return h("p", "召唤三昧真火焚烧敌方的仙术");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "惊雷闪",
|
|
||||||
prop: "l",
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.wx != 2;
|
|
||||||
},
|
|
||||||
component: ({ h }: any) => {
|
|
||||||
return h("p", "召唤惊雷无数,对敌方全体进行攻击,是十分强力的仙术");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "如沐春风",
|
|
||||||
prop: "f",
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.wx != 3;
|
|
||||||
},
|
|
||||||
component: ({ h }: any) => {
|
|
||||||
return h("p", "温暖柔和的复苏春风,使人回复活力");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "艮山壁障",
|
|
||||||
prop: "t",
|
|
||||||
hidden: ({ scope }: any) => {
|
|
||||||
return scope.wx != 4;
|
|
||||||
},
|
|
||||||
component: ({ h }: any) => {
|
|
||||||
return h("p", "以艮山之灵形成一道壁障,受此壁障守护者刀枪不入");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
formRef.value?.open({
|
|
||||||
width: "1000px",
|
|
||||||
props: {
|
|
||||||
labelWidth: "140px"
|
|
||||||
},
|
|
||||||
items,
|
|
||||||
on: {
|
|
||||||
submit(data, { done }) {
|
|
||||||
console.log(data);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCrudLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(TestService).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addVad(list: Array<any>) {
|
|
||||||
list.push({
|
|
||||||
val: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
open,
|
|
||||||
formRef,
|
|
||||||
onCrudLoad,
|
|
||||||
addVad
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.demo-form {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
<template>
|
|
||||||
<cl-query field="status" :list="list" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { QueryList } from "@cool-vue/crud/types";
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const list = ref<QueryList[]>([
|
|
||||||
{
|
|
||||||
label: "启用",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "禁用",
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
list
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>{{ value }}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "crud-test",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const value = ref<string>("Value:" + Math.random());
|
|
||||||
|
|
||||||
return {
|
|
||||||
value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "test2",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: String,
|
|
||||||
scope: null
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue", "change"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const value = ref<string>(props.modelValue || "");
|
|
||||||
|
|
||||||
function onChange(val: string) {
|
|
||||||
emit("update:modelValue", val);
|
|
||||||
emit("change", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
onChange
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
render(ctx: any) {
|
|
||||||
return <el-input v-model={ctx.value} size="mini" onChange={ctx.onChange} />;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo-table">
|
|
||||||
<cl-table :ref="setRefs('table')" :columns="columns" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, ref } from "vue";
|
|
||||||
import { TableColumn } from "@cool-vue/crud/types";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
import Test2 from "./render/test2";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const { refs, setRefs } = useCool();
|
|
||||||
|
|
||||||
const columns = ref<TableColumn[]>([
|
|
||||||
{
|
|
||||||
type: "selection",
|
|
||||||
width: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "姓名",
|
|
||||||
prop: "name",
|
|
||||||
minWidth: 120,
|
|
||||||
component: Test2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "存款",
|
|
||||||
prop: "price",
|
|
||||||
sortable: true,
|
|
||||||
minWidth: 120,
|
|
||||||
component: {
|
|
||||||
name: "el-progress"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "文件",
|
|
||||||
prop: "urls",
|
|
||||||
component: {
|
|
||||||
name: "cl-link",
|
|
||||||
props: {
|
|
||||||
size: 50
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
minWidth: 120,
|
|
||||||
dict: [
|
|
||||||
{
|
|
||||||
label: "启用",
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "禁用",
|
|
||||||
value: 2,
|
|
||||||
type: "danger"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "创建时间",
|
|
||||||
prop: "createTime",
|
|
||||||
minWidth: 150,
|
|
||||||
component: {
|
|
||||||
name: "cl-date",
|
|
||||||
props: {
|
|
||||||
format: "YYYY-MM-DD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "操作",
|
|
||||||
type: "op"
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
onMounted(function () {});
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
columns
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.demo-table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo-upsert">
|
|
||||||
<cl-upsert ref="upsertRef" :items="items" :dialog="dialog" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { UpsertItem, UpsertRef } from "@cool-vue/crud/types";
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const upsertRef = ref<UpsertRef>();
|
|
||||||
|
|
||||||
const dialog = {
|
|
||||||
props: {
|
|
||||||
fullscreen: false,
|
|
||||||
width: "1200px"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const items = ref<UpsertItem[]>([
|
|
||||||
{
|
|
||||||
label: "多图",
|
|
||||||
prop: "imgs",
|
|
||||||
component: {
|
|
||||||
name: "cl-upload",
|
|
||||||
props: {
|
|
||||||
listType: "picture-card",
|
|
||||||
multiple: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "文件",
|
|
||||||
prop: "file",
|
|
||||||
component: {
|
|
||||||
name: "cl-upload",
|
|
||||||
props: {
|
|
||||||
listType: "text",
|
|
||||||
limit: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "开关",
|
|
||||||
prop: "switch",
|
|
||||||
component: {
|
|
||||||
name: "el-switch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "时间",
|
|
||||||
prop: "time",
|
|
||||||
hook: "datetimeRange",
|
|
||||||
component: {
|
|
||||||
name: "el-date-picker",
|
|
||||||
props: {
|
|
||||||
type: "datetimerange",
|
|
||||||
valueFormat: "YYYY-MM-DD HH:mm:ss",
|
|
||||||
defaultTime: [
|
|
||||||
new Date(2000, 1, 1, 0, 0, 0),
|
|
||||||
new Date(2000, 1, 1, 23, 59, 59)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
items,
|
|
||||||
upsertRef,
|
|
||||||
dialog
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>cl-context-menu</span>
|
|
||||||
右键菜单
|
|
||||||
</div>
|
|
||||||
<div class="c">
|
|
||||||
<p class="btn" @contextmenu.stop.prevent="open">右键点击</p>
|
|
||||||
</div>
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/10/23</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
methods: {
|
|
||||||
open(e) {
|
|
||||||
this.$crud.openContextMenu(e, {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "新增",
|
|
||||||
"suffix-icon": "el-icon-plus",
|
|
||||||
callback: (_, done) => {
|
|
||||||
this.$message.info("点击了新增");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "编辑",
|
|
||||||
"suffix-icon": "el-icon-edit",
|
|
||||||
callback: (_, done) => {
|
|
||||||
this.$message.info("点击了编辑");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "删除",
|
|
||||||
"suffix-icon": "el-icon-delete"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "二级",
|
|
||||||
"suffix-icon": "el-icon-right",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: "文本超出隐藏,有一天晚上",
|
|
||||||
ellipsis: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "禁用",
|
|
||||||
disabled: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "更多",
|
|
||||||
callback: (_, done) => {
|
|
||||||
this.$message.warning("开发中");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scope {
|
|
||||||
.btn {
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
font-size: 13px;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 5px 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>cl-crud</span>
|
|
||||||
增删改查,加强
|
|
||||||
</div>
|
|
||||||
<div class="c">
|
|
||||||
<router-link to="/crud">传送门</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/09/25</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>cl-editor-quill</span>
|
|
||||||
Quill 富文本编辑器
|
|
||||||
</div>
|
|
||||||
<div class="c">
|
|
||||||
<router-link to="/editor-quill">传送门</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/11/07</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>cl-upload</span>
|
|
||||||
图片上传
|
|
||||||
</div>
|
|
||||||
<div class="c">
|
|
||||||
<router-link to="/upload">传送门</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/09/25</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
avatar: "",
|
|
||||||
avatar2: ""
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
onSuccess() {
|
|
||||||
this.$message.success("上传成功");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scope {
|
|
||||||
.label {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 12px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>error-page</span>
|
|
||||||
错误页
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="c">
|
|
||||||
<router-link to="/403">403</router-link>
|
|
||||||
<router-link to="/404">404</router-link>
|
|
||||||
<router-link to="/500">500</router-link>
|
|
||||||
<router-link to="/502">502</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/10/25</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scope {
|
|
||||||
.c {
|
|
||||||
a {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>icon-svg</span>
|
|
||||||
svg图片库
|
|
||||||
</div>
|
|
||||||
<div class="c _svg">
|
|
||||||
<el-tooltip v-for="(item, index) in list" :key="index" content="icon-like">
|
|
||||||
<icon-svg :size="18" :name="`icon-${item}`" />
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/09/25</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
list: ["like", "video", "rank", "menu", "favor"]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="scope">
|
|
||||||
<div class="h">
|
|
||||||
<span>v-copy</span>
|
|
||||||
复制到剪贴板
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="c">
|
|
||||||
<el-button v-copy="'https://www.cool-admin.com/'" size="small">
|
|
||||||
https://www.cool-admin.com
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="f">
|
|
||||||
<span class="date">2019/09/25</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo">
|
|
||||||
<cl-crud @load="onLoad">
|
|
||||||
<el-row>
|
|
||||||
<cl-refresh-btn />
|
|
||||||
<cl-add-btn />
|
|
||||||
<cl-multi-delete-btn />
|
|
||||||
<demo-dialog />
|
|
||||||
<demo-context-menu />
|
|
||||||
<demo-form />
|
|
||||||
<demo-query />
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-search-key
|
|
||||||
field="name"
|
|
||||||
:field-list="[
|
|
||||||
{ label: '姓名', value: 'name' },
|
|
||||||
{ label: '年龄', value: 'age' }
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<demo-adv-search />
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<demo-table />
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<cl-flex1 />
|
|
||||||
<cl-pagination />
|
|
||||||
</el-row>
|
|
||||||
<demo-upsert />
|
|
||||||
</cl-crud>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import { TestService } from "../utils/service";
|
|
||||||
import Dialog from "../components/crud/dialog.vue";
|
|
||||||
import ContextMenu from "../components/crud/context-menu.vue";
|
|
||||||
import Query from "../components/crud/query.vue";
|
|
||||||
import AdvSearch from "../components/crud/adv-search.vue";
|
|
||||||
import Table from "../components/crud/table.vue";
|
|
||||||
import Upsert from "../components/crud/upsert.vue";
|
|
||||||
import Form from "../components/crud/form.vue";
|
|
||||||
import { CrudLoad } from "@cool-vue/crud/types";
|
|
||||||
export default defineComponent({
|
|
||||||
name: "crud",
|
|
||||||
components: {
|
|
||||||
"demo-dialog": Dialog,
|
|
||||||
"demo-context-menu": ContextMenu,
|
|
||||||
"demo-query": Query,
|
|
||||||
"demo-adv-search": AdvSearch,
|
|
||||||
"demo-table": Table,
|
|
||||||
"demo-upsert": Upsert,
|
|
||||||
"demo-form": Form
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
function onLoad({ ctx, app }: CrudLoad) {
|
|
||||||
ctx.service(TestService).done();
|
|
||||||
app.refresh();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
onLoad
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#app,
|
|
||||||
.demo {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo scroller1">
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col v-for="(item, index) in list" :key="index" :xs="24" :sm="12" :md="8" :lg="6">
|
|
||||||
<component :is="item" />
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import BClUpload from "../components/demo/b-cl-upload.vue";
|
|
||||||
import BVCopy from "../components/demo/b-v-copy.vue";
|
|
||||||
import BIconSvg from "../components/demo/b-icon-svg.vue";
|
|
||||||
import BClCrud from "../components/demo/b-cl-crud.vue";
|
|
||||||
import BClContextMenu from "../components/demo/b-cl-context-menu.vue";
|
|
||||||
import BErrorPage from "../components/demo/b-error-page.vue";
|
|
||||||
import BClEditorQuill from "../components/demo/b-cl-editor-quill.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "demo",
|
|
||||||
|
|
||||||
components: {
|
|
||||||
BClUpload,
|
|
||||||
BVCopy,
|
|
||||||
BIconSvg,
|
|
||||||
BClCrud,
|
|
||||||
BClContextMenu,
|
|
||||||
BErrorPage,
|
|
||||||
BClEditorQuill
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
list: [
|
|
||||||
"b-cl-upload",
|
|
||||||
"b-cl-crud",
|
|
||||||
"b-icon-svg",
|
|
||||||
"b-v-copy",
|
|
||||||
"b-cl-context-menu",
|
|
||||||
"b-error-page",
|
|
||||||
"b-cl-editor-quill"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.demo {
|
|
||||||
.scope {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
.h {
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
background-color: $color-primary;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
margin-right: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.c {
|
|
||||||
padding: 10px;
|
|
||||||
height: 50px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&._svg {
|
|
||||||
.icon-svg {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
width: 100%;
|
|
||||||
height: 1px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
left: 0;
|
|
||||||
background-color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.f {
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
.date {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="demo-upload scroller1">
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>文件空间</p>
|
|
||||||
<cl-upload-space v-model="urls" />
|
|
||||||
|
|
||||||
<p style="margin-top: 10px">选择的文件:</p>
|
|
||||||
|
|
||||||
<el-image
|
|
||||||
v-for="(item, index) in urlList"
|
|
||||||
:key="index"
|
|
||||||
:src="item"
|
|
||||||
:style="{ width: '100px', marginRight: '10px' }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>普通上传</p>
|
|
||||||
<cl-upload />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>指定类型上传 accept=.jpg,.png</p>
|
|
||||||
<cl-upload accept=".jpg,.png" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>多图上传 picture-card</p>
|
|
||||||
|
|
||||||
<cl-upload multiple :limit="3" list-type="picture-card" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>文件上传 text</p>
|
|
||||||
<cl-upload v-model="urls" multiple :limit="5" accept="*" list-type="text" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>自定义</p>
|
|
||||||
<cl-upload icon="el-icon-picture" text="选择图片" :size="[120, 200]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-upload__item">
|
|
||||||
<p>拖拽上传</p>
|
|
||||||
<cl-upload drag>
|
|
||||||
<i class="el-icon-upload"></i>
|
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
||||||
</cl-upload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "upload",
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const urls = ref<string>("");
|
|
||||||
const urlList = computed(() => urls.value.split(",").filter(Boolean));
|
|
||||||
|
|
||||||
return { urls, urlList };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.demo-upload {
|
|
||||||
.demo-upload__item {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
& > p {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,676 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="cl-upload__wrap">
|
|
||||||
<div
|
|
||||||
class="cl-upload"
|
|
||||||
:class="[
|
|
||||||
`cl-upload--${listType}`,
|
|
||||||
{
|
|
||||||
'is-multiple': multiple,
|
|
||||||
'is-drag': drag
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<el-input class="cl-upload__hidden" type="hidden" />
|
|
||||||
|
|
||||||
<el-upload
|
|
||||||
:action="action"
|
|
||||||
:accept="_accept"
|
|
||||||
:multiple="multiple"
|
|
||||||
:limit="limit"
|
|
||||||
:data="data"
|
|
||||||
:name="_name"
|
|
||||||
:drag="drag"
|
|
||||||
:list-type="listType"
|
|
||||||
:file-list="fileList"
|
|
||||||
:show-file-list="_showFileList"
|
|
||||||
:auto-upload="autoUpload"
|
|
||||||
:disabled="disabled"
|
|
||||||
v-loading="_loading"
|
|
||||||
:headers="{
|
|
||||||
Authorization: token,
|
|
||||||
...headers
|
|
||||||
}"
|
|
||||||
:http-request="action ? undefined : httpRequest"
|
|
||||||
:on-remove="_onRemove"
|
|
||||||
:on-preview="_onPreview"
|
|
||||||
:on-progress="onProgress"
|
|
||||||
:on-change="onChange"
|
|
||||||
:on-exceed="onExceed"
|
|
||||||
:before-upload="_beforeUpload"
|
|
||||||
:before-remove="beforeRemove"
|
|
||||||
:style="_style"
|
|
||||||
>
|
|
||||||
<slot>
|
|
||||||
<!-- 多图上传 -->
|
|
||||||
<template v-if="listType == 'picture-card'">
|
|
||||||
<i :class="['cl-upload__icon', _icon]"></i>
|
|
||||||
<span v-if="_text" class="cl-upload__text">{{ _text }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 文件上传 -->
|
|
||||||
<template v-else-if="listType == 'text'">
|
|
||||||
<el-button size="mini" type="primary" :loading="loading"
|
|
||||||
>点击上传</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 默认上传 -->
|
|
||||||
<template v-else>
|
|
||||||
<template v-if="_file">
|
|
||||||
<div class="cl-upload__cover">
|
|
||||||
<!-- 图片 -->
|
|
||||||
<img v-if="_file.type == 'image'" :src="_file.url" />
|
|
||||||
|
|
||||||
<!-- 文件名 -->
|
|
||||||
<span v-else>{{ _file.name }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 功能按钮 -->
|
|
||||||
<div class="cl-upload__actions">
|
|
||||||
<span
|
|
||||||
v-if="_file.type == 'image'"
|
|
||||||
class="cl-upload__actions-preview"
|
|
||||||
@click.stop="_onPreview(_file)"
|
|
||||||
>
|
|
||||||
<i class="el-icon-zoom-in"></i>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="cl-upload__actions-delete"
|
|
||||||
@click.stop="_onRemove(_file)"
|
|
||||||
>
|
|
||||||
<i class="el-icon-delete"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 空态 -->
|
|
||||||
<template v-else>
|
|
||||||
<i :class="['cl-upload__icon', _icon]"></i>
|
|
||||||
<span v-if="_text" class="cl-upload__text">{{ _text }}</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</slot>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<cl-dialog
|
|
||||||
v-model="preview.visible"
|
|
||||||
title="图片预览"
|
|
||||||
:props="{
|
|
||||||
width: previewWidth
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<img style="width: 100%" :src="preview.url" alt="" />
|
|
||||||
</cl-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapGetters } from "vuex";
|
|
||||||
import { clone, last, isArray, isNumber, isBoolean, basename } from "/@/cool/utils";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "cl-upload",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: [Array, String],
|
|
||||||
// 尺寸
|
|
||||||
size: [Array, String, Number],
|
|
||||||
// 显示图标
|
|
||||||
icon: String,
|
|
||||||
// 显示文案
|
|
||||||
text: String,
|
|
||||||
// 是否以 uuid 重命名
|
|
||||||
rename: {
|
|
||||||
type: Boolean,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
// 最大允许上传文件大小(MB)
|
|
||||||
limitSize: Number,
|
|
||||||
// 预览图片的宽度
|
|
||||||
previewWidth: {
|
|
||||||
type: String,
|
|
||||||
default: "500px"
|
|
||||||
},
|
|
||||||
// 上传的地址
|
|
||||||
action: {
|
|
||||||
type: String,
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
// 设置上传的请求头部
|
|
||||||
headers: Object,
|
|
||||||
// 是否多选
|
|
||||||
multiple: Boolean,
|
|
||||||
// 上传时附带的额外参数
|
|
||||||
data: Object,
|
|
||||||
// 上传的文件字段名
|
|
||||||
name: String,
|
|
||||||
// 支持发送 cookie 凭证信息
|
|
||||||
withCredentials: Boolean,
|
|
||||||
// 是否显示已上传文件列表
|
|
||||||
showFileList: {
|
|
||||||
type: Boolean,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
// 是否拖拽
|
|
||||||
drag: Boolean,
|
|
||||||
// 上传的文件类型
|
|
||||||
accept: String,
|
|
||||||
// 文件列表的类型
|
|
||||||
listType: {
|
|
||||||
type: String,
|
|
||||||
default: "default"
|
|
||||||
},
|
|
||||||
// 是否自带上传
|
|
||||||
autoUpload: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
// 是否禁用
|
|
||||||
disabled: Boolean,
|
|
||||||
// 最大允许上传个数
|
|
||||||
limit: Number,
|
|
||||||
// 文件列表移除文件时的钩子
|
|
||||||
onRemove: Function,
|
|
||||||
// 点击文件列表中已上传的文件时的钩子
|
|
||||||
onPreview: Function,
|
|
||||||
// 文件上传成功时的钩子
|
|
||||||
onSuccess: Function,
|
|
||||||
// 文件上传失败时的钩子
|
|
||||||
onError: Function,
|
|
||||||
// 文件上传时的钩子
|
|
||||||
onProgress: Function,
|
|
||||||
// 文件状态改变时的钩子
|
|
||||||
onChange: Function,
|
|
||||||
// 文件超出个数限制时的钩子
|
|
||||||
onExceed: Function,
|
|
||||||
// 上传文件之前的钩子
|
|
||||||
beforeUpload: Function,
|
|
||||||
// 删除文件之前的钩子
|
|
||||||
beforeRemove: Function,
|
|
||||||
// 是否拼接
|
|
||||||
urlJoin: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue", "change"],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fileList: [],
|
|
||||||
urls: [],
|
|
||||||
loading: false,
|
|
||||||
preview: {
|
|
||||||
visible: false,
|
|
||||||
url: ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters(["token", "modules"]),
|
|
||||||
|
|
||||||
conf() {
|
|
||||||
return this.modules.upload.options;
|
|
||||||
},
|
|
||||||
|
|
||||||
_size() {
|
|
||||||
return this.size || this.conf.size || "128px";
|
|
||||||
},
|
|
||||||
|
|
||||||
_icon() {
|
|
||||||
return this.icon || this.conf.icon || "el-icon-upload";
|
|
||||||
},
|
|
||||||
|
|
||||||
_text() {
|
|
||||||
return this.text === undefined ? this.conf.text : this.text;
|
|
||||||
},
|
|
||||||
|
|
||||||
_accept() {
|
|
||||||
const d = this.accept || this.conf.accept;
|
|
||||||
|
|
||||||
switch (this.listType) {
|
|
||||||
case "picture-card":
|
|
||||||
return d || "image/*";
|
|
||||||
default:
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_name() {
|
|
||||||
return this.name || this.conf.name || "file";
|
|
||||||
},
|
|
||||||
|
|
||||||
_limitSize() {
|
|
||||||
return this.limitSize || this.conf.limitSize || 10;
|
|
||||||
},
|
|
||||||
|
|
||||||
_rename() {
|
|
||||||
return isBoolean(this.rename) ? this.rename : this.conf.rename;
|
|
||||||
},
|
|
||||||
|
|
||||||
_showFileList() {
|
|
||||||
let f = null;
|
|
||||||
|
|
||||||
switch (this.listType) {
|
|
||||||
case "picture-card":
|
|
||||||
case "text":
|
|
||||||
f = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
f = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.showFileList === undefined ? f : this.showFileList;
|
|
||||||
},
|
|
||||||
|
|
||||||
_loading() {
|
|
||||||
return this.listType == "default" ? this.loading : false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_urls() {
|
|
||||||
return this.urls.filter((e) => Boolean(e.url));
|
|
||||||
},
|
|
||||||
|
|
||||||
_file() {
|
|
||||||
const d = this._urls[0];
|
|
||||||
|
|
||||||
if (d) {
|
|
||||||
const suf = last(d.url.split(".")).toLowerCase();
|
|
||||||
|
|
||||||
if (["bmp", "jpg", "jpeg", "png", "tif", "gif", "svg", "webp"].includes(suf)) {
|
|
||||||
d.type = "image";
|
|
||||||
}
|
|
||||||
|
|
||||||
return d;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_style() {
|
|
||||||
let arr = [];
|
|
||||||
|
|
||||||
if (isArray(this._size)) {
|
|
||||||
arr = this._size;
|
|
||||||
} else {
|
|
||||||
arr = [this._size, this._size];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [height, width] = arr.map((e) => (isNumber(e) ? `${e}px` : e));
|
|
||||||
|
|
||||||
if (this.listType == "default" && !this.drag) {
|
|
||||||
return {
|
|
||||||
height,
|
|
||||||
width
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
modelValue: {
|
|
||||||
immediate: true,
|
|
||||||
handler: "parseValue"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
// 解析参数
|
|
||||||
parseValue(value) {
|
|
||||||
let list = [];
|
|
||||||
|
|
||||||
if (this.multiple) {
|
|
||||||
if (value instanceof Array) {
|
|
||||||
list = value;
|
|
||||||
} else {
|
|
||||||
list = (value || "").split(",");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (value) {
|
|
||||||
list = [value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 比较数据,避免重复动画
|
|
||||||
if (
|
|
||||||
!this.urls.some((e) => {
|
|
||||||
return list.includes(e.url);
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
this.fileList = list.filter(Boolean).map((url) => {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
name: basename(url),
|
|
||||||
uid: url
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置 URLS
|
|
||||||
this.urls = clone(this.fileList);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 更新值
|
|
||||||
update() {
|
|
||||||
let urls = this.urls.filter((e) => Boolean(e.url)).map((e) => e.url);
|
|
||||||
|
|
||||||
// 是否拼接
|
|
||||||
if (this.urlJoin) {
|
|
||||||
urls = urls.join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit("update:modelValue", urls);
|
|
||||||
this.$emit("change", urls);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 追加文件
|
|
||||||
append(data) {
|
|
||||||
if (this.multiple) {
|
|
||||||
this.urls.push(data);
|
|
||||||
} else {
|
|
||||||
this.urls = [data];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
// 关闭上传加载中
|
|
||||||
done() {
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 删除文件
|
|
||||||
_onRemove(file) {
|
|
||||||
this.urls.splice(
|
|
||||||
this.urls.findIndex((e) => e.uid === file.uid),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
this.update();
|
|
||||||
|
|
||||||
// 删除文件之前的钩子
|
|
||||||
if (this.onRemove) {
|
|
||||||
this.onRemove(file, this.urls);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 预览图片
|
|
||||||
_onPreview(file) {
|
|
||||||
let url = "";
|
|
||||||
|
|
||||||
if (file.raw) {
|
|
||||||
if (file.raw.type.indexOf("image/") == 0) {
|
|
||||||
const item = this.urls.find((e) => e.uid == file.uid);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
url = item.url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (file.type == "image") {
|
|
||||||
url = file.url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
this.preview.visible = true;
|
|
||||||
this.preview.url = url;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 上传前
|
|
||||||
_beforeUpload(file) {
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
if (this._limitSize) {
|
|
||||||
if (file.size / 1024 / 1024 >= this._limitSize) {
|
|
||||||
this.$message.error(`上传文件大小不能超过 ${this._limitSize}MB!`);
|
|
||||||
this.done();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.beforeUpload) {
|
|
||||||
return this.beforeUpload(file, {
|
|
||||||
done: this.done
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 上传成功
|
|
||||||
_onSuccess(res, file) {
|
|
||||||
this.loading = false;
|
|
||||||
|
|
||||||
this.append({
|
|
||||||
url: res.data,
|
|
||||||
name: file.raw.name,
|
|
||||||
uid: file.raw.uid
|
|
||||||
});
|
|
||||||
|
|
||||||
// 文件上传成功时的钩子
|
|
||||||
if (this.onSuccess) {
|
|
||||||
this.onSuccess(res, file.raw, this.urls);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 重设上传请求
|
|
||||||
async httpRequest(req) {
|
|
||||||
const mode = await this.uploadMode();
|
|
||||||
|
|
||||||
// 多种上传请求
|
|
||||||
const upload = (file) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const next = (res) => {
|
|
||||||
const data = new FormData();
|
|
||||||
|
|
||||||
for (const i in res) {
|
|
||||||
if (i != "host") {
|
|
||||||
data.append(i, res[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let fileName = file.name;
|
|
||||||
|
|
||||||
// 是否以 uuid 重新命名
|
|
||||||
if (this._rename) {
|
|
||||||
fileName = uuidv4() + "." + last((file.name || "").split("."));
|
|
||||||
}
|
|
||||||
|
|
||||||
data.append("key", `app/${fileName}`);
|
|
||||||
data.append("file", file);
|
|
||||||
|
|
||||||
// 上传
|
|
||||||
this.service.base.common
|
|
||||||
.request({
|
|
||||||
url: res.host,
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data"
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
onUploadProgress: (e) => {
|
|
||||||
if (this.onProgress) {
|
|
||||||
e.percent = parseInt((e.loaded / e.total) * 100);
|
|
||||||
this.onProgress(e, req.file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((url) => {
|
|
||||||
if (mode === "local") {
|
|
||||||
resolve(url);
|
|
||||||
} else {
|
|
||||||
resolve(`${res.host}/app/${fileName}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mode == "local") {
|
|
||||||
next({
|
|
||||||
host: "/upload"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.service.base.common
|
|
||||||
.upload()
|
|
||||||
.then((res) => {
|
|
||||||
next(res);
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
await upload(req.file)
|
|
||||||
.then((url) => {
|
|
||||||
this._onSuccess({ data: url }, { raw: req.file });
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("upload error", err);
|
|
||||||
this.$message.error(err);
|
|
||||||
|
|
||||||
// 文件上传失败时的钩子
|
|
||||||
if (this.onError) {
|
|
||||||
this.onError(err, req.file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 上传模式
|
|
||||||
uploadMode() {
|
|
||||||
return this.service.base.common.uploadMode().then((res) => res.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.cl-upload {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
&__hidden {
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-multiple {
|
|
||||||
.cl-upload__wrap {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--default {
|
|
||||||
&:not(.is-drag) {
|
|
||||||
.el-upload {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 1px dashed #d9d9d9;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 28px;
|
|
||||||
color: #8c939d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload__cover {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload__actions {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
cursor: default;
|
|
||||||
text-align: center;
|
|
||||||
color: #fff;
|
|
||||||
opacity: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
-webkit-transition: opacity 0.3s;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
display: inline-block;
|
|
||||||
content: "";
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
span + span {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: $color-primary;
|
|
||||||
|
|
||||||
.cl-upload__actions {
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--picture-card {
|
|
||||||
.el-upload {
|
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
.cl-upload__icon {
|
|
||||||
position: relative;
|
|
||||||
top: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon + span {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__text {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,302 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="cl-upload-space-category"
|
|
||||||
:class="{
|
|
||||||
'is-position': browser.isMini,
|
|
||||||
'is-show': space.category.visible
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="cl-upload-space-category__search">
|
|
||||||
<el-button type="primary" size="mini" @click="edit()">添加分类</el-button>
|
|
||||||
|
|
||||||
<el-input v-model="keyword" placeholder="输入关键字过滤" size="mini" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cl-upload-space-category__list">
|
|
||||||
<ul class="scroller1">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in flist"
|
|
||||||
:key="index"
|
|
||||||
:class="{
|
|
||||||
'is-active': item.id == current
|
|
||||||
}"
|
|
||||||
@click="select(item.id)"
|
|
||||||
@contextmenu.stop.prevent="openContextMenu($event, item)"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<cl-form :ref="setRefs('form')" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { computed, defineComponent, inject, ref, watch } from "vue";
|
|
||||||
import { isEmpty } from "/@/cool/utils";
|
|
||||||
import { ContextMenu } from "@cool-vue/crud";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-upload-space-category",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: [Number, String]
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue", "change"],
|
|
||||||
|
|
||||||
setup(_, { emit }) {
|
|
||||||
const { refs, setRefs, service, store }: any = useCool();
|
|
||||||
|
|
||||||
const space = inject<any>("space");
|
|
||||||
|
|
||||||
// 数据列表
|
|
||||||
const list = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 当前选择
|
|
||||||
const current = ref<number | string>("");
|
|
||||||
|
|
||||||
// 搜索关键字
|
|
||||||
const keyword = ref<string>("");
|
|
||||||
|
|
||||||
// 过滤列表
|
|
||||||
const flist = computed(() => {
|
|
||||||
return list.value.filter((e: any) => e.name.includes(keyword.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 浏览器信息
|
|
||||||
const browser = computed(() => store.getters.browser);
|
|
||||||
|
|
||||||
// 监听选择变化
|
|
||||||
watch(
|
|
||||||
() => current.value,
|
|
||||||
(id: number | string) => {
|
|
||||||
emit("update:modelValue", id);
|
|
||||||
emit("change", id);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 刷新分类
|
|
||||||
function refresh() {
|
|
||||||
return service.space.type.list().then((res: any) => {
|
|
||||||
res.unshift({
|
|
||||||
name: "全部文件",
|
|
||||||
id: null
|
|
||||||
});
|
|
||||||
|
|
||||||
list.value = res;
|
|
||||||
|
|
||||||
if (!isEmpty(res)) {
|
|
||||||
if (!current.value) {
|
|
||||||
current.value = res[0].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑分类
|
|
||||||
function edit(item: any = {}) {
|
|
||||||
refs.value.form.open({
|
|
||||||
title: "添加分类",
|
|
||||||
width: "400px",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: "分类名称",
|
|
||||||
prop: "name",
|
|
||||||
value: item.name,
|
|
||||||
component: {
|
|
||||||
name: "el-input",
|
|
||||||
attrs: {
|
|
||||||
placeholder: "请填写分类名称"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: true,
|
|
||||||
message: "分类名称不能为空"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
on: {
|
|
||||||
submit: (data: any, { done, close }: any) => {
|
|
||||||
let next = null;
|
|
||||||
|
|
||||||
if (!item.id) {
|
|
||||||
next = service.space.type.add(data);
|
|
||||||
} else {
|
|
||||||
next = service.space.type.update({
|
|
||||||
...data,
|
|
||||||
id: item.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
next.then(() => {
|
|
||||||
refresh();
|
|
||||||
close();
|
|
||||||
}).catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择类目
|
|
||||||
function select(id: number) {
|
|
||||||
current.value = id;
|
|
||||||
|
|
||||||
// 小屏幕下收起左侧类目
|
|
||||||
if (browser.value.isMini) {
|
|
||||||
space.category.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开类目列表右键菜单
|
|
||||||
function openContextMenu(e: any, { id, name }: any) {
|
|
||||||
if (!id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenu.open(e, {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "刷新",
|
|
||||||
"suffix-icon": "el-icon-refresh",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
refresh();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "编辑",
|
|
||||||
"suffix-icon": "el-icon-edit",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
edit({ id, name });
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "删除",
|
|
||||||
"suffix-icon": "el-icon-delete",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
`此操作将删除【${name}】下的文件, 是否继续?`,
|
|
||||||
"提示",
|
|
||||||
{
|
|
||||||
type: "warning"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
service.space.type
|
|
||||||
.delete({
|
|
||||||
ids: [id]
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
|
|
||||||
if (id == current.value) {
|
|
||||||
current.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
return {
|
|
||||||
refs,
|
|
||||||
setRefs,
|
|
||||||
browser,
|
|
||||||
space,
|
|
||||||
list,
|
|
||||||
flist,
|
|
||||||
current,
|
|
||||||
keyword,
|
|
||||||
refresh,
|
|
||||||
edit,
|
|
||||||
select,
|
|
||||||
openContextMenu
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cl-upload-space-category {
|
|
||||||
height: 100%;
|
|
||||||
width: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: width 0.2s ease-in-out;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
&.is-show {
|
|
||||||
width: 250px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-position {
|
|
||||||
position: absolute;
|
|
||||||
left: 5px;
|
|
||||||
top: 51px;
|
|
||||||
height: calc(100% - 56px);
|
|
||||||
z-index: 3000;
|
|
||||||
|
|
||||||
&.is-show {
|
|
||||||
width: calc(100% - 10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__search {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__list {
|
|
||||||
height: calc(100% - 48px);
|
|
||||||
padding: 0 10px;
|
|
||||||
|
|
||||||
ul {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
font-size: 14px;
|
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
|
||||||
border-bottom: 1px dashed #eee;
|
|
||||||
padding: 0 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="cl-upload-space-item"
|
|
||||||
:class="[`is-${type}`]"
|
|
||||||
@click.stop.prevent="select"
|
|
||||||
@contextmenu.stop.prevent="openContextMenu"
|
|
||||||
>
|
|
||||||
<!-- 错误 -->
|
|
||||||
<template v-if="info.error">
|
|
||||||
<div class="cl-upload-space-item__error">上传失败:{{ info.error }}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 成功 -->
|
|
||||||
<template v-else>
|
|
||||||
<!-- 图片 -->
|
|
||||||
<template v-if="type === 'image'">
|
|
||||||
<el-image fit="cover" :src="info.url" lazy />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 视频 -->
|
|
||||||
<template v-else-if="type === 'video'">
|
|
||||||
<video
|
|
||||||
controls
|
|
||||||
:src="info.url"
|
|
||||||
:style="{
|
|
||||||
maxHeight: '100%',
|
|
||||||
maxWidth: '100%'
|
|
||||||
}"
|
|
||||||
></video>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 其他 -->
|
|
||||||
<template v-else>
|
|
||||||
<span>{{ info.url }}</span>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 大小 -->
|
|
||||||
<div class="cl-upload-space-item__size"></div>
|
|
||||||
|
|
||||||
<!-- 遮罩层 -->
|
|
||||||
<div v-if="isSelected" class="cl-upload-space-item__mask">
|
|
||||||
<span>{{ index + 1 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, inject } from "vue";
|
|
||||||
import { ContextMenu } from "@cool-vue/crud";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import Clipboard from "clipboard";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-upload-space-item",
|
|
||||||
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["select", "remove"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const space = inject<any>("space");
|
|
||||||
|
|
||||||
// 文件信息
|
|
||||||
const info = computed(() => props.modelValue);
|
|
||||||
|
|
||||||
// 已选的序号
|
|
||||||
const index = computed(() =>
|
|
||||||
space.selection.value.findIndex((e: any) => e.id === info.value.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 是否已选择
|
|
||||||
const isSelected = computed(() => index.value >= 0);
|
|
||||||
|
|
||||||
// 文件类型
|
|
||||||
const type = computed(() => (info.value.type || "").split("/")[0]);
|
|
||||||
|
|
||||||
// 选择
|
|
||||||
function select() {
|
|
||||||
emit("select", info.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除
|
|
||||||
function remove() {
|
|
||||||
emit("remove", info.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右键菜单
|
|
||||||
function openContextMenu(e: any) {
|
|
||||||
ContextMenu.open(e, {
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
label: "复制地址",
|
|
||||||
callback: (e: any, done: Function) => {
|
|
||||||
const clipboard: any = new Clipboard(e.target, {
|
|
||||||
text: () => info.value.url
|
|
||||||
});
|
|
||||||
|
|
||||||
clipboard.on("success", () => {
|
|
||||||
ElMessage.success("复制成功");
|
|
||||||
clipboard.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
clipboard.on("error", () => {
|
|
||||||
clipboard.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
clipboard.onClick(e);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: isSelected.value ? "取消选中" : "选中",
|
|
||||||
"suffix-icon": isSelected.value ? "el-icon-close" : "el-icon-check",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
select();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "删除",
|
|
||||||
"suffix-icon": "el-icon-delete",
|
|
||||||
callback: (_: any, done: Function) => {
|
|
||||||
remove();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
info,
|
|
||||||
index,
|
|
||||||
type,
|
|
||||||
isSelected,
|
|
||||||
select,
|
|
||||||
remove,
|
|
||||||
openContextMenu
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cl-upload-space-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 160px;
|
|
||||||
width: 160px;
|
|
||||||
max-width: calc(50% - 10px);
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
margin: 5px 10px 5px 0;
|
|
||||||
|
|
||||||
&.is-image {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-video {
|
|
||||||
video {
|
|
||||||
max-height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__size {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__error {
|
|
||||||
padding: 10px;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__mask {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
|
||||||
|
|
||||||
span {
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
top: 10px;
|
|
||||||
background-color: #67c23a;
|
|
||||||
color: #fff;
|
|
||||||
display: inline-block;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 20px;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,505 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="cl-upload-space__wrap">
|
|
||||||
<slot>
|
|
||||||
<el-button v-if="showButton" size="mini" @click="open">点击上传</el-button>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<!-- 弹框 -->
|
|
||||||
<cl-dialog
|
|
||||||
v-model="visible"
|
|
||||||
title="文件空间"
|
|
||||||
height="630px"
|
|
||||||
width="1000px"
|
|
||||||
keep-alive
|
|
||||||
:props="{
|
|
||||||
'close-on-click-modal': false,
|
|
||||||
'append-to-body': true,
|
|
||||||
customClass: 'dialog-upload-space'
|
|
||||||
}"
|
|
||||||
:controls="['slot-expand', 'cl-flex1', 'fullscreen', 'close']"
|
|
||||||
>
|
|
||||||
<div class="cl-upload-space">
|
|
||||||
<!-- 类目 -->
|
|
||||||
<category v-model="category.id" @change="refresh()" />
|
|
||||||
|
|
||||||
<!-- 内容 -->
|
|
||||||
<div class="cl-upload-space__content">
|
|
||||||
<!-- 操作栏 -->
|
|
||||||
<div class="cl-upload-space__header scroller1">
|
|
||||||
<el-button size="mini" @click="refresh()">刷新</el-button>
|
|
||||||
|
|
||||||
<cl-upload
|
|
||||||
style="margin: 0 10px"
|
|
||||||
list-type="slot"
|
|
||||||
:action="action"
|
|
||||||
:accept="accept"
|
|
||||||
:limit-size="limitSize"
|
|
||||||
:show-file-list="false"
|
|
||||||
:headers="headers"
|
|
||||||
:data="data"
|
|
||||||
:disabled="disabled"
|
|
||||||
:rename="rename"
|
|
||||||
:on-success="onSuccess"
|
|
||||||
:on-error="onError"
|
|
||||||
:on-progress="onProgress"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
>
|
|
||||||
<el-button size="mini" type="primary">点击上传</el-button>
|
|
||||||
</cl-upload>
|
|
||||||
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
size="mini"
|
|
||||||
:disabled="!isSelected"
|
|
||||||
@click="confirm()"
|
|
||||||
>使用选中文件 {{ limitTip }}</el-button
|
|
||||||
>
|
|
||||||
|
|
||||||
<el-button
|
|
||||||
type="danger"
|
|
||||||
size="mini"
|
|
||||||
:disabled="!isSelected"
|
|
||||||
@click="remove()"
|
|
||||||
>删除选中文件</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文件区域 -->
|
|
||||||
<div
|
|
||||||
v-loading="loading"
|
|
||||||
class="cl-upload-space__file scroller1"
|
|
||||||
element-loading-text="拼命加载中"
|
|
||||||
>
|
|
||||||
<!-- 文件列表 -->
|
|
||||||
<template v-if="list.length > 0">
|
|
||||||
<div class="cl-upload-space__file-list">
|
|
||||||
<file-item
|
|
||||||
v-for="item in list"
|
|
||||||
:key="item.id"
|
|
||||||
v-loading="item.loading"
|
|
||||||
:modelValue="item"
|
|
||||||
:element-loading-text="item.progress"
|
|
||||||
@select="select"
|
|
||||||
@remove="remove"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 空态 -->
|
|
||||||
<div v-else class="cl-upload-space__file-empty">
|
|
||||||
<cl-upload
|
|
||||||
drag
|
|
||||||
:action="action"
|
|
||||||
:accept="accept"
|
|
||||||
:limit-size="limitSize"
|
|
||||||
:headers="headers"
|
|
||||||
:data="data"
|
|
||||||
:disabled="disabled"
|
|
||||||
:rename="rename"
|
|
||||||
:on-success="onSuccess"
|
|
||||||
:on-error="onError"
|
|
||||||
:on-progress="onProgress"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
>
|
|
||||||
<i class="el-icon-upload"></i>
|
|
||||||
<div class="el-upload__text">
|
|
||||||
将文件拖到此处,或<em>点击上传</em>
|
|
||||||
</div>
|
|
||||||
</cl-upload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="cl-upload-space__footer">
|
|
||||||
<el-pagination
|
|
||||||
background
|
|
||||||
:page-size="pagination.size"
|
|
||||||
:current-page="pagination.page"
|
|
||||||
:total="pagination.total"
|
|
||||||
@current-change="onCurrentChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 展开按钮 -->
|
|
||||||
<template #slot-expand>
|
|
||||||
<button>
|
|
||||||
<i
|
|
||||||
v-if="category.visible"
|
|
||||||
class="el-icon-notebook-2"
|
|
||||||
@click="category.visible = false"
|
|
||||||
></i>
|
|
||||||
<i v-else class="el-icon-arrow-left" @click="category.visible = true"></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</cl-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, provide, reactive, ref, watch } from "vue";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { isEmpty } from "/@/cool/utils";
|
|
||||||
import Category from "./category.vue";
|
|
||||||
import FileItem from "./file-item.vue";
|
|
||||||
import { useCool } from "/@/cool";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "cl-upload-space",
|
|
||||||
|
|
||||||
components: {
|
|
||||||
Category,
|
|
||||||
FileItem
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
// 绑定值
|
|
||||||
modelValue: String,
|
|
||||||
// 上传的地址
|
|
||||||
action: String,
|
|
||||||
// 选择图片的长度
|
|
||||||
limit: {
|
|
||||||
type: Number,
|
|
||||||
default: 9
|
|
||||||
},
|
|
||||||
// 最大允许上传文件大小(MB)
|
|
||||||
limitSize: {
|
|
||||||
type: Number,
|
|
||||||
default: 10
|
|
||||||
},
|
|
||||||
// 是否禁用
|
|
||||||
disabled: Boolean,
|
|
||||||
// 是否以 uuid 重命名
|
|
||||||
rename: Boolean,
|
|
||||||
// 设置上传的请求头部
|
|
||||||
headers: Object,
|
|
||||||
// 上传时附带的额外参数
|
|
||||||
data: Object,
|
|
||||||
// 上传的文件类型
|
|
||||||
accept: String,
|
|
||||||
// 是否返回详细数据
|
|
||||||
detailData: Boolean,
|
|
||||||
// 是否显示按钮
|
|
||||||
showButton: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ["update:modelValue", "confirm"],
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { store, service } = useCool();
|
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
const visible = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 是否加载中
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 已选列表
|
|
||||||
const selection = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 文件列表
|
|
||||||
const list = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 类目数据
|
|
||||||
const category = reactive<any>({
|
|
||||||
id: "",
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 分页信息
|
|
||||||
const pagination = reactive<any>({
|
|
||||||
page: 1,
|
|
||||||
size: 12,
|
|
||||||
total: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// 浏览器信息
|
|
||||||
const browser = computed(() => store.getters.browser);
|
|
||||||
|
|
||||||
// 监听屏幕大小变化
|
|
||||||
watch(
|
|
||||||
() => browser.value.isMini,
|
|
||||||
(val) => {
|
|
||||||
category.visible = val ? false : true;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 提示信息
|
|
||||||
const limitTip = computed(() => selection.value.length + "/" + props.limit);
|
|
||||||
|
|
||||||
// 是否选中
|
|
||||||
const isSelected = computed(() => !isEmpty(selection.value));
|
|
||||||
|
|
||||||
// Provide
|
|
||||||
provide("space", {
|
|
||||||
category,
|
|
||||||
selection
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打开
|
|
||||||
function open() {
|
|
||||||
visible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空选择
|
|
||||||
function clear() {
|
|
||||||
selection.value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭
|
|
||||||
function close() {
|
|
||||||
visible.value = false;
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传成功
|
|
||||||
function onSuccess(res: any, file: any) {
|
|
||||||
const item = list.value.find((e: any) => file.uid == e.uid);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
item.url = res.data;
|
|
||||||
|
|
||||||
service.space.info
|
|
||||||
.add({
|
|
||||||
url: res.data,
|
|
||||||
type: item.type,
|
|
||||||
classifyId: item.classifyId
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
item.loading = false;
|
|
||||||
item.id = res.id;
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传失败
|
|
||||||
function onError(err: string, file: any) {
|
|
||||||
const item = list.value.find((e) => file.uid == e.uid);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
item.loading = false;
|
|
||||||
item.error = err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传前,添加文件
|
|
||||||
function beforeUpload({ tempFilePath, type, uid }: any) {
|
|
||||||
list.value.unshift({
|
|
||||||
url: tempFilePath,
|
|
||||||
type,
|
|
||||||
uid,
|
|
||||||
classifyId: category.id,
|
|
||||||
loading: true,
|
|
||||||
progress: "0%"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传进度
|
|
||||||
function onProgress({ percent }: any, file: any) {
|
|
||||||
const item = list.value.find(({ uid }: any) => uid == file.uid);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
item.progress = percent + "%";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新资源文件
|
|
||||||
async function refresh(params: any = {}) {
|
|
||||||
// 清空选择
|
|
||||||
clear();
|
|
||||||
|
|
||||||
// 加载中
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
await service.space.info
|
|
||||||
.page({
|
|
||||||
...pagination,
|
|
||||||
...params,
|
|
||||||
classifyId: category.id,
|
|
||||||
type: props.accept
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
Object.assign(pagination, res.pagination);
|
|
||||||
|
|
||||||
list.value = res.list.map((e: any) => {
|
|
||||||
return {
|
|
||||||
...e,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 加载完成
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认选中
|
|
||||||
function confirm() {
|
|
||||||
const urls = selection.value.map((e: any) => e.url).join(",");
|
|
||||||
|
|
||||||
emit("update:modelValue", urls);
|
|
||||||
emit("confirm", props.detailData ? selection.value : urls);
|
|
||||||
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择
|
|
||||||
function select(item: any) {
|
|
||||||
const index = selection.value.findIndex((e: any) => e.id === item.id);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
selection.value.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
if (selection.value.length < props.limit) {
|
|
||||||
selection.value.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除选中
|
|
||||||
function remove(item?: any) {
|
|
||||||
// 已选文件 id
|
|
||||||
const ids: number[] = item ? [item.id] : selection.value.map((e: any) => e.id);
|
|
||||||
|
|
||||||
ElMessageBox.confirm("此操作将删除文件, 是否继续?", "提示", {
|
|
||||||
type: "warning"
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
|
|
||||||
// 删除文件及选择
|
|
||||||
ids.forEach((id) => {
|
|
||||||
[list.value, selection.value].forEach((list) => {
|
|
||||||
const index = list.findIndex((e: any) => e.id === id);
|
|
||||||
list.splice(index, 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除请求
|
|
||||||
service.space.info
|
|
||||||
.delete({
|
|
||||||
ids
|
|
||||||
})
|
|
||||||
.catch((err: string) => {
|
|
||||||
ElMessage.error(err);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面切换
|
|
||||||
function onCurrentChange(page: number) {
|
|
||||||
refresh({ page });
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
visible,
|
|
||||||
loading,
|
|
||||||
selection,
|
|
||||||
list,
|
|
||||||
category,
|
|
||||||
pagination,
|
|
||||||
browser,
|
|
||||||
limitTip,
|
|
||||||
isSelected,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
refresh,
|
|
||||||
remove,
|
|
||||||
confirm,
|
|
||||||
select,
|
|
||||||
onSuccess,
|
|
||||||
onError,
|
|
||||||
beforeUpload,
|
|
||||||
onProgress,
|
|
||||||
onCurrentChange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.dialog-upload-space {
|
|
||||||
.el-dialog {
|
|
||||||
&__body {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload-space {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
padding: 5px;
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 50px;
|
|
||||||
overflow: auto hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__file {
|
|
||||||
height: calc(100% - 100px);
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-empty {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(50% - 90px);
|
|
||||||
left: calc(50% - 160px);
|
|
||||||
|
|
||||||
.cl-upload {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.el-upload-dragger {
|
|
||||||
height: 180px;
|
|
||||||
width: 320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 67px;
|
|
||||||
color: #c0c4cc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
padding: 9px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
export default {
|
|
||||||
// 上传的地址
|
|
||||||
action: "",
|
|
||||||
// 上传的文件类型
|
|
||||||
accept: "",
|
|
||||||
// 上传的文件字段名
|
|
||||||
name: "file",
|
|
||||||
// 尺寸
|
|
||||||
size: "128px",
|
|
||||||
// 显示图标
|
|
||||||
icon: "el-icon-picture",
|
|
||||||
// 显示文案
|
|
||||||
text: "选择文件",
|
|
||||||
// 上传大小限制
|
|
||||||
limitSize: 2,
|
|
||||||
// 是否已 uuid 重新命名
|
|
||||||
rename: true
|
|
||||||
};
|
|
||||||
89
src/cool/router/base.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import {
|
||||||
|
createRouter,
|
||||||
|
createWebHashHistory,
|
||||||
|
createWebHistory,
|
||||||
|
NavigationGuardNext,
|
||||||
|
RouteRecordRaw
|
||||||
|
} from "vue-router";
|
||||||
|
import { storage } from "/@/cool";
|
||||||
|
import { useBaseStore } from "/$/base";
|
||||||
|
import { routerMode } from "/@/cool/config";
|
||||||
|
|
||||||
|
// 忽略
|
||||||
|
const ignore: any = {
|
||||||
|
token: ["/login", "/403", "/404", "/500", "/502"]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 默认路由
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "index",
|
||||||
|
component: () => import("/$/base/pages/layout/index.vue"),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "数据统计",
|
||||||
|
component: () => import("/@/views/home/index.vue")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/:catchAll(.*)",
|
||||||
|
name: "404",
|
||||||
|
redirect: "/404"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
const router = createRouter({
|
||||||
|
history: routerMode == "history" ? createWebHistory() : createWebHashHistory(),
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
// 路由守卫
|
||||||
|
router.beforeEach((to: any, _: any, next: NavigationGuardNext) => {
|
||||||
|
const { user, process } = useBaseStore();
|
||||||
|
|
||||||
|
if (user.token) {
|
||||||
|
if (to.path.includes("/login")) {
|
||||||
|
// 登录成功且 token 未过期,回到首页
|
||||||
|
if (!storage.isExpired("token")) {
|
||||||
|
return next("/");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 添加路由进程
|
||||||
|
process.add({
|
||||||
|
keepAlive: to.meta?.keepAlive,
|
||||||
|
label: to.meta?.label || to.name,
|
||||||
|
value: to.fullPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!ignore.token.find((e: string) => to.path == e)) {
|
||||||
|
return next("/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
let lock = false;
|
||||||
|
|
||||||
|
// 错误监听
|
||||||
|
router.onError((err: any) => {
|
||||||
|
if (!lock) {
|
||||||
|
lock = true;
|
||||||
|
|
||||||
|
ElMessage.error("页面不存在或者未配置!");
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
lock = false;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export { router };
|
||||||
|
export * from "./views";
|
||||||
2
src/cool/router/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./base";
|
||||||
|
export * from "./views";
|
||||||
47
src/cool/router/views.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { cloneDeep, isArray } from "lodash";
|
||||||
|
import { router } from "./base";
|
||||||
|
|
||||||
|
const views = import.meta.globEager("/src/**/views/**/*.vue");
|
||||||
|
|
||||||
|
for (const i in views) {
|
||||||
|
views[i.slice(5)] = views[i];
|
||||||
|
delete views[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addViews(data: any[] | any) {
|
||||||
|
// 列表
|
||||||
|
const list = isArray(data) ? data : [data];
|
||||||
|
|
||||||
|
list.forEach((e: any) => {
|
||||||
|
const d: any = cloneDeep(e);
|
||||||
|
|
||||||
|
// 命名
|
||||||
|
d.name = d.router;
|
||||||
|
|
||||||
|
if (!d.component) {
|
||||||
|
const url = d.viewPath;
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
if (
|
||||||
|
/^(http[s]?:\/\/)([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i.test(
|
||||||
|
url
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
d.meta.iframeUrl = url;
|
||||||
|
d.component = () => import(`/$/base/pages/iframe/index.vue`);
|
||||||
|
} else {
|
||||||
|
d.component = () => Promise.resolve(views[url.replace("cool/", "")]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.redirect = "/404";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量添加
|
||||||
|
router.addRoute("index", d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getViews() {
|
||||||
|
return router.getRoutes().find((e) => e.name == "index")?.children;
|
||||||
|
}
|
||||||
127
src/cool/service/base.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { baseUrl, isDev, test } from "../config";
|
||||||
|
import { isObject } from "../utils";
|
||||||
|
import request from "./request";
|
||||||
|
|
||||||
|
export function Service(
|
||||||
|
value:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
namespace?: string;
|
||||||
|
url?: string;
|
||||||
|
mock?: boolean;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return function (target: any) {
|
||||||
|
// 命名
|
||||||
|
if (typeof value == "string") {
|
||||||
|
target.prototype.namespace = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复杂项
|
||||||
|
if (isObject(value)) {
|
||||||
|
target.prototype.namespace = value.namespace;
|
||||||
|
target.prototype.mock = value.mock;
|
||||||
|
|
||||||
|
if (value.url) {
|
||||||
|
target.prototype.url = value.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseService {
|
||||||
|
constructor(
|
||||||
|
options = {} as {
|
||||||
|
namespace?: string;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (options?.namespace) {
|
||||||
|
this.namespace = options.namespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request(
|
||||||
|
options = {} as {
|
||||||
|
params?: any;
|
||||||
|
data?: any;
|
||||||
|
url: string;
|
||||||
|
method?: "GET" | "get" | "POST" | "post" | string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (!options.params) options.params = {};
|
||||||
|
|
||||||
|
let ns = "";
|
||||||
|
|
||||||
|
// 是否 mock 模式
|
||||||
|
if (this.mock || test.mock) {
|
||||||
|
// 测试
|
||||||
|
} else {
|
||||||
|
if (isDev) {
|
||||||
|
ns = this.proxy || baseUrl;
|
||||||
|
} else {
|
||||||
|
ns = this.proxy ? this.url : baseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接前缀
|
||||||
|
if (this.namespace) {
|
||||||
|
ns += "/" + this.namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 http
|
||||||
|
if (options.url.indexOf("http") !== 0) {
|
||||||
|
options.url = ns + options.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
list(data: any) {
|
||||||
|
return this.request({
|
||||||
|
url: "/list",
|
||||||
|
method: "POST",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
page(data: { page?: number; size?: number; [key: string]: any }) {
|
||||||
|
return this.request({
|
||||||
|
url: "/page",
|
||||||
|
method: "POST",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
info(params: { id?: number | string; [key: string]: any }) {
|
||||||
|
return this.request({
|
||||||
|
url: "/info",
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(data: { id?: number | string; [key: string]: any }) {
|
||||||
|
return this.request({
|
||||||
|
url: "/update",
|
||||||
|
method: "POST",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(data: { ids?: number[] | string[]; [key: string]: any }) {
|
||||||
|
return this.request({
|
||||||
|
url: "/delete",
|
||||||
|
method: "POST",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add(data: any) {
|
||||||
|
return this.request({
|
||||||
|
url: "/add",
|
||||||
|
method: "POST",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
204
src/cool/service/eps.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { isDev, test } from "../config";
|
||||||
|
import { BaseService } from "./base";
|
||||||
|
import { storage, toCamel } from "../utils";
|
||||||
|
|
||||||
|
// 获取标签名
|
||||||
|
function getNames(v: any) {
|
||||||
|
return Object.getOwnPropertyNames(v.constructor.prototype).filter(
|
||||||
|
(e) => !["namespace", "constructor", "request"].includes(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签名
|
||||||
|
const names = getNames(new BaseService());
|
||||||
|
|
||||||
|
export function useEps(service: Service) {
|
||||||
|
// 创建描述文件
|
||||||
|
function createDts(list: any[]) {
|
||||||
|
function deep(v: any) {
|
||||||
|
for (const i in v) {
|
||||||
|
if (v[i].namespace) {
|
||||||
|
v[i].namespace = v[i].namespace;
|
||||||
|
|
||||||
|
// 模块
|
||||||
|
const item: any = list.find((e: any) => e.prefix.includes(v[i].namespace));
|
||||||
|
|
||||||
|
// 接口
|
||||||
|
const api: any[] = item ? item.api : [];
|
||||||
|
|
||||||
|
// 获取方法集合
|
||||||
|
[...names, ...getNames(v[i])].forEach((e) => {
|
||||||
|
if (!api.find((a) => a.path.includes(e))) {
|
||||||
|
api.push({
|
||||||
|
path: `/${e}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item.api = api;
|
||||||
|
} else {
|
||||||
|
list.push({
|
||||||
|
prefix: `/${v[i].namespace}`,
|
||||||
|
api
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deep(v[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deep(service);
|
||||||
|
|
||||||
|
// 本地服务
|
||||||
|
return service.request({
|
||||||
|
url: `http://localhost:${__SERVER_PORT__}/__cool_eps`,
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
service,
|
||||||
|
list
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 eps
|
||||||
|
function getEps() {
|
||||||
|
if (isDev) {
|
||||||
|
service
|
||||||
|
.request({
|
||||||
|
url: "/admin/base/open/eps"
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
storage.set("eps", res);
|
||||||
|
set(res, true);
|
||||||
|
console.log("[Eps] 初始化成功。");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("[Eps] 获取失败!", err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置
|
||||||
|
async function set(d: any, c?: boolean) {
|
||||||
|
// 接口列表
|
||||||
|
const list: any[] = [];
|
||||||
|
|
||||||
|
if (!d) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const i in d) {
|
||||||
|
d[i].forEach((e: any) => {
|
||||||
|
// 分隔路径
|
||||||
|
const arr = e.prefix
|
||||||
|
.replace(/\//, "")
|
||||||
|
.replace("admin", "")
|
||||||
|
.split("/")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(toCamel);
|
||||||
|
|
||||||
|
// 遍历
|
||||||
|
function deep(d: any, i: number) {
|
||||||
|
const k = arr[i];
|
||||||
|
|
||||||
|
if (k) {
|
||||||
|
// 是否最后一个
|
||||||
|
if (arr[i + 1]) {
|
||||||
|
if (!d[k]) {
|
||||||
|
d[k] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
deep(d[k], i + 1);
|
||||||
|
} else {
|
||||||
|
// 本地不存在则创建实例
|
||||||
|
if (!d[k]) {
|
||||||
|
d[k] = new BaseService({
|
||||||
|
namespace: e.prefix.substr(1, e.prefix.length - 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建方法
|
||||||
|
e.api.forEach((a: any) => {
|
||||||
|
// 方法名
|
||||||
|
const n = (a.name || a.path).replace("/", "");
|
||||||
|
|
||||||
|
// 过滤
|
||||||
|
if (!names.includes(n)) {
|
||||||
|
// 本地不存在则创建
|
||||||
|
if (!d[k][n]) {
|
||||||
|
if (n && !/[-:]/g.test(n)) {
|
||||||
|
d[k][n] = function (data: any) {
|
||||||
|
return this.request({
|
||||||
|
url: a.path,
|
||||||
|
method: a.method,
|
||||||
|
[a.method.toLocaleLowerCase() == "post"
|
||||||
|
? "data"
|
||||||
|
: "params"]: data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建权限
|
||||||
|
if (!d[k].permission) {
|
||||||
|
d[k].permission = {};
|
||||||
|
|
||||||
|
for (const i in d[k]) {
|
||||||
|
d[k].permission[i] = `${d[k].namespace.replace(
|
||||||
|
"admin/",
|
||||||
|
""
|
||||||
|
)}/${i}`.replace(/\//g, ":");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deep(service, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDev && c) {
|
||||||
|
await createDts(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
if (isDev) {
|
||||||
|
// 缓存数据
|
||||||
|
set(storage.get("eps"));
|
||||||
|
|
||||||
|
// 接口数据
|
||||||
|
if (test.eps) {
|
||||||
|
getEps();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const eps: any[] = [];
|
||||||
|
|
||||||
|
JSON.parse(__EPS__).forEach((e: any) => {
|
||||||
|
const [prefix, api] = e;
|
||||||
|
|
||||||
|
eps.push({
|
||||||
|
prefix,
|
||||||
|
api: api.map((e: string[]) => {
|
||||||
|
const [method, path, name] = e;
|
||||||
|
|
||||||
|
return {
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
name
|
||||||
|
};
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 文件数据
|
||||||
|
set({ eps });
|
||||||
|
}
|
||||||
|
}
|
||||||