mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2025-12-13 14:12:50 +00:00
发布6.0
This commit is contained in:
parent
76c7045cf8
commit
43f37ca564
@ -1,2 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
@ -1,21 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
yarn.lock
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
@ -1,6 +1 @@
|
||||
/public/
|
||||
/dist/
|
||||
/node_modules/
|
||||
/src/icons/svg/
|
||||
/mock/
|
||||
vue.config.js
|
||||
vite.config.ts
|
||||
66
.eslintrc.js
66
.eslintrc.js
@ -1,14 +1,66 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
extends: ["plugin:vue/essential", "@vue/prettier"],
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
"comma-dangle": [2, "never"]
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser"
|
||||
parser: "@typescript-eslint/parser",
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
jsxPragma: "React",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
tsx: true
|
||||
}
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"vue/component-name-in-template-casing": ["error", "kebab-case"],
|
||||
"vue/component-definition-name-casing": ["error", "kebab-case"],
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^h$",
|
||||
varsIgnorePattern: "^h$"
|
||||
}
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^h$",
|
||||
varsIgnorePattern: "^h$"
|
||||
}
|
||||
],
|
||||
"space-before-function-paren": "off",
|
||||
"vue/attributes-order": "off",
|
||||
"vue/one-component-per-file": "off",
|
||||
"vue/html-closing-bracket-newline": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/multiline-html-element-content-newline": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/attribute-hyphenation": "off",
|
||||
"vue/html-self-closing": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/v-on-event-hyphenation": "off"
|
||||
}
|
||||
};
|
||||
|
||||
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.vue text eol=lf
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@ -1,20 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
pnpm-lock.yaml
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
|
||||
15
.vscode/config.code-snippets
vendored
Normal file
15
.vscode/config.code-snippets
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"module-config": {
|
||||
"prefix": "module-config",
|
||||
"scope": "typescript",
|
||||
"body": [
|
||||
"import { ModuleConfig } from \"/@/cool\";",
|
||||
"",
|
||||
"export default (): ModuleConfig => {",
|
||||
" return {};",
|
||||
"};",
|
||||
""
|
||||
],
|
||||
"description": "module config snippets"
|
||||
}
|
||||
}
|
||||
61
.vscode/crud.code-snippets
vendored
61
.vscode/crud.code-snippets
vendored
@ -1,10 +1,11 @@
|
||||
{
|
||||
"cl-crud": {
|
||||
"prefix": "cl-crud",
|
||||
"scope": "vue",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <cl-crud ref=\"crud\" @load=\"onLoad\">",
|
||||
" <el-row type=\"flex\" align=\"middle\">",
|
||||
" <cl-crud ref=\"Crud\">",
|
||||
" <cl-row>",
|
||||
" <!-- 刷新按钮 -->",
|
||||
" <cl-refresh-btn />",
|
||||
" <!-- 新增按钮 -->",
|
||||
@ -14,45 +15,49 @@
|
||||
" <cl-flex1 />",
|
||||
" <!-- 关键字搜索 -->",
|
||||
" <cl-search-key />",
|
||||
" </el-row>",
|
||||
" </cl-row>",
|
||||
"",
|
||||
" <el-row>",
|
||||
" <cl-row>",
|
||||
" <!-- 数据表格 -->",
|
||||
" <cl-table v-bind=\"table\"></cl-table>",
|
||||
" </el-row>",
|
||||
" <cl-table ref=\"Table\" />",
|
||||
" </cl-row>",
|
||||
"",
|
||||
" <el-row type=\"flex\">",
|
||||
" <cl-row>",
|
||||
" <cl-flex1 />",
|
||||
" <!-- 分页控件 -->",
|
||||
" <cl-pagination />",
|
||||
" </el-row>",
|
||||
" </cl-row>",
|
||||
"",
|
||||
" <!-- 新增、编辑 -->",
|
||||
" <cl-upsert ref=\"upsert\" v-bind=\"upsert\"></cl-upsert>",
|
||||
" <cl-upsert ref=\"Upsert\" />",
|
||||
" </cl-crud>",
|
||||
"</template>",
|
||||
"",
|
||||
"<script>",
|
||||
"export default {",
|
||||
" data() {",
|
||||
" return {",
|
||||
" // 新增、编辑配置",
|
||||
" upsert: {",
|
||||
" items: []",
|
||||
" },",
|
||||
" // 表格配置",
|
||||
" table: {",
|
||||
" columns: []",
|
||||
" }",
|
||||
" };",
|
||||
"<script lang=\"ts\" name=\"菜单名称\" setup>",
|
||||
"import { useCrud, useTable, useUpsert } from \"@cool-vue/crud\";",
|
||||
"import { useCool } from \"/@/cool\";",
|
||||
"",
|
||||
"const { service } = useCool();",
|
||||
"",
|
||||
"// cl-upsert",
|
||||
"const Upsert = useUpsert({",
|
||||
" items: []",
|
||||
"});",
|
||||
"",
|
||||
"// cl-table",
|
||||
"const Table = useTable({",
|
||||
" columns: []",
|
||||
"});",
|
||||
"",
|
||||
"// cl-crud",
|
||||
"const Crud = useCrud(",
|
||||
" {",
|
||||
" service: service.demo.goods",
|
||||
" },",
|
||||
" methods: {",
|
||||
" onLoad({ ctx, app }) {",
|
||||
" ctx.service(${1}).done();",
|
||||
" app.refresh();",
|
||||
" }",
|
||||
" (app) => {",
|
||||
" app.refresh();",
|
||||
" }",
|
||||
"};",
|
||||
");",
|
||||
"</script>",
|
||||
""
|
||||
],
|
||||
|
||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
"editor.formatOnSave": true,
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
FROM node:lts-alpine
|
||||
WORKDIR /build
|
||||
# 设置Node-Sass的镜像地址
|
||||
RUN npm config set sass_binary_site https://repo.huaweicloud.com/node-sass
|
||||
RUN npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
|
||||
# 设置npm镜像
|
||||
RUN npm config set registry https://repo.huaweicloud.com/repository/npm/
|
||||
RUN npm config set registry https://registry.npm.taobao.org
|
||||
COPY package.json /build/package.json
|
||||
RUN npm install
|
||||
RUN yarn
|
||||
COPY ./ /build
|
||||
RUN npm run build
|
||||
|
||||
|
||||
246
README.md
246
README.md
@ -1,10 +1,10 @@
|
||||
# cool-admin [vue2]
|
||||
# cool-admin [vue3 - ts - vite]
|
||||
|
||||
<p align="center">
|
||||
<a href="https://show.cool-admin.com/" target="blank"><img src="https://admin.cool-js.com/logo.png" width="200" alt="cool-admin Logo" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">cool-admin 一个很酷的后台权限管理系统,开源免费,模块化、插件化、极速开发 CRUD,方便快速构建迭代后台管理系统, 到论坛 进一步了解</p>
|
||||
<p align="center">cool-admin 一个很酷的后台权限管理系统,开源免费,模块化、插件化、极速开发 CRUD,方便快速构建迭代后台管理系统, 到<a href="https://cool-js.com" target="_blank">文档</a> 进一步了解</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/cool-team-official/cool-admin-vue/blob/master/LICENSE" target="_blank"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="GitHub license" />
|
||||
@ -38,18 +38,6 @@
|
||||
|
||||
<img width="260" src="https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/wechat.jpeg" alt="Admin Wechat"></a>
|
||||
|
||||
## 微信公众号
|
||||
|
||||
<img width="260" src="https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/mp.jpg" alt="Admin Wechat"></a>
|
||||
|
||||
## 在线社区
|
||||
|
||||
[https://bbs.cool-js.com/](https://bbs.cool-js.com/)
|
||||
|
||||
## 使用条件
|
||||
|
||||
请确保您的操作系统上安装了 Node.js(> = 8.9.0)、@vue/cli (> 3.0.0)。
|
||||
|
||||
## 安装项目依赖
|
||||
|
||||
推荐使用 `yarn`:
|
||||
@ -69,233 +57,9 @@ yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass
|
||||
安装过程完成后,运行以下命令启动服务。您可以在浏览器中预览网站 [http://localhost:9000](http://localhost:9000)
|
||||
|
||||
```shell
|
||||
yarn serve
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## 极速 CRUD
|
||||
### 低价服务器
|
||||
|
||||
1. `vscode` 编辑器下输入关键字 `cl-crud`,会生成对应的模板代码:
|
||||
|
||||
```html
|
||||
<template>
|
||||
<cl-crud ref="crud" @load="onLoad">
|
||||
<el-row type="flex" align="middle">
|
||||
<!-- 刷新按钮 -->
|
||||
<cl-refresh-btn />
|
||||
<!-- 新增按钮 -->
|
||||
<cl-add-btn />
|
||||
<!-- 删除按钮 -->
|
||||
<cl-multi-delete-btn />
|
||||
<cl-flex1 />
|
||||
<!-- 关键字搜索 -->
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<!-- 数据表格 -->
|
||||
<cl-table v-bind="table"></cl-table>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<!-- 分页控件 -->
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
|
||||
<!-- 新增、编辑 -->
|
||||
<cl-upsert ref="upsert" v-bind="upsert"></cl-upsert>
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 新增、编辑配置
|
||||
upsert: {
|
||||
items: []
|
||||
},
|
||||
// 表格配置
|
||||
table: {
|
||||
columns: []
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
// crud 配置
|
||||
ctx.service().done();
|
||||
// 发送 page 接口请求
|
||||
app.refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
2. 编辑数据表格 `cl-table`:
|
||||
|
||||
```js
|
||||
{
|
||||
table: {
|
||||
// 参数与 el-table-column 一致,并支持许多骚操作
|
||||
columns: [
|
||||
// 多选列
|
||||
{
|
||||
type: "selection",
|
||||
width: 60
|
||||
},
|
||||
// 自定义列
|
||||
{
|
||||
label: "昵称",
|
||||
prop: "name"
|
||||
},
|
||||
{
|
||||
label: "账户",
|
||||
prop: "price",
|
||||
sortable: "custom" // 是否添加排序
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
// 字典匹配,使用 el-tag 展示
|
||||
dict: [
|
||||
{
|
||||
label: "启用",
|
||||
value: 1,
|
||||
type: "primary"
|
||||
},
|
||||
{
|
||||
label: "禁用",
|
||||
value: 0,
|
||||
type: "danger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
prop: "createTime"
|
||||
},
|
||||
// 操作按钮列,默认包含:编辑、删除
|
||||
{
|
||||
type: "op"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 编辑新增、编辑表单 `cl-upsert`:
|
||||
|
||||
```js
|
||||
{
|
||||
upsert: {
|
||||
items: [
|
||||
{
|
||||
label: "昵称",
|
||||
prop: "name",
|
||||
// 参数与 el-form-item 一致
|
||||
props: {},
|
||||
value: "神仙都没用", // 昵称默认值
|
||||
// 渲染参数,支持 slot, 组件实例,jsx
|
||||
component: {
|
||||
name: "el-input", // 可以是任意已注册的组件名
|
||||
props: {}, // 组件的参数
|
||||
on: {} // 组件的回调事件
|
||||
},
|
||||
// 验证规则,与 el-form 一致
|
||||
rules: {
|
||||
required: true,
|
||||
message: "昵称不呢为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "存款",
|
||||
prop: "price",
|
||||
component: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
min: 0,
|
||||
max: 10000
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
value: 1,
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "启用",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "禁用",
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. 绑定 `service`。在 `src/service/` 下新建文件 `test.js`,并编辑:
|
||||
|
||||
```js
|
||||
// src/service/test.js
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
|
||||
// 请求接口的路径
|
||||
@Service("test")
|
||||
class Test extends BaseService {
|
||||
// 继承 BaseService 后,拥有 page, list, add, delete, update, info 6个基本接口
|
||||
|
||||
// 自定义其他接口
|
||||
@Permission("product") // 权限装饰器,可选
|
||||
product(id) {
|
||||
// this.request() 参数与 axios 一致
|
||||
return this.request({
|
||||
url: "/product",
|
||||
method: "POST",
|
||||
data: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Test;
|
||||
```
|
||||
|
||||
在 `src/service/` 下的文件,框架会自动根据 `目录结构` 和 `文件名称` 格式化成对应的 `$service` 对象。你现在可以这么使用它:
|
||||
|
||||
```js
|
||||
this.$service.test.page({ page: 1 });
|
||||
this.$service.test.product(1);
|
||||
```
|
||||
|
||||
`service` 编写好后,我们把它绑定在 `crud` 上:
|
||||
|
||||
```js
|
||||
export default {
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
// 绑定 service,这边指定到 test 即可
|
||||
ctx.service(this.$service.test).done();
|
||||
|
||||
// 发起 page 请求
|
||||
app.refresh({
|
||||
// 请求默认参数
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
5. 效果预览
|
||||
|
||||

|
||||
[阿里云、腾讯云、华为云低价云服务器,不限新老](https://cool-js.com/ad/server.html)
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/app"],
|
||||
plugins: [
|
||||
["jsx-v-model"],
|
||||
[
|
||||
"component",
|
||||
{
|
||||
libraryName: "element-ui",
|
||||
styleLibraryName: "theme-chalk"
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
68
build/cool/index.ts
Normal file
68
build/cool/index.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { Plugin } from "vite";
|
||||
import { parseJson } from "./utils";
|
||||
import { createEps, createMenu, createSvg, createTag, getEps } from "./lib";
|
||||
|
||||
export function cool(): Plugin {
|
||||
return {
|
||||
name: "vite-cool",
|
||||
enforce: "pre",
|
||||
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;
|
||||
|
||||
switch (req.url) {
|
||||
// 快速创建菜单
|
||||
case "/__cool_createMenu":
|
||||
next = createMenu(body);
|
||||
break;
|
||||
|
||||
// 创建描述文件
|
||||
case "/__cool_eps":
|
||||
next = createEps(body);
|
||||
break;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
next.then((data: any) => {
|
||||
done({
|
||||
code: 1000,
|
||||
data
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
done({
|
||||
code: 1001,
|
||||
message: err.message
|
||||
});
|
||||
});
|
||||
} else {
|
||||
done({
|
||||
code: 1000
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
},
|
||||
transform(code, id) {
|
||||
return createTag(code, id);
|
||||
},
|
||||
transformIndexHtml(html) {
|
||||
return createSvg(html);
|
||||
},
|
||||
config() {
|
||||
return {
|
||||
define: {
|
||||
__EPS__: getEps()
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
35
build/cool/lib/eps/config.ts
Normal file
35
build/cool/lib/eps/config.ts
Normal file
@ -0,0 +1,35 @@
|
||||
export default {
|
||||
entity: {
|
||||
mapping: [
|
||||
{
|
||||
// 自定义匹配
|
||||
custom: ({ entityName, propertyName, type }) => {
|
||||
// status 原本是tinyint,如果是1的话,== true 是可以的,但是不能 === true,请谨慎使用
|
||||
if (propertyName === "status" && type == "tinyint") return "boolean";
|
||||
// 如果没有,返回null或者不返回,则继续遍历其他匹配规则
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
test: ["varchar", "text", "simple-json"]
|
||||
},
|
||||
{
|
||||
type: "string[]",
|
||||
test: ["simple-array"]
|
||||
},
|
||||
{
|
||||
type: "Date",
|
||||
test: ["datetime", "date"]
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
test: ["tinyint", "int", "decimal"]
|
||||
},
|
||||
{
|
||||
type: "BigInt",
|
||||
test: ["bigint"]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
311
build/cool/lib/eps/index.ts
Normal file
311
build/cool/lib/eps/index.ts
Normal file
@ -0,0 +1,311 @@
|
||||
import prettier from "prettier";
|
||||
import { isEmpty, last } from "lodash";
|
||||
import { createDir, firstUpperCase, readFile, toCamel } from "../../utils";
|
||||
import { createWriteStream } from "fs";
|
||||
import { join } from "path";
|
||||
import config from "./config";
|
||||
|
||||
interface Options {
|
||||
list: {
|
||||
prefix: string;
|
||||
name: string;
|
||||
columns: any[];
|
||||
api: {
|
||||
name: string;
|
||||
method: string;
|
||||
path: string;
|
||||
summary: string;
|
||||
dts: {
|
||||
parameters: {
|
||||
description: string;
|
||||
schema: {
|
||||
type: string;
|
||||
};
|
||||
name: string;
|
||||
required: boolean;
|
||||
}[];
|
||||
};
|
||||
}[];
|
||||
}[];
|
||||
service: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
// 临时目录路径
|
||||
const tempPath = join(__dirname, "../../temp");
|
||||
|
||||
// 获取类型
|
||||
function getType({ entityName, propertyName, type }) {
|
||||
for (const map of config.entity.mapping) {
|
||||
if (map.custom) {
|
||||
const resType = map.custom({ entityName, propertyName, type });
|
||||
if (resType) return resType;
|
||||
}
|
||||
if (map.test) {
|
||||
if (map.test.includes(type)) return map.type;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// 创建 Entity
|
||||
function createEntity({ list }: Options) {
|
||||
const t0: any[] = [];
|
||||
|
||||
for (const item of list) {
|
||||
if (!item.name) continue;
|
||||
const t = [`interface ${item.name} {`];
|
||||
for (const col of item.columns || []) {
|
||||
// 描述
|
||||
t.push("\n");
|
||||
t.push("/**\n");
|
||||
t.push(` * ${col.comment}\n`);
|
||||
t.push(" */\n");
|
||||
t.push(
|
||||
`${col.propertyName}?: ${getType({
|
||||
entityName: item.name,
|
||||
propertyName: col.propertyName,
|
||||
type: col.type
|
||||
})};`
|
||||
);
|
||||
}
|
||||
t.push("\n");
|
||||
t.push("/**\n");
|
||||
t.push(` * 任意键值\n`);
|
||||
t.push(" */\n");
|
||||
t.push(`[key: string]: any;`);
|
||||
t.push("}");
|
||||
t0.push(t);
|
||||
}
|
||||
|
||||
return t0.map((e) => e.join("")).join("\n\n");
|
||||
}
|
||||
|
||||
// 创建 Service
|
||||
function createService({ list, service }: Options) {
|
||||
const t0: any[] = [];
|
||||
|
||||
const t1 = [
|
||||
`type Service = {
|
||||
request(options?: {
|
||||
url: string;
|
||||
method?: 'POST' | 'GET' | string;
|
||||
data?: any;
|
||||
params?: any;
|
||||
proxy?: boolean;
|
||||
[key: string]: any;
|
||||
}): Promise<any>;
|
||||
`
|
||||
];
|
||||
|
||||
// 处理数据
|
||||
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) => (e.prefix || "").includes(d[i].namespace));
|
||||
|
||||
if (item) {
|
||||
const t = [`interface ${name} {`];
|
||||
|
||||
t1.push(`${i}: ${name};`);
|
||||
|
||||
// 插入方法
|
||||
if (item.api) {
|
||||
// 权限列表
|
||||
const permission: string[] = [];
|
||||
|
||||
item.api.forEach((a) => {
|
||||
// 方法名
|
||||
const n = toCamel(a.name || last(a.path.split("/")) || "").replace(
|
||||
/[:\/-]/g,
|
||||
""
|
||||
);
|
||||
|
||||
if (n) {
|
||||
// 参数类型
|
||||
let q: string[] = [];
|
||||
|
||||
// 参数列表
|
||||
const { parameters = [] } = a.dts || {};
|
||||
|
||||
parameters.forEach((p) => {
|
||||
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 = "";
|
||||
|
||||
// 实体名
|
||||
const en = item.name || "any";
|
||||
|
||||
switch (a.path) {
|
||||
case "/page":
|
||||
res = `
|
||||
{
|
||||
pagination: { size: number; page: number; total: number };
|
||||
list: ${en} [];
|
||||
[key: string]: any;
|
||||
}
|
||||
`;
|
||||
break;
|
||||
|
||||
case "/list":
|
||||
res = `${en} []`;
|
||||
break;
|
||||
|
||||
case "/info":
|
||||
res = en;
|
||||
break;
|
||||
|
||||
default:
|
||||
res = "any";
|
||||
break;
|
||||
}
|
||||
|
||||
// 描述
|
||||
t.push("\n");
|
||||
t.push("/**\n");
|
||||
t.push(` * ${a.summary || n}\n`);
|
||||
t.push(" */\n");
|
||||
|
||||
t.push(
|
||||
`${n}(data${q.length == 1 ? "?" : ""}: ${q.join(
|
||||
""
|
||||
)}): Promise<${res}>;`
|
||||
);
|
||||
}
|
||||
|
||||
permission.push(n);
|
||||
});
|
||||
|
||||
// 权限标识
|
||||
t.push("\n");
|
||||
t.push("/**\n");
|
||||
t.push(" * 权限标识\n");
|
||||
t.push(" */\n");
|
||||
t.push(
|
||||
`permission: { ${permission.map((e) => `${e}: string;`).join("\n")} };`
|
||||
);
|
||||
|
||||
// 权限状态
|
||||
t.push("\n");
|
||||
t.push("/**\n");
|
||||
t.push(" * 权限状态\n");
|
||||
t.push(" */\n");
|
||||
t.push(
|
||||
`_permission: { ${permission
|
||||
.map((e) => `${e}: boolean;`)
|
||||
.join("\n")} };`
|
||||
);
|
||||
|
||||
// 请求
|
||||
t.push("\n");
|
||||
t.push("/**\n");
|
||||
t.push(" * 请求\n");
|
||||
t.push(" */\n");
|
||||
t.push(`request: Service['request']`);
|
||||
}
|
||||
|
||||
t.push("}");
|
||||
t0.push(t);
|
||||
}
|
||||
} else {
|
||||
t1.push(`${i}: {`);
|
||||
deep(d[i], name);
|
||||
t1.push(`},`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深度
|
||||
deep(service);
|
||||
|
||||
// 结束
|
||||
t1.push("}");
|
||||
|
||||
// 追加
|
||||
t0.push(t1);
|
||||
|
||||
return t0.map((e) => e.join("")).join("\n\n");
|
||||
}
|
||||
|
||||
// 创建描述文件
|
||||
export async function createEps(options: Options) {
|
||||
// 文件内容
|
||||
const text = `
|
||||
declare namespace Eps {
|
||||
${createEntity(options)}
|
||||
${createService(options)}
|
||||
}
|
||||
`;
|
||||
|
||||
// 文本内容
|
||||
const content = prettier.format(text, {
|
||||
parser: "typescript",
|
||||
useTabs: true,
|
||||
tabWidth: 4,
|
||||
endOfLine: "lf",
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
printWidth: 100,
|
||||
trailingComma: "none"
|
||||
});
|
||||
|
||||
// 创建 temp 目录
|
||||
createDir(tempPath);
|
||||
|
||||
// 创建 eps 描述文件
|
||||
createWriteStream(join(tempPath, "eps.d.ts"), {
|
||||
flags: "w"
|
||||
}).write(content);
|
||||
|
||||
// 创建 eps 数据文件
|
||||
createWriteStream(join(tempPath, "eps.json"), {
|
||||
flags: "w"
|
||||
}).write(
|
||||
JSON.stringify(
|
||||
(options.list || []).map((e) => {
|
||||
const req = e.api.map((a) => {
|
||||
const arr = [a.name ? `/${a.name}` : a.path];
|
||||
|
||||
if (a.method) {
|
||||
arr.push(a.method);
|
||||
}
|
||||
|
||||
return arr;
|
||||
});
|
||||
|
||||
return [e.prefix, e.name || "", req];
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 获取描述
|
||||
export function getEps() {
|
||||
return JSON.stringify(readFile(join(tempPath, "eps.json")));
|
||||
}
|
||||
4
build/cool/lib/index.ts
Normal file
4
build/cool/lib/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./eps";
|
||||
export * from "./menu";
|
||||
export * from "./svg";
|
||||
export * from "./tag";
|
||||
34
build/cool/lib/menu/index.ts
Normal file
34
build/cool/lib/menu/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { createWriteStream } from "fs";
|
||||
import prettier from "prettier";
|
||||
import { join } from "path";
|
||||
import { mkdirs } from "../../utils";
|
||||
|
||||
// 创建文件
|
||||
export async function createMenu(options: { viewPath: string; code: string }) {
|
||||
// 格式化内容
|
||||
const content = prettier.format(options.code, {
|
||||
parser: "vue",
|
||||
useTabs: true,
|
||||
tabWidth: 4,
|
||||
endOfLine: "lf",
|
||||
semi: true,
|
||||
jsxBracketSameLine: true,
|
||||
singleQuote: false,
|
||||
printWidth: 100,
|
||||
trailingComma: "none"
|
||||
});
|
||||
|
||||
// 目录路径
|
||||
const dir = (options.viewPath || "").split("/");
|
||||
|
||||
// 文件名
|
||||
const fname = dir.pop();
|
||||
|
||||
// 创建目录
|
||||
const path = mkdirs(`./src/${dir.join("/")}`);
|
||||
|
||||
// 创建文件
|
||||
createWriteStream(join(path, fname || "demo"), {
|
||||
flags: "w"
|
||||
}).write(content);
|
||||
}
|
||||
54
build/cool/lib/svg/index.ts
Normal file
54
build/cool/lib/svg/index.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { readFileSync, readdirSync } from "fs";
|
||||
import { extname } from "path";
|
||||
|
||||
function findFiles(dir: string): string[] {
|
||||
const res: string[] = [];
|
||||
const dirs = readdirSync(dir, {
|
||||
withFileTypes: true
|
||||
});
|
||||
for (const d of dirs) {
|
||||
if (d.isDirectory()) {
|
||||
res.push(...findFiles(dir + d.name + "/"));
|
||||
} else {
|
||||
if (extname(d.name) == ".svg") {
|
||||
const svg = readFileSync(dir + d.name)
|
||||
.toString()
|
||||
.replace(/(\r)|(\n)/g, "")
|
||||
.replace(/<svg([^>+].*?)>/, (_: any, $2: any) => {
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let content = $2.replace(
|
||||
/(width|height)="([^>+].*?)"/g,
|
||||
(_: any, s2: any, s3: any) => {
|
||||
if (s2 === "width") {
|
||||
width = s3;
|
||||
} else if (s2 === "height") {
|
||||
height = s3;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
);
|
||||
if (!/(viewBox="[^>+].*?")/g.test($2)) {
|
||||
content += `viewBox="0 0 ${width} ${height}"`;
|
||||
}
|
||||
return `<symbol id="icon-${d.name.replace(".svg", "")}" ${content}>`;
|
||||
})
|
||||
.replace("</svg>", "</symbol>");
|
||||
res.push(svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function createSvg(html: string) {
|
||||
const res = findFiles("./src/modules/");
|
||||
|
||||
return html.replace(
|
||||
"<body>",
|
||||
`<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
|
||||
${res.join("")}
|
||||
</svg>`
|
||||
);
|
||||
}
|
||||
32
build/cool/lib/tag/index.ts
Normal file
32
build/cool/lib/tag/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { parse, compileScript } from "@vue/compiler-sfc";
|
||||
import magicString from "magic-string";
|
||||
|
||||
export function createTag(code: string, id: string) {
|
||||
if (/\.vue$/.test(id)) {
|
||||
let s: any;
|
||||
const str = () => s || (s = new magicString(code));
|
||||
const { descriptor } = parse(code);
|
||||
|
||||
if (!descriptor.script && descriptor.scriptSetup) {
|
||||
const res = compileScript(descriptor, { id });
|
||||
const { name, lang }: any = res.attrs;
|
||||
|
||||
str().appendLeft(
|
||||
0,
|
||||
`<script lang="${lang}">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: "${name}"
|
||||
})
|
||||
<\/script>`
|
||||
);
|
||||
|
||||
return {
|
||||
map: str().generateMap(),
|
||||
code: str().toString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
1712
build/cool/temp/eps.d.ts
vendored
Normal file
1712
build/cool/temp/eps.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
build/cool/temp/eps.json
Normal file
1
build/cool/temp/eps.json
Normal file
@ -0,0 +1 @@
|
||||
[["/admin/base/comm","",[["/personUpdate","post"],["/uploadMode","get"],["/permmenu","get"],["/person","get"],["/upload","post"],["/logout","post"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/open","",[["/refreshToken","get"],["/captcha","get"],["/login","post"],["/html","get"],["/eps","get"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/sys/department","BaseSysDepartmentEntity",[["/delete","post"],["/update","post"],["/order","post"],["/list","post"],["/add","post"],["/page"],["/info"]]],["/admin/base/sys/log","BaseSysLogEntity",[["/setKeep","post"],["/getKeep","get"],["/clear","post"],["/page","post"],["/list"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/sys/menu","BaseSysMenuEntity",[["/create","post"],["/delete","post"],["/update","post"],["/parse","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/base/sys/param","BaseSysParamEntity",[["/delete","post"],["/update","post"],["/html","get"],["/info","get"],["/page","post"],["/add","post"],["/list"]]],["/admin/base/sys/role","BaseSysRoleEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/base/sys/user","BaseSysUserEntity",[["/delete","post"],["/update","post"],["/move","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/db","CloudDBEntity",[["/initEntity","post"],["/delete","post"],["/update","post"],["/data","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/func/info","CloudFuncInfoEntity",[["/invoke","post"],["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/func/log","CloudFuncLogEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/demo/goods","DemoGoodsEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/dict/info","DictInfoEntity",[["/delete","post"],["/update","post"],["/data","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/dict/type","DictTypeEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/iot/device","IotDeviceEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/iot/message","IotMessageEntity",[["/page","post"],["/list"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/iot/mqtt","",[["/publish","post"],["/config","get"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/recycle/data","RecycleDataEntity",[["/restore","post"],["/info","get"],["/page","post"],["/list"],["/update"],["/delete"],["/add"]]],["/admin/space/info","SpaceInfoEntity",[["/getConfig","get"],["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/space/type","SpaceTypeEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/task/info","TaskInfoEntity",[["/delete","post"],["/update","post"],["/start","post"],["/once","post"],["/stop","post"],["/info","get"],["/page","post"],["/log","get"],["/add","post"],["/list"]]],["/chat/message","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/chat/session","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/test","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]]]
|
||||
70
build/cool/utils/index.ts
Normal file
70
build/cool/utils/index.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import fs from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
// 首字母大写
|
||||
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): Promise<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({});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 深度创建目录
|
||||
export function mkdirs(path: string) {
|
||||
const arr = path.split("/");
|
||||
let p = "";
|
||||
|
||||
arr.forEach((e) => {
|
||||
const t = join(p, e);
|
||||
|
||||
try {
|
||||
fs.statSync(t);
|
||||
} catch (err) {
|
||||
try {
|
||||
fs.mkdirSync(t);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
p = t;
|
||||
});
|
||||
|
||||
return p;
|
||||
}
|
||||
163
index.html
Normal file
163
index.html
Normal file
@ -0,0 +1,163 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="referer" content="never" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<title></title>
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.preload__wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
letter-spacing: 1px;
|
||||
background-color: #2f3447;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.preload__container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.preload__name {
|
||||
font-size: 30px;
|
||||
color: #fff;
|
||||
letter-spacing: 5px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.preload__title {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
margin: 30px 0 20px 0;
|
||||
}
|
||||
|
||||
.preload__sub-title {
|
||||
color: #ababab;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.preload__footer {
|
||||
text-align: center;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
.preload__footer a {
|
||||
font-size: 12px;
|
||||
color: #ababab;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.preload__loading {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border-radius: 30px;
|
||||
border: 7px solid currentColor;
|
||||
border-bottom-color: #2f3447 !important;
|
||||
position: relative;
|
||||
animation: r 1s infinite cubic-bezier(0.17, 0.67, 0.83, 0.67),
|
||||
bc 2s infinite ease-in;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
@keyframes r {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.preload__loading::after,
|
||||
.preload__loading::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
border-radius: 10px;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
.preload__loading::after {
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.preload__loading::before {
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
@keyframes bc {
|
||||
0% {
|
||||
color: #689cc5;
|
||||
}
|
||||
|
||||
25% {
|
||||
color: #b3b7e2;
|
||||
}
|
||||
|
||||
50% {
|
||||
color: #93dbe9;
|
||||
}
|
||||
|
||||
75% {
|
||||
color: #abbd81;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: #689cc5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="preload__wrap" id="Loading">
|
||||
<div class="preload__container">
|
||||
<p class="preload__name">COOL-ADMIN</p>
|
||||
<div class="preload__loading"></div>
|
||||
<p class="preload__title">正在加载资源...</p>
|
||||
<p class="preload__sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
|
||||
</div>
|
||||
|
||||
<div class="preload__footer">
|
||||
<a href="https://cool-js.com/" target="_blank"> https://cool-js.com </a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
41
nginx.conf
41
nginx.conf
@ -14,6 +14,9 @@ http {
|
||||
access_log /var/log/nginx/access.log main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
upstream backend {
|
||||
server midway:8001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
@ -25,7 +28,7 @@ http {
|
||||
}
|
||||
location /api/
|
||||
{
|
||||
proxy_pass http://midway:7001/;
|
||||
proxy_pass http://backend/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
@ -48,6 +51,42 @@ http {
|
||||
|
||||
#expires 12h;
|
||||
}
|
||||
# location /im {
|
||||
# proxy_pass http://backend/im;
|
||||
# proxy_connect_timeout 3600s; #配置点1
|
||||
# proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
|
||||
# proxy_send_timeout 3600s; #配置点3
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header REMOTE-HOST $remote_addr;
|
||||
# #proxy_bind $remote_addr transparent;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection "upgrade";
|
||||
# # rewrite /socket/(.*) /$1 break;
|
||||
# proxy_redirect off;
|
||||
|
||||
# }
|
||||
|
||||
# location /socket {
|
||||
# proxy_pass http://backend/socket;
|
||||
# proxy_connect_timeout 3600s; #配置点1
|
||||
# proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
|
||||
# proxy_send_timeout 3600s; #配置点3
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header REMOTE-HOST $remote_addr;
|
||||
# #proxy_bind $remote_addr transparent;
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection "upgrade";
|
||||
# rewrite /socket/(.*) /$1 break;
|
||||
# proxy_redirect off;
|
||||
|
||||
# }
|
||||
|
||||
|
||||
location /adminer/
|
||||
{
|
||||
|
||||
108
package.json
108
package.json
@ -1,65 +1,67 @@
|
||||
{
|
||||
"name": "cool-admin-vue",
|
||||
"version": "3.2.2",
|
||||
"name": "front-next",
|
||||
"version": "6.0.0",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"report": "vue-cli-service build --report",
|
||||
"lint": "vue-cli-service lint",
|
||||
"inspect": "vue inspect --mode=production > output.js"
|
||||
"dev": "vite --host",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:eslint": "eslint \"{src}/**/*.{vue,ts,tsx}\" --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"cl-admin": "^1.5.3",
|
||||
"cl-admin-crud": "^1.6.15",
|
||||
"cl-admin-theme": "^0.0.5",
|
||||
"clipboard": "^2.0.7",
|
||||
"codemirror": "^5.59.4",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.10.4",
|
||||
"echarts": "^5.0.2",
|
||||
"element-ui": "^2.15.1",
|
||||
"js-beautify": "^1.13.5",
|
||||
"@cool-vue/crud": "^6.1.7",
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@vueuse/core": "^9.1.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^1.3.4",
|
||||
"core-js": "^3.23.5",
|
||||
"dayjs": "^1.11.7",
|
||||
"echarts": "^5.3.3",
|
||||
"element-plus": "2.2.28",
|
||||
"file-saver": "^2.0.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"monaco-editor": "^0.36.0",
|
||||
"mqtt": "^4.3.7",
|
||||
"nprogress": "^0.2.0",
|
||||
"qs": "^6.9.1",
|
||||
"pinia": "^2.0.28",
|
||||
"quill": "^1.3.7",
|
||||
"socket.io-client": "2.3.1",
|
||||
"socket.io-client": "^4.5.1",
|
||||
"store": "^2.0.12",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-cron": "^1.0.9",
|
||||
"vue-echarts": "^6.0.0-rc.3",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.4.0"
|
||||
"ts-wps": "^1.0.5",
|
||||
"vue": "^3.2.47",
|
||||
"vue-echarts": "^6.2.3",
|
||||
"vue-router": "^4.1.6",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^3.0.0",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
|
||||
"@vue/babel-preset-jsx": "^1.1.2",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/composition-api": "^1.0.0-rc.5",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-plugin-jsx-v-model": "^2.0.3",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"hard-source-webpack-plugin": "^0.13.1",
|
||||
"node-sass": "^4.12.0",
|
||||
"prettier": "^1.19.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"svg-sprite-loader": "^5.0.0",
|
||||
"typescript": "^3.9.3",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack-cli": "^3.3.12"
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mockjs": "^1.0.6",
|
||||
"@types/node": "^18.0.6",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/quill": "^2.0.10",
|
||||
"@types/store": "^2.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.27.0",
|
||||
"prettier": "^2.8.4",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"sass": "^1.53.0",
|
||||
"terser": "^5.16.3",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.1.4",
|
||||
"vite-plugin-compression": "^0.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
@ -1,99 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="referer" content="never" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||
<title>COOL-ADMIN</title>
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
|
||||
<% } %>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.preload {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
letter-spacing: 1px;
|
||||
background-color: #2f3447;
|
||||
}
|
||||
|
||||
.preload .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.preload .name {
|
||||
font-size: 30px;
|
||||
color: #fff;
|
||||
letter-spacing: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.preload .title {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.preload .sub-title {
|
||||
color: #ababab;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.preload .footer {
|
||||
text-align: center;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
.preload .footer a {
|
||||
font-size: 12px;
|
||||
color: #ababab;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but cool-admin doesn't work properly without JavaScript enabled. Please
|
||||
enable it to continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<div class="preload">
|
||||
<div class="container">
|
||||
<p class="name">COOL-ADMIN</p>
|
||||
<p class="title">正在加载资源...</p>
|
||||
<p class="sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<a href="https://cool-js.com/" target="_blank"> https://cool-js.com </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
13169
public/theme/black.css
13169
public/theme/black.css
File diff suppressed because it is too large
Load Diff
13215
public/theme/blue.css
13215
public/theme/blue.css
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
13215
public/theme/green.css
13215
public/theme/green.css
File diff suppressed because it is too large
Load Diff
13154
public/theme/purple.css
13154
public/theme/purple.css
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view />
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<style lang="scss" src="./assets/css/index.scss"></style>
|
||||
<script lang="ts" setup>
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||
</script>
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
$primary: #4165d7;
|
||||
|
||||
$color-primary: var(--color-primary, $primary);
|
||||
$color-success: #67c23a;
|
||||
$color-danger: #f56c6c;
|
||||
$color-info: #909399;
|
||||
$color-warning: #e6a23c;
|
||||
|
||||
:export {
|
||||
colorPrimary: $primary;
|
||||
colorSuccess: $color-success;
|
||||
colorDanger: $color-danger;
|
||||
colorInfo: $color-info;
|
||||
colorWarning: $color-warning;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
$--color-primary: $primary;
|
||||
$--color-success: $color-success;
|
||||
$--color-danger: $color-danger;
|
||||
$--color-warning: $color-warning;
|
||||
$--color-info: $color-info;
|
||||
|
||||
$--font-path: "~element-ui/lib/theme-chalk/fonts";
|
||||
|
||||
@import "~element-ui/packages/theme-chalk/src/index";
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
@ -1,50 +0,0 @@
|
||||
import store from "store";
|
||||
import { getUrlParam } from "cl-admin/utils";
|
||||
|
||||
// 路由模式
|
||||
export const routerMode = "history";
|
||||
|
||||
// 开发模式
|
||||
export const isDev = process.env.NODE_ENV == "development";
|
||||
|
||||
// Host
|
||||
export const host = "https://show.cool-admin.com";
|
||||
|
||||
// Socket
|
||||
export const socketUrl = (isDev ? `${host}` : "") + "/socket";
|
||||
|
||||
// 请求地址,本地会使用代理请求
|
||||
export const baseUrl = (function() {
|
||||
let proxy = getUrlParam("proxy");
|
||||
|
||||
if (proxy) {
|
||||
store.set("proxy", proxy);
|
||||
} else {
|
||||
proxy = store.get("proxy") || "dev";
|
||||
}
|
||||
|
||||
return isDev ? `/${proxy}/admin` : `/api/admin`;
|
||||
})();
|
||||
|
||||
// 阿里字体图标库 https://at.alicdn.com/t/**.css
|
||||
export const iconfontUrl = ``;
|
||||
|
||||
// 程序配置参数
|
||||
export const app = store.get("__app__") || {
|
||||
name: "COOL-ADMIN",
|
||||
|
||||
conf: {
|
||||
showAMenu: false, // 是否显示一级菜单栏
|
||||
showRouteNav: true, // 是否显示路由导航栏
|
||||
showProcess: true, // 是否显示页面进程栏
|
||||
customMenu: false // 自定义菜单
|
||||
},
|
||||
|
||||
theme: {
|
||||
color: "", // 主题色
|
||||
url: "" // 主题样式地址
|
||||
}
|
||||
};
|
||||
|
||||
// 自定义菜单列表
|
||||
export const menuList = [];
|
||||
205
src/cool/bootstrap/eps.ts
Normal file
205
src/cool/bootstrap/eps.ts
Normal file
@ -0,0 +1,205 @@
|
||||
import { isDev, config } from "../config";
|
||||
import { BaseService, service } from "../service";
|
||||
import { toCamel } from "../utils";
|
||||
import { hmr } from "../hook";
|
||||
import { isArray, isEmpty, isObject } from "lodash-es";
|
||||
|
||||
// 获取标签名
|
||||
function getNames(v: any) {
|
||||
return [...Object.getOwnPropertyNames(v.constructor.prototype), ...Object.keys(v)].filter(
|
||||
(e) => !["namespace", "constructor", "request", "permission"].includes(e)
|
||||
);
|
||||
}
|
||||
|
||||
// 标签名
|
||||
const names = getNames(new BaseService());
|
||||
|
||||
// 创建
|
||||
export async function createEps() {
|
||||
// 创建描述文件
|
||||
function createDts(list: any[]) {
|
||||
if (!isDev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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: "/__cool_eps",
|
||||
method: "POST",
|
||||
proxy: false,
|
||||
data: {
|
||||
service,
|
||||
list
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 设置
|
||||
async function set(d?: any) {
|
||||
const list: any[] = [];
|
||||
|
||||
if (isArray(d)) {
|
||||
d = { d };
|
||||
}
|
||||
|
||||
for (const i in d) {
|
||||
if (isArray(d[i])) {
|
||||
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.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 = {};
|
||||
|
||||
const ks = Array.from(new Set([...names, ...getNames(d[k])]));
|
||||
|
||||
ks.forEach((e) => {
|
||||
d[k].permission[e] = `${d[k].namespace.replace(
|
||||
"admin/",
|
||||
""
|
||||
)}/${e}`.replace(/\//g, ":");
|
||||
});
|
||||
}
|
||||
|
||||
list.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deep(service, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存数据
|
||||
hmr.setData("service", service);
|
||||
|
||||
createDts(list);
|
||||
}
|
||||
|
||||
// 获取
|
||||
async function getEps() {
|
||||
try {
|
||||
// 本地数据
|
||||
let list = JSON.parse(__EPS__ || "[]").map(([prefix, name, api]: any[]) => {
|
||||
return {
|
||||
prefix,
|
||||
name,
|
||||
api: api.map(([path, method]: string[]) => {
|
||||
return {
|
||||
method,
|
||||
path
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
// 接口数据
|
||||
if (isDev && config.test.eps) {
|
||||
await service
|
||||
.request({
|
||||
url: "/admin/base/open/eps"
|
||||
})
|
||||
.then((res) => {
|
||||
if (!isEmpty(res) && isObject(res)) {
|
||||
list = res;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (list) {
|
||||
set(list);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Eps] 获取失败!", err);
|
||||
}
|
||||
}
|
||||
|
||||
await getEps();
|
||||
}
|
||||
23
src/cool/bootstrap/index.ts
Normal file
23
src/cool/bootstrap/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { createPinia } from "pinia";
|
||||
import { App } from "vue";
|
||||
import { createModule } from "./module";
|
||||
import { createEps } from "./eps";
|
||||
import { router } from "../router";
|
||||
import { Loading } from "../utils";
|
||||
|
||||
export async function bootstrap(app: App) {
|
||||
// pinia
|
||||
app.use(createPinia());
|
||||
|
||||
// 路由
|
||||
app.use(router);
|
||||
|
||||
// 模块
|
||||
const { eventLoop } = createModule(app);
|
||||
|
||||
// eps
|
||||
await createEps();
|
||||
|
||||
// 加载
|
||||
Loading.set([eventLoop()]);
|
||||
}
|
||||
112
src/cool/bootstrap/module.ts
Normal file
112
src/cool/bootstrap/module.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { App } from "vue";
|
||||
import { isFunction, orderBy } from "lodash-es";
|
||||
import { deepMerge, filename, mergeService } from "../utils";
|
||||
import { service } from "../service";
|
||||
import { module } from "../module";
|
||||
import { hmr } from "../hook";
|
||||
|
||||
// 扫描文件
|
||||
const files: any = import.meta.glob("/src/modules/*/{config.ts,service/**,directives/**}", {
|
||||
eager: true
|
||||
});
|
||||
|
||||
// 模块列表
|
||||
module.list = hmr.getData("modules", []);
|
||||
|
||||
// 解析
|
||||
for (const i in files) {
|
||||
// 分割
|
||||
const [, , , name, action] = i.split("/");
|
||||
|
||||
// 文件名
|
||||
const fname = filename(i);
|
||||
|
||||
// 文件内容
|
||||
const v = files[i]?.default;
|
||||
|
||||
// 模块是否存在
|
||||
const m = module.get(name);
|
||||
|
||||
// 数据
|
||||
const d = m || {
|
||||
name,
|
||||
value: null,
|
||||
services: [],
|
||||
directives: []
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
// 配置参数
|
||||
case "config.ts":
|
||||
d.value = v;
|
||||
break;
|
||||
|
||||
// 请求服务
|
||||
case "service":
|
||||
const s = new v();
|
||||
|
||||
if (s) {
|
||||
d.services?.push({
|
||||
path: s.namespace,
|
||||
value: s
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
// 指令
|
||||
case "directives":
|
||||
d.directives?.push({ name: fname, value: v });
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m) {
|
||||
module.add(d);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建
|
||||
export function createModule(app: App) {
|
||||
// 模块加载
|
||||
const list = orderBy(module.list, "order").map((e) => {
|
||||
const d = isFunction(e.value) ? e.value(app) : e.value;
|
||||
|
||||
if (d) {
|
||||
Object.assign(e, d);
|
||||
|
||||
// 注册组件
|
||||
e.components?.forEach(async (c: any) => {
|
||||
const v = await (isFunction(c) ? c() : c);
|
||||
const n = v.default || v;
|
||||
app.component(n.name, n);
|
||||
});
|
||||
|
||||
// 注册指令
|
||||
e.directives?.forEach((v) => {
|
||||
app.directive(v.name, v.value);
|
||||
});
|
||||
|
||||
// 安装事件
|
||||
if (d.install) {
|
||||
d.install(app, d.options);
|
||||
}
|
||||
}
|
||||
|
||||
// 合并
|
||||
deepMerge(service, mergeService(e.services || []));
|
||||
|
||||
return e;
|
||||
});
|
||||
|
||||
return {
|
||||
// 事件加载
|
||||
async eventLoop() {
|
||||
const events: any = {};
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].onLoad) {
|
||||
Object.assign(events, await list[i]?.onLoad?.(events));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
20
src/cool/config/dev.ts
Normal file
20
src/cool/config/dev.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { getUrlParam, storage } from "../utils";
|
||||
import { proxy } from "./proxy";
|
||||
|
||||
export default {
|
||||
// 根地址
|
||||
host: proxy["/dev/"].target,
|
||||
|
||||
// 请求地址
|
||||
get baseUrl() {
|
||||
let proxy = getUrlParam("proxy");
|
||||
|
||||
if (proxy) {
|
||||
storage.set("proxy", proxy);
|
||||
} else {
|
||||
proxy = storage.get("proxy") || "dev";
|
||||
}
|
||||
|
||||
return `/${proxy}/`;
|
||||
}
|
||||
};
|
||||
55
src/cool/config/index.ts
Normal file
55
src/cool/config/index.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Config } from "../types";
|
||||
import dev from "./dev";
|
||||
import prod from "./prod";
|
||||
|
||||
// 是否开发模式
|
||||
export const isDev = import.meta.env.MODE === "development";
|
||||
|
||||
// 配置
|
||||
export const config: Config = {
|
||||
// 项目信息
|
||||
app: {
|
||||
name: "COOL-ADMIN",
|
||||
|
||||
// 菜单
|
||||
menu: {
|
||||
// 是否分组显示
|
||||
isGroup: false,
|
||||
// 自定义菜单列表
|
||||
list: []
|
||||
},
|
||||
|
||||
// 路由
|
||||
router: {
|
||||
// 模式
|
||||
mode: "history",
|
||||
// 转场动画
|
||||
transition: "slide",
|
||||
// 首页组件
|
||||
home: () => import("/$/demo/views/home/index.vue")
|
||||
},
|
||||
|
||||
// 字体图标库
|
||||
iconfont: []
|
||||
},
|
||||
|
||||
// 忽略规则
|
||||
ignore: {
|
||||
// 不显示请求进度条
|
||||
NProgress: ["/", "/base/open/eps", "/base/comm/upload", "/base/comm/uploadMode"],
|
||||
// 页面不需要登录验证
|
||||
token: ["/login", "/401", "/403", "/404", "/500", "/502"]
|
||||
},
|
||||
|
||||
// 调试
|
||||
test: {
|
||||
token: "",
|
||||
mock: false,
|
||||
eps: true
|
||||
},
|
||||
|
||||
// 当前环境
|
||||
...(isDev ? dev : prod)
|
||||
};
|
||||
|
||||
export * from "./proxy";
|
||||
9
src/cool/config/prod.ts
Normal file
9
src/cool/config/prod.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { proxy } from "./proxy";
|
||||
|
||||
export default {
|
||||
// 根地址
|
||||
host: proxy["/prod/"].target,
|
||||
|
||||
// 请求地址
|
||||
baseUrl: "/api"
|
||||
};
|
||||
13
src/cool/config/proxy.ts
Normal file
13
src/cool/config/proxy.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export const proxy = {
|
||||
"/dev/": {
|
||||
target: "http://127.0.0.1:8001",
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/dev/, "")
|
||||
},
|
||||
|
||||
"/prod/": {
|
||||
target: "https://show.cool-admin.com",
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/prod/, "/api")
|
||||
}
|
||||
};
|
||||
30
src/cool/hook/browser.ts
Normal file
30
src/cool/hook/browser.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { reactive, watch } from "vue";
|
||||
import { getBrowser } from "../utils";
|
||||
|
||||
const browser = reactive(getBrowser());
|
||||
const events: (() => void)[] = [];
|
||||
|
||||
watch(
|
||||
() => browser.screen,
|
||||
() => {
|
||||
events.forEach((ev) => ev());
|
||||
}
|
||||
);
|
||||
|
||||
useEventListener(window, "resize", () => {
|
||||
Object.assign(browser, getBrowser());
|
||||
});
|
||||
|
||||
export function useBrowser() {
|
||||
return {
|
||||
browser,
|
||||
onScreenChange(ev: () => void, immediate = true) {
|
||||
events.push(ev);
|
||||
|
||||
if (immediate) {
|
||||
ev();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
23
src/cool/hook/hmr.ts
Normal file
23
src/cool/hook/hmr.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// 解决热更新后失效问题;
|
||||
const data = import.meta.hot?.data.getData?.() || {};
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.data.getData = () => {
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
export const hmr = {
|
||||
data,
|
||||
|
||||
setData(key: string, value: any) {
|
||||
data[key] = value;
|
||||
},
|
||||
|
||||
getData(key: string, defaultValue?: any) {
|
||||
if (defaultValue !== undefined && !data[key]) {
|
||||
this.setData(key, defaultValue);
|
||||
}
|
||||
return data[key];
|
||||
}
|
||||
};
|
||||
52
src/cool/hook/index.ts
Normal file
52
src/cool/hook/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { getCurrentInstance, Ref, reactive } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { service } from "../service";
|
||||
import { useBrowser } from "./browser";
|
||||
import { useMitt } from "./mitt";
|
||||
|
||||
export function useRefs() {
|
||||
const refs = reactive<{ [key: string]: any }>({});
|
||||
function setRefs(name: string) {
|
||||
return (el: any) => {
|
||||
refs[name] = el;
|
||||
};
|
||||
}
|
||||
|
||||
return { refs, setRefs };
|
||||
}
|
||||
|
||||
export function useParent(name: string, r: Ref) {
|
||||
const d = getCurrentInstance();
|
||||
|
||||
if (d) {
|
||||
let parent = d.proxy?.$.parent;
|
||||
|
||||
if (parent) {
|
||||
while (parent && parent.type?.name != name) {
|
||||
parent = parent?.parent;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
if (parent.type.name == name) {
|
||||
r.value = parent.exposed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
export function useCool() {
|
||||
return {
|
||||
service,
|
||||
route: useRoute(),
|
||||
router: useRouter(),
|
||||
mitt: useMitt(),
|
||||
...useBrowser(),
|
||||
...useRefs()
|
||||
};
|
||||
}
|
||||
|
||||
export * from "./browser";
|
||||
export * from "./hmr";
|
||||
8
src/cool/hook/mitt.ts
Normal file
8
src/cool/hook/mitt.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import Mitt, { Emitter } from "mitt";
|
||||
import { hmr } from "./hmr";
|
||||
|
||||
const mitt: Emitter<any> = hmr.getData("mitt", Mitt());
|
||||
|
||||
export function useMitt() {
|
||||
return mitt;
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import Crud from "cl-admin-crud";
|
||||
import Theme from "cl-admin-theme";
|
||||
|
||||
export default {
|
||||
modules: [
|
||||
// 基础模块
|
||||
"base",
|
||||
// 文件上传
|
||||
{
|
||||
name: "upload",
|
||||
options: {
|
||||
icon: "el-icon-picture",
|
||||
text: "选择图片"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "crud",
|
||||
value: Crud,
|
||||
options: {
|
||||
crud: {
|
||||
dict: {
|
||||
sort: {
|
||||
prop: "order",
|
||||
order: "sort"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 客服聊天
|
||||
"chat",
|
||||
// 任务管理
|
||||
"task",
|
||||
// 复制指令
|
||||
"copy",
|
||||
// 省市区选择
|
||||
"distpicker",
|
||||
// 示例页
|
||||
"demo",
|
||||
// 主题切换
|
||||
{
|
||||
name: "theme",
|
||||
value: Theme
|
||||
}
|
||||
]
|
||||
};
|
||||
8
src/cool/index.ts
Normal file
8
src/cool/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from "./service";
|
||||
export * from "./bootstrap";
|
||||
export * from "./hook";
|
||||
export * from "./module";
|
||||
export * from "./router";
|
||||
export * from "./config";
|
||||
export * from "./types/index.d";
|
||||
export { storage } from "./utils";
|
||||
23
src/cool/module/index.ts
Normal file
23
src/cool/module/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Module } from "../types";
|
||||
import { hmr } from "../hook";
|
||||
|
||||
// 数据列表
|
||||
const list: Module[] = hmr.getData("modules", []);
|
||||
|
||||
// 模块
|
||||
const module = {
|
||||
list,
|
||||
req: Promise.resolve(),
|
||||
get(name: string): Module {
|
||||
// @ts-ignore
|
||||
return this.list.find((e) => e.name == name);
|
||||
},
|
||||
add(data: Module) {
|
||||
this.list.push(data);
|
||||
},
|
||||
wait() {
|
||||
return this.req;
|
||||
}
|
||||
};
|
||||
|
||||
export { module };
|
||||
@ -1,4 +0,0 @@
|
||||
import { iconList } from "./theme";
|
||||
import "./resize";
|
||||
|
||||
export { iconList };
|
||||
@ -1,52 +0,0 @@
|
||||
import store from "@/store";
|
||||
|
||||
const lock = {
|
||||
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,40 +0,0 @@
|
||||
import { iconfontUrl, app } from "@/config/env";
|
||||
import { createLink } from "../utils";
|
||||
import { colorPrimary } from "@/assets/css/common.scss";
|
||||
|
||||
// 主题初始化
|
||||
|
||||
if (app.theme) {
|
||||
const { url, color } = app.theme;
|
||||
|
||||
if (url) {
|
||||
createLink(url, "theme-style");
|
||||
}
|
||||
|
||||
document
|
||||
.getElementsByTagName("body")[0]
|
||||
.style.setProperty("--color-primary", color || colorPrimary);
|
||||
}
|
||||
|
||||
// 字体图标库加载
|
||||
|
||||
if (iconfontUrl) {
|
||||
createLink(iconfontUrl);
|
||||
}
|
||||
|
||||
// svg 图标加载
|
||||
|
||||
const req = require.context("@/icons/svg/", false, /\.svg$/);
|
||||
|
||||
req.keys().map(req);
|
||||
|
||||
function iconList() {
|
||||
return req
|
||||
.keys()
|
||||
.map(req)
|
||||
.map(e => e.default.id)
|
||||
.filter(e => e.includes("icon"))
|
||||
.sort();
|
||||
}
|
||||
|
||||
export { iconList };
|
||||
@ -1,96 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-avatar" :class="[size, shape]" :style="[style]">
|
||||
<el-image :src="src" alt="">
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
</div>
|
||||
</el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isNumber } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-avatar",
|
||||
|
||||
props: {
|
||||
src: String,
|
||||
size: {
|
||||
type: String,
|
||||
default: "large"
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
default: "circle"
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
const size = isNumber(this.size) ? this.size + "px" : this.size;
|
||||
|
||||
return {
|
||||
height: size,
|
||||
width: size
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-avatar {
|
||||
overflow: hidden;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
&.large {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
&.circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&.square {
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
/deep/.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon-picture-outline {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,268 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-codemirror">
|
||||
<codemirror
|
||||
ref="code"
|
||||
v-model="value2"
|
||||
:options="options2"
|
||||
:style="{
|
||||
height,
|
||||
width
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { codemirror } from "vue-codemirror";
|
||||
import beautifyJs from "js-beautify";
|
||||
|
||||
import "codemirror/theme/cobalt.css";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/addon/hint/javascript-hint";
|
||||
import "codemirror/mode/javascript/javascript";
|
||||
|
||||
export default {
|
||||
name: "cl-codemirror",
|
||||
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
|
||||
props: {
|
||||
value: String,
|
||||
height: String,
|
||||
width: String,
|
||||
options: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value2: ""
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.value2 = val || "";
|
||||
}
|
||||
},
|
||||
value2(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
options2() {
|
||||
return {
|
||||
mode: "javascript",
|
||||
theme: "ambiance",
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
indentUnit: 4,
|
||||
...this.options
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$el.onkeydown = e => {
|
||||
let keyCode = e.keyCode || e.which || e.charCode;
|
||||
let altKey = e.altKey || e.metaKey;
|
||||
let shiftKey = e.shiftKey || e.metaKey;
|
||||
|
||||
if (altKey && shiftKey && keyCode == 70) {
|
||||
this.setValue();
|
||||
}
|
||||
};
|
||||
|
||||
this.setValue(this.value2);
|
||||
},
|
||||
|
||||
methods: {
|
||||
setValue(val) {
|
||||
this.value2 = beautifyJs(val || this.value2);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-codemirror {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dcdfe6;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cm-s-ambiance * {
|
||||
font-family: "Consolas";
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-header {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-quote {
|
||||
color: #24c2c7;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-keyword {
|
||||
color: #a626a4;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-atom {
|
||||
color: #986801;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-number {
|
||||
color: #986801;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-def {
|
||||
color: #383a42;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-variable {
|
||||
color: #4078f2;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-variable-2 {
|
||||
color: #eed1b3;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-variable-3,
|
||||
.cm-s-ambiance .cm-type {
|
||||
color: #faded3;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-property {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-operator {
|
||||
color: #0184bc;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-comment {
|
||||
color: #555;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-string {
|
||||
color: #50a14f;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-string-2 {
|
||||
color: #9d937c;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-meta {
|
||||
color: #d2a8a1;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-qualifier {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-builtin {
|
||||
color: #9999cc;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-bracket {
|
||||
color: #24c2c7;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-tag {
|
||||
color: #fee4ff;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-attribute {
|
||||
color: #9b859d;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-hr {
|
||||
color: pink;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-link {
|
||||
color: #f4c20b;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-special {
|
||||
color: #ff9d00;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .cm-error {
|
||||
color: #af2018;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-matchingbracket {
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-nonmatchingbracket {
|
||||
color: #f22;
|
||||
}
|
||||
|
||||
.cm-s-ambiance div.CodeMirror-selected {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-line::selection,
|
||||
.cm-s-ambiance .CodeMirror-line > span::selection,
|
||||
.cm-s-ambiance .CodeMirror-line > span > span::selection {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-line::-moz-selection,
|
||||
.cm-s-ambiance .CodeMirror-line > span::-moz-selection,
|
||||
.cm-s-ambiance .CodeMirror-line > span > span::-moz-selection {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Editor styling */
|
||||
.cm-s-ambiance.CodeMirror {
|
||||
line-height: 1.4em;
|
||||
color: #383a42;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-gutters {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-linenumber {
|
||||
color: #666;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-guttermarker {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-guttermarker-subtle {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-cursor {
|
||||
border-left: 1px solid #999;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.cm-s-ambiance .CodeMirror-activeline-background {
|
||||
background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031);
|
||||
}
|
||||
</style>
|
||||
@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-dept-check" v-loading="loading">
|
||||
<p v-if="title">{{ title }}</p>
|
||||
|
||||
<div class="cl-dept-check__search">
|
||||
<el-input placeholder="输入关键字进行过滤" v-model="keyword" size="small"> </el-input>
|
||||
<el-switch
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
v-model="form.relevance"
|
||||
@change="onCheckStrictlyChange"
|
||||
></el-switch
|
||||
>是否关联上下级
|
||||
</div>
|
||||
|
||||
<div class="cl-dept-check__tree" v-if="visible">
|
||||
<el-tree
|
||||
:data="list"
|
||||
:props="props"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
:check-strictly="!form.relevance"
|
||||
highlight-current
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
ref="tree"
|
||||
@check-change="onCheckChange"
|
||||
>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-dept-check",
|
||||
|
||||
props: {
|
||||
value: Array,
|
||||
title: String
|
||||
},
|
||||
|
||||
inject: ["form"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
checked: [],
|
||||
keyword: "",
|
||||
props: {
|
||||
label: "name",
|
||||
children: "children"
|
||||
},
|
||||
loading: false,
|
||||
visible: true
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
keyword(val) {
|
||||
this.$refs["tree"].filter(val);
|
||||
},
|
||||
|
||||
value(val) {
|
||||
this.refreshTree(val);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
methods: {
|
||||
refreshTree(val) {
|
||||
this.checked = val || [];
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.$service.system.dept
|
||||
.list()
|
||||
.then(res => {
|
||||
this.list = deepTree(res);
|
||||
this.refreshTree(this.value);
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
filterNode(val, data) {
|
||||
if (!val) return true;
|
||||
return data.name.includes(val);
|
||||
},
|
||||
|
||||
onCheckStrictlyChange() {
|
||||
this.form.departmentIdList = [];
|
||||
this.visible = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.visible = true;
|
||||
});
|
||||
},
|
||||
|
||||
onCheckChange() {
|
||||
this.$emit("input", this.$refs["tree"].getCheckedKeys());
|
||||
}
|
||||
}
|
||||
};
|
||||
</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,104 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-dept-move"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-dept-move",
|
||||
|
||||
methods: {
|
||||
async toMove(ids) {
|
||||
this.$crud.openForm({
|
||||
title: "部门转移",
|
||||
width: "600px",
|
||||
props: {
|
||||
"label-width": "80px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "选择部门",
|
||||
prop: "dept",
|
||||
component: {
|
||||
name: "system-user__dept-move",
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.list = await this.$service.system.dept.list().then(deepTree);
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectRow(e) {
|
||||
this.$emit("input", e);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #eee",
|
||||
"border-radius": "3px",
|
||||
padding: "2px"
|
||||
}}>
|
||||
<el-tree
|
||||
data={this.list}
|
||||
{...{
|
||||
props: {
|
||||
props: {
|
||||
label: "name"
|
||||
}
|
||||
}
|
||||
}}
|
||||
node-key="id"
|
||||
highlight-current
|
||||
on-node-click={this.selectRow}></el-tree>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
on: {
|
||||
submit: (data, { done, close }) => {
|
||||
if (!data.dept) {
|
||||
this.$message.warning("请选择部门");
|
||||
return done();
|
||||
}
|
||||
|
||||
const { name, id } = data.dept;
|
||||
|
||||
this.$confirm(`是否将用户转移到部门 ${name} 下`, "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$service.system.user
|
||||
.move({
|
||||
departmentId: id,
|
||||
userIds: ids
|
||||
})
|
||||
.then(res => {
|
||||
this.$message.success("转移成功");
|
||||
this.$emit("success", res);
|
||||
close();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
this.$emit("error", err);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,422 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-dept-tree">
|
||||
<div class="cl-dept-tree__header">
|
||||
<div>组织架构</div>
|
||||
|
||||
<ul class="cl-dept-tree__op">
|
||||
<li>
|
||||
<el-tooltip content="刷新">
|
||||
<i class="el-icon-refresh" @click="refresh()"></i>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
|
||||
<li v-if="drag && !browser.isMini">
|
||||
<el-tooltip content="拖动排序">
|
||||
<i class="el-icon-s-operation" @click="isDrag = true"></i>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
|
||||
<li class="no" v-show="isDrag">
|
||||
<el-button type="text" size="mini" @click="treeOrder(true)">保存</el-button>
|
||||
<el-button type="text" size="mini" @click="treeOrder(false)">取消</el-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="cl-dept-tree__container" @contextmenu.prevent="openCM">
|
||||
<el-tree
|
||||
node-key="id"
|
||||
highlight-current
|
||||
default-expand-all
|
||||
:data="list"
|
||||
:props="{
|
||||
label: 'name'
|
||||
}"
|
||||
:draggable="isDrag"
|
||||
:allow-drag="allowDrag"
|
||||
:allow-drop="allowDrop"
|
||||
:expand-on-click-node="false"
|
||||
v-loading="loading"
|
||||
@node-contextmenu="openCM"
|
||||
>
|
||||
<template slot-scope="{ node, data }">
|
||||
<div class="cl-dept-tree__node">
|
||||
<span class="cl-dept-tree__node-label" @click="rowClick(data)">{{
|
||||
node.label
|
||||
}}</span>
|
||||
<span
|
||||
class="cl-dept-tree__node-icon"
|
||||
v-if="browser.isMini"
|
||||
@click="openCM($event, data, node)"
|
||||
>
|
||||
<i class="el-icon-more"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree, isArray, revDeepTree } from "cl-admin/utils";
|
||||
import { ContextMenu, Form } from "cl-admin-crud";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "cl-dept-tree",
|
||||
|
||||
props: {
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 99
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
isDrag: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["browser"])
|
||||
},
|
||||
|
||||
created() {
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
methods: {
|
||||
openCM(e, d, n) {
|
||||
if (!d) {
|
||||
d = this.list[0] || {};
|
||||
}
|
||||
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "新增",
|
||||
"suffix-icon": "el-icon-plus",
|
||||
hidden: n && n.level >= this.level,
|
||||
callback: (_, done) => {
|
||||
this.rowEdit({
|
||||
name: "",
|
||||
parentName: d.name,
|
||||
parentId: d.id
|
||||
});
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "编辑",
|
||||
"suffix-icon": "el-icon-edit",
|
||||
callback: (_, done) => {
|
||||
this.rowEdit(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
"suffix-icon": "el-icon-delete",
|
||||
hidden: !Boolean(d.parentId),
|
||||
callback: (_, done) => {
|
||||
this.rowDel(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "新增成员",
|
||||
"suffix-icon": "el-icon-user",
|
||||
callback: (_, done) => {
|
||||
this.$emit("user-add", d);
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
|
||||
allowDrag({ data }) {
|
||||
return data.parentId;
|
||||
},
|
||||
|
||||
allowDrop(_, dropNode) {
|
||||
return dropNode.data.parentId;
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.isDrag = false;
|
||||
this.loading = true;
|
||||
|
||||
this.$service.system.dept
|
||||
.list()
|
||||
.then(res => {
|
||||
this.list = deepTree(res);
|
||||
this.$emit("list-change", this.list);
|
||||
})
|
||||
.done(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
rowClick(e) {
|
||||
ContextMenu.close();
|
||||
let ids = e.children ? revDeepTree(e.children).map(e => e.id) : [];
|
||||
ids.unshift(e.id);
|
||||
this.$emit("row-click", { item: e, ids });
|
||||
},
|
||||
|
||||
rowEdit(e) {
|
||||
const method = e.id ? "update" : "add";
|
||||
|
||||
Form.open({
|
||||
title: "编辑部门",
|
||||
width: "550px",
|
||||
props: {
|
||||
"label-width": "100px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "部门名称",
|
||||
prop: "name",
|
||||
value: e.name,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
placeholder: "请填写部门名称"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "部门名称不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "上级部门",
|
||||
prop: "parentId",
|
||||
value: e.parentName || "...",
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "orderNum",
|
||||
value: e.orderNum || 0,
|
||||
component: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
"controls-position": "right",
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
on: {
|
||||
submit: (data, { done, close }) => {
|
||||
this.$service.system.dept[method]({
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
name: data.name,
|
||||
orderNum: data.orderNum
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`新增部门${data.name}成功`);
|
||||
close();
|
||||
this.refresh();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
rowDel(e) {
|
||||
const del = f => {
|
||||
this.$service.system.dept
|
||||
.delete({
|
||||
ids: [e.id],
|
||||
deleteUser: f
|
||||
})
|
||||
.then(() => {
|
||||
if (f) {
|
||||
this.$message.success("删除成功");
|
||||
} else {
|
||||
this.$confirm(
|
||||
`“${e.name}” 部门的用户已成功转移到 “${e.parentName}” 部门。`,
|
||||
"删除成功"
|
||||
);
|
||||
}
|
||||
})
|
||||
.done(() => {
|
||||
this.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
this.$confirm(`该操作会删除 “${e.name}” 部门的所有用户,是否确认?`, "提示", {
|
||||
type: "warning",
|
||||
confirmButtonText: "直接删除",
|
||||
cancelButtonText: "保留用户",
|
||||
distinguishCancelAndClose: true
|
||||
})
|
||||
.then(() => {
|
||||
del(true);
|
||||
})
|
||||
.catch(action => {
|
||||
if (action == "cancel") {
|
||||
del(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
treeOrder(f) {
|
||||
if (f) {
|
||||
this.$confirm("部门架构已发生改变,是否保存?", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
const deep = (list, pid) => {
|
||||
list.forEach(e => {
|
||||
e.parentId = pid;
|
||||
ids.push(e);
|
||||
|
||||
if (e.children && isArray(e.children)) {
|
||||
deep(e.children, e.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let ids = [];
|
||||
|
||||
deep(this.list, null);
|
||||
|
||||
this.$service.system.dept
|
||||
.order(
|
||||
ids.map((e, i) => {
|
||||
return {
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
orderNum: i
|
||||
};
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
this.$message.success("更新排序成功");
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
})
|
||||
.done(() => {
|
||||
this.refresh();
|
||||
this.isDrag = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-dept-tree {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
background-color: #fff;
|
||||
letter-spacing: 1px;
|
||||
position: relative;
|
||||
|
||||
div {
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.el-tree-node__content {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
&__op {
|
||||
display: flex;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
margin-left: 5px;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(.no):hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
/deep/.el-tree-node__content {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,245 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-editor-quill">
|
||||
<div class="editor" :style="style"></div>
|
||||
|
||||
<cl-upload-space
|
||||
ref="upload-space"
|
||||
detail-data
|
||||
:show-button="false"
|
||||
@confirm="onFileConfirm"
|
||||
>
|
||||
</cl-upload-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Quill from "quill";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import { isNumber } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-editor-quill",
|
||||
|
||||
props: {
|
||||
value: null,
|
||||
height: [String, Number],
|
||||
width: [String, Number],
|
||||
options: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
content: "",
|
||||
quill: null,
|
||||
cursorIndex: 0
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
const height = isNumber(this.height) ? this.height + "px" : this.height;
|
||||
const width = isNumber(this.width) ? this.width + "px" : this.width;
|
||||
|
||||
return {
|
||||
height,
|
||||
width
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val) {
|
||||
if (val !== this.content) {
|
||||
this.setContent(val);
|
||||
}
|
||||
} else {
|
||||
this.setContent("");
|
||||
}
|
||||
},
|
||||
|
||||
content(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// 实例化
|
||||
this.quill = new Quill(this.$el.querySelector(".editor"), {
|
||||
theme: "snow",
|
||||
placeholder: "输入内容",
|
||||
modules: {
|
||||
toolbar: [
|
||||
["bold", "italic", "underline", "strike"],
|
||||
["blockquote", "code-block"],
|
||||
[{ header: 1 }, { header: 2 }],
|
||||
[{ list: "ordered" }, { list: "bullet" }],
|
||||
[{ script: "sub" }, { script: "super" }],
|
||||
[{ indent: "-1" }, { indent: "+1" }],
|
||||
[{ direction: "rtl" }],
|
||||
[{ size: ["small", false, "large", "huge"] }],
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ font: [] }],
|
||||
[{ align: [] }],
|
||||
["clean"],
|
||||
["link", "image"]
|
||||
]
|
||||
},
|
||||
...this.options
|
||||
});
|
||||
|
||||
// 添加文件上传工具
|
||||
this.quill.getModule("toolbar").addHandler("image", this.uploadHandler);
|
||||
|
||||
// 监听内容变化
|
||||
this.quill.on("text-change", () => {
|
||||
this.content = this.quill.root.innerHTML;
|
||||
});
|
||||
|
||||
// 设置默认内容
|
||||
this.setContent(this.value);
|
||||
|
||||
// 加载完回调
|
||||
this.$emit("load", this.quill);
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadHandler() {
|
||||
const selection = this.quill.getSelection();
|
||||
|
||||
if (selection) {
|
||||
this.cursorIndex = selection.index;
|
||||
}
|
||||
|
||||
this.$refs["upload-space"].open();
|
||||
},
|
||||
|
||||
onFileConfirm(files) {
|
||||
if (files.length > 0) {
|
||||
// 批量插件图片
|
||||
files.forEach((file, i) => {
|
||||
let [type] = file.type.split("/");
|
||||
|
||||
this.quill.insertEmbed(
|
||||
this.cursorIndex + i,
|
||||
type,
|
||||
file.url,
|
||||
Quill.sources.USER
|
||||
);
|
||||
});
|
||||
|
||||
// 移动光标到图片后一位
|
||||
this.quill.setSelection(this.cursorIndex + files.length);
|
||||
}
|
||||
},
|
||||
|
||||
setContent(val) {
|
||||
this.quill.root.innerHTML = val || "";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-editor-quill {
|
||||
background-color: #fff;
|
||||
|
||||
.ql-snow {
|
||||
line-height: 22px !important;
|
||||
}
|
||||
|
||||
.el-upload,
|
||||
#quill-upload-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ql-snow {
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode="link"]::before {
|
||||
content: "请输入链接地址:";
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0px;
|
||||
content: "保存";
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode="video"]::before {
|
||||
content: "请输入视频地址:";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: "14px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
|
||||
content: "10px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
|
||||
content: "18px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
|
||||
content: "32px";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: "文本";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
|
||||
content: "标题1";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
|
||||
content: "标题2";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
|
||||
content: "标题3";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
|
||||
content: "标题4";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
|
||||
content: "标题5";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
|
||||
content: "标题6";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: "标准字体";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
|
||||
content: "衬线字体";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
|
||||
content: "等宽字体";
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<svg :class="svgClass" :style="style2" aria-hidden="true">
|
||||
<use :xlink:href="iconName"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isNumber } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "icon-svg",
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
className: {
|
||||
type: String
|
||||
},
|
||||
size: {
|
||||
type: [String, Number]
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
style2: {}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#${this.name}`;
|
||||
},
|
||||
svgClass() {
|
||||
return ["icon-svg", `icon-svg__${this.name}`, this.className];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.style2 = {
|
||||
fontSize: isNumber(this.size) ? this.size + "px" : this.size
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon-svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@ -1,39 +0,0 @@
|
||||
import Avatar from "./avatar";
|
||||
import Scrollbar from "./scrollbar";
|
||||
import RouteNav from "./route-nav";
|
||||
import Process from "./process";
|
||||
import IconSvg from "./icon-svg";
|
||||
import DeptCheck from "./dept/check";
|
||||
import DeptMove from "./dept/move";
|
||||
import DeptTree from "./dept/tree";
|
||||
import MenuSlider from "./menu/slider";
|
||||
import MenuTopbar from "./menu/topbar";
|
||||
import MenuFile from "./menu/file";
|
||||
import MenuIcons from "./menu/icons";
|
||||
import MenuPerms from "./menu/perms";
|
||||
import MenuTree from "./menu/tree";
|
||||
import RoleSelect from "./role/select";
|
||||
import RolePerms from "./role/perms";
|
||||
import EditorQuill from "./editor-quill";
|
||||
import Codemirror from "./codemirror";
|
||||
|
||||
export default {
|
||||
Avatar,
|
||||
Scrollbar,
|
||||
RouteNav,
|
||||
Process,
|
||||
IconSvg,
|
||||
DeptCheck,
|
||||
DeptMove,
|
||||
DeptTree,
|
||||
MenuSlider,
|
||||
MenuTopbar,
|
||||
MenuFile,
|
||||
MenuIcons,
|
||||
MenuPerms,
|
||||
MenuTree,
|
||||
RoleSelect,
|
||||
RolePerms,
|
||||
EditorQuill,
|
||||
Codemirror
|
||||
};
|
||||
@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-file">
|
||||
<el-select v-model="newValue" allow-create filterable clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:label="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const files = require
|
||||
.context("@/", true, /views\/(?!(components)|(.*\/components)|(index\.js)).*.(js|vue)/)
|
||||
.keys();
|
||||
|
||||
export default {
|
||||
name: "cl-menu-file",
|
||||
|
||||
props: {
|
||||
value: [String]
|
||||
},
|
||||
|
||||
inject: ["form"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
newValue: "",
|
||||
list: []
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.newValue = val || "";
|
||||
}
|
||||
},
|
||||
|
||||
newValue(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.list = files.map(e => {
|
||||
return {
|
||||
value: e.substr(2)
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-menu-file {
|
||||
width: 100%;
|
||||
|
||||
/deep/ .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__module {
|
||||
display: inline-flex;
|
||||
|
||||
.label {
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-icons">
|
||||
<el-popover
|
||||
ref="iconPopover"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
popper-class="popper-menu-icon"
|
||||
>
|
||||
<el-row :gutter="10" class="list">
|
||||
<el-col :span="3" :xs="4" v-for="(item, index) in list" :key="index">
|
||||
<el-button
|
||||
size="mini"
|
||||
:class="{ 'is-active': item === value }"
|
||||
@click="onUpdate(item)"
|
||||
>
|
||||
<icon-svg :name="item"></icon-svg>
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
|
||||
<el-input
|
||||
v-model="name"
|
||||
v-popover:iconPopover
|
||||
placeholder="请选择"
|
||||
@input="onUpdate"
|
||||
></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { iconList } from "@/cool/modules/base";
|
||||
|
||||
export default {
|
||||
name: "cl-menu-icons",
|
||||
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
name: ""
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.name = val;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.list = iconList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
onUpdate(icon) {
|
||||
this.$emit("input", icon);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
.
|
||||
<style lang="scss">
|
||||
.popper-menu-icon {
|
||||
width: 480px;
|
||||
max-width: 90%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.list {
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
margin-bottom: 10px;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
|
||||
.icon-svg {
|
||||
font-size: 18px;
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<el-cascader
|
||||
:options="options"
|
||||
:props="{ multiple: true }"
|
||||
separator=":"
|
||||
clearable
|
||||
filterable
|
||||
v-model="newValue"
|
||||
@change="onChange"
|
||||
></el-cascader>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "cl-menu-perms",
|
||||
|
||||
props: {
|
||||
value: [String, Number, Array]
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
newValue: []
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.parse();
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
let options = [];
|
||||
let list = [];
|
||||
|
||||
const flat = obj => {
|
||||
for (let i in obj) {
|
||||
let { permission } = obj[i];
|
||||
|
||||
if (permission) {
|
||||
list = [...list, Object.values(permission)].flat();
|
||||
} else {
|
||||
flat(obj[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flat(this.$service);
|
||||
|
||||
list.filter(e => e.includes(":"))
|
||||
.map(e => e.split(":"))
|
||||
.forEach(arr => {
|
||||
const col = (i, d) => {
|
||||
let key = arr[i];
|
||||
|
||||
let index = d.findIndex(e => e.label == key);
|
||||
|
||||
if (index >= 0) {
|
||||
col(i + 1, d[index].children);
|
||||
} else {
|
||||
let isLast = i == arr.length - 1;
|
||||
|
||||
d.push({
|
||||
label: key,
|
||||
value: key,
|
||||
children: isLast ? null : []
|
||||
});
|
||||
|
||||
if (!isLast) {
|
||||
col(i + 1, d[d.length - 1].children || []);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
col(0, options);
|
||||
});
|
||||
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.parse();
|
||||
},
|
||||
|
||||
methods: {
|
||||
parse() {
|
||||
this.newValue = this.value ? this.value.split(",").map(e => e.split(":")) : [];
|
||||
},
|
||||
|
||||
onChange(row) {
|
||||
this.$emit("input", row.map(e => e.join(":")).join(","));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,92 +0,0 @@
|
||||
import { mapGetters } from "vuex";
|
||||
import "./index.scss";
|
||||
|
||||
export default {
|
||||
name: "cl-menu-slider",
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: true
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["menuList", "menuCollapse", "browser", "app"])
|
||||
},
|
||||
|
||||
watch: {
|
||||
menuList() {
|
||||
this.refresh();
|
||||
},
|
||||
"app.conf.showAMenu"() {
|
||||
this.$store.commit("SET_MENU_LIST");
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toView(url) {
|
||||
if (url != this.$route.path) {
|
||||
this.$router.push(url);
|
||||
}
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.visible = false;
|
||||
|
||||
setTimeout(() => {
|
||||
this.visible = true;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const fn = list => {
|
||||
return list
|
||||
.filter(e => e.isShow)
|
||||
.map(e => {
|
||||
let html = null;
|
||||
|
||||
if (e.type == 0) {
|
||||
html = (
|
||||
<el-submenu
|
||||
popper-class="cl-slider-menu__submenu"
|
||||
index={String(e.id)}
|
||||
key={e.id}>
|
||||
<template slot="title">
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
<span slot="title">{e.name}</span>
|
||||
</template>
|
||||
{fn(e.children)}
|
||||
</el-submenu>
|
||||
);
|
||||
} else {
|
||||
html = (
|
||||
<el-menu-item index={e.path} key={e.path}>
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
<span slot="title">{e.name}</span>
|
||||
</el-menu-item>
|
||||
);
|
||||
}
|
||||
|
||||
return html;
|
||||
});
|
||||
};
|
||||
|
||||
let el = fn(this.menuList);
|
||||
|
||||
return (
|
||||
this.visible && (
|
||||
<div class="cl-slider-menu">
|
||||
<el-menu
|
||||
default-active={this.$route.path}
|
||||
background-color="transparent"
|
||||
collapse-transition={false}
|
||||
collapse={this.browser.isMini ? false : this.menuCollapse}
|
||||
on-select={this.toView}>
|
||||
{el}
|
||||
</el-menu>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,81 +0,0 @@
|
||||
.cl-slider-menu {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: 0;
|
||||
|
||||
.el-submenu__title,
|
||||
&-item {
|
||||
&.is-active,
|
||||
&:hover {
|
||||
background-color: $color-primary !important;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-submenu__title,
|
||||
&-item,
|
||||
&__title {
|
||||
color: #eee;
|
||||
letter-spacing: 0.5px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
|
||||
.icon-svg {
|
||||
font-size: 16px;
|
||||
margin: 0 15px 0 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&--collapse {
|
||||
.el-submenu__title {
|
||||
.icon-svg {
|
||||
margin-left: 2px;
|
||||
font-size: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-slider-menu__submenu {
|
||||
background-color: #fff;
|
||||
|
||||
&.el-menu {
|
||||
&--vertical {
|
||||
.el-submenu {
|
||||
&__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,110 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-topbar">
|
||||
<el-menu
|
||||
:default-active="index"
|
||||
mode="horizontal"
|
||||
background-color="transparent"
|
||||
@select="onSelect"
|
||||
>
|
||||
<el-menu-item v-for="(item, index) in list" :index="`${index}`" :key="index">
|
||||
<icon-svg v-if="item.icon" :name="item.icon" :size="18"></icon-svg>
|
||||
<span>{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from "vuex";
|
||||
import { firstMenu } from "../../utils";
|
||||
|
||||
export default {
|
||||
name: "cl-menu-topbar",
|
||||
|
||||
data() {
|
||||
return {
|
||||
index: "0"
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
list() {
|
||||
return this.$store.getters.menuGroup.filter(e => e.isShow);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const deep = (e, i) => {
|
||||
switch (e.type) {
|
||||
case 0:
|
||||
e.children.forEach(e => {
|
||||
deep(e, i);
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
if (this.$route.path.includes(e.path)) {
|
||||
this.index = String(i);
|
||||
this.SET_MENU_LIST(i);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.list.forEach((e, i) => {
|
||||
deep(e, i);
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations(["SET_MENU_LIST"]),
|
||||
|
||||
onSelect(index) {
|
||||
this.SET_MENU_LIST(index);
|
||||
|
||||
// 获取第一个菜单地址
|
||||
const url = firstMenu(this.list[index].children);
|
||||
this.$router.push(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-menu-topbar {
|
||||
margin-right: 10px;
|
||||
|
||||
/deep/.el-menu {
|
||||
height: 50px;
|
||||
background: transparent;
|
||||
border-bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.el-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
border-bottom: 0;
|
||||
padding: 0 20px;
|
||||
background: transparent;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
margin-left: 3px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
/deep/.icon-svg {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-tree">
|
||||
<el-popover
|
||||
ref="popover"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
popper-class="popper-menu-tree"
|
||||
>
|
||||
<el-input size="small" v-model="filterValue">
|
||||
<i slot="prefix" class="el-input__icon el-icon-search"></i>
|
||||
</el-input>
|
||||
|
||||
<el-tree
|
||||
ref="tree"
|
||||
node-key="menuId"
|
||||
:data="treeList"
|
||||
:props="props"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:default-expanded-keys="expandedKeys"
|
||||
:filter-node-method="filterNode"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
</el-tree>
|
||||
</el-popover>
|
||||
<el-input v-model="name" v-popover:popover readonly placeholder="请选择"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-menu-tree",
|
||||
|
||||
props: {
|
||||
value: [Number, String]
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
filterValue: "",
|
||||
list: [],
|
||||
props: {
|
||||
label: "name",
|
||||
children: "children"
|
||||
},
|
||||
expandedKeys: []
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
filterValue(val) {
|
||||
this.$refs.tree.filter(val);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
name() {
|
||||
const item = this.list.find(e => e.id == this.value);
|
||||
return item ? item.name : "一级菜单";
|
||||
},
|
||||
|
||||
treeList() {
|
||||
return deepTree(this.list);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.menuList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
currentChange({ id }) {
|
||||
this.$emit("input", id);
|
||||
},
|
||||
|
||||
menuList() {
|
||||
this.$service.system.menu.list().then(res => {
|
||||
let list = res.filter(e => e.type != 2);
|
||||
|
||||
list.unshift({
|
||||
name: "一级菜单",
|
||||
id: null
|
||||
});
|
||||
|
||||
this.list = list;
|
||||
});
|
||||
},
|
||||
|
||||
filterNode(value, data) {
|
||||
if (!value) return true;
|
||||
return data.name.indexOf(value) !== -1;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popper-menu-tree {
|
||||
width: 480px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-input {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,211 +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 class="app-process__scroller" ref="scroller">
|
||||
<div
|
||||
class="app-process__item"
|
||||
v-for="(item, index) in processList"
|
||||
:key="index"
|
||||
:ref="`item-${index}`"
|
||||
:class="{ active: item.active }"
|
||||
:data-index="index"
|
||||
@click="onTap(item, index)"
|
||||
@contextmenu.stop.prevent="openCM($event, item)"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
|
||||
<i class="el-icon-close" v-if="index > 0" @click.stop="onDel(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>
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import { ContextMenu } from "cl-admin-crud";
|
||||
import { last } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-process",
|
||||
|
||||
computed: {
|
||||
...mapGetters(["processList"])
|
||||
},
|
||||
|
||||
watch: {
|
||||
"$route.path"(val) {
|
||||
this.adScroll(this.processList.findIndex(e => e.value === val) || 0);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations(["ADD_PROCESS", "DEL_PROCESS", "SET_PROCESS"]),
|
||||
|
||||
onTap(item, index) {
|
||||
this.adScroll(index);
|
||||
this.$router.push(item.value);
|
||||
},
|
||||
|
||||
onDel(index) {
|
||||
this.DEL_PROCESS(index);
|
||||
this.toPath();
|
||||
},
|
||||
|
||||
openCM(e, item) {
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "关闭当前",
|
||||
hidden: this.$route.path !== item.value,
|
||||
callback: (_, done) => {
|
||||
this.onDel(this.processList.findIndex(e => e.value == item.value));
|
||||
done();
|
||||
this.toPath();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "关闭其他",
|
||||
callback: (_, done) => {
|
||||
this.SET_PROCESS(
|
||||
this.processList.filter(
|
||||
e => e.value == item.value || e.value == "/"
|
||||
)
|
||||
);
|
||||
done();
|
||||
this.toPath();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "关闭所有",
|
||||
callback: (_, done) => {
|
||||
this.SET_PROCESS(this.processList.filter(e => e.value == "/"));
|
||||
done();
|
||||
this.toPath();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
|
||||
toPath() {
|
||||
const active = this.processList.find(e => e.active);
|
||||
|
||||
if (!active) {
|
||||
const next = last(this.processList);
|
||||
this.$router.push(next ? next.value : "/");
|
||||
}
|
||||
},
|
||||
|
||||
adScroll(index) {
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
const el = this.$refs[`item-${index}`][0];
|
||||
|
||||
if (el) {
|
||||
this.scrollTo(el.offsetLeft + el.clientWidth - this.$refs["scroller"].clientWidth);
|
||||
}
|
||||
},
|
||||
|
||||
toScroll(f) {
|
||||
this.scrollTo(this.$refs["scroller"].scrollLeft + (f ? -100 : 100));
|
||||
},
|
||||
|
||||
scrollTo(left) {
|
||||
this.$refs["scroller"].scrollTo({
|
||||
left,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</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,128 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-role-perms" v-loading="loading">
|
||||
<p v-if="title">{{ title }}</p>
|
||||
|
||||
<el-input placeholder="输入关键字进行过滤" v-model="keyword" size="small"> </el-input>
|
||||
|
||||
<div class="scroller">
|
||||
<el-tree
|
||||
:data="list"
|
||||
:props="props"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
ref="tree"
|
||||
@check-change="save"
|
||||
>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-role-perms",
|
||||
|
||||
props: {
|
||||
value: Array,
|
||||
title: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
checked: [],
|
||||
keyword: "",
|
||||
props: {
|
||||
label: "name",
|
||||
children: "children"
|
||||
},
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
keyword(val) {
|
||||
this.$refs["tree"].filter(val);
|
||||
},
|
||||
|
||||
value(val) {
|
||||
this.refreshTree(val);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
methods: {
|
||||
refreshTree(val) {
|
||||
if (!val) {
|
||||
this.checked = [];
|
||||
}
|
||||
|
||||
let ids = [];
|
||||
|
||||
// 处理半选状态
|
||||
let fn = list => {
|
||||
list.forEach(e => {
|
||||
if (e.children) {
|
||||
fn(e.children);
|
||||
} else {
|
||||
ids.push(Number(e.id));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fn(this.list);
|
||||
|
||||
this.checked = ids.filter(id => (val || []).find(e => e == id));
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.$service.system.menu
|
||||
.list()
|
||||
.then(res => {
|
||||
this.list = deepTree(res);
|
||||
|
||||
this.refreshTree(this.value);
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
filterNode(val, data) {
|
||||
if (!val) return true;
|
||||
return data.name.includes(val);
|
||||
},
|
||||
|
||||
save() {
|
||||
const tree = this.$refs["tree"];
|
||||
|
||||
// 选中的节点
|
||||
const checked = tree.getCheckedKeys();
|
||||
// 半选中的节点
|
||||
const halfChecked = tree.getHalfCheckedKeys();
|
||||
|
||||
this.$emit("input", [...checked, ...halfChecked]);
|
||||
}
|
||||
}
|
||||
};
|
||||
</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,55 +0,0 @@
|
||||
<template>
|
||||
<el-select v-model="newValue" v-bind="props" multiple @change="onChange">
|
||||
<el-option
|
||||
v-for="(item, index) in list"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
:key="index"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "cl-role-select",
|
||||
|
||||
props: {
|
||||
value: [String, Number, Array],
|
||||
props: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
newValue: undefined
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
let arr = [];
|
||||
|
||||
if (!(val instanceof Array)) {
|
||||
arr = [val];
|
||||
} else {
|
||||
arr = val;
|
||||
}
|
||||
|
||||
this.newValue = arr.filter(Boolean);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.list = await this.$service.system.role.list();
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,99 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-route-nav">
|
||||
<p class="title" v-if="browser.isMini">
|
||||
{{ lastName }}
|
||||
</p>
|
||||
|
||||
<template v-else>
|
||||
<el-breadcrumb>
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(item, index) in list" :key="index">{{
|
||||
(item.meta && item.meta.label) || item.name
|
||||
}}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import _ from "lodash";
|
||||
|
||||
export default {
|
||||
name: "cl-route-nav",
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route: {
|
||||
immediate: true,
|
||||
handler(route) {
|
||||
const deep = item => {
|
||||
if (route.path === "/") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.path == route.path) {
|
||||
return item;
|
||||
} else {
|
||||
if (item.children) {
|
||||
const ret = item.children.map(deep).find(Boolean);
|
||||
|
||||
if (ret) {
|
||||
return [item, ret];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.list = _(this.menuGroup)
|
||||
.map(deep)
|
||||
.filter(Boolean)
|
||||
.flattenDeep()
|
||||
.value();
|
||||
|
||||
if (this.list.length === 0) {
|
||||
this.list.push(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["menuGroup", "browser"]),
|
||||
|
||||
lastName() {
|
||||
return _.last(this.list).name;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-route-nav {
|
||||
white-space: nowrap;
|
||||
|
||||
/deep/.el-breadcrumb {
|
||||
&__inner {
|
||||
font-size: 13px;
|
||||
padding: 0 10px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<el-scrollbar
|
||||
class="cl-scrollbar"
|
||||
:view-style="[
|
||||
{
|
||||
'overflow-x': 'hidden',
|
||||
width
|
||||
},
|
||||
viewStyle
|
||||
]"
|
||||
:native="native"
|
||||
:wrap-style="wrapStyle"
|
||||
:wrap-class="wrapClass"
|
||||
:view-class="viewClass"
|
||||
:noresize="noresize"
|
||||
:tag="tag"
|
||||
>
|
||||
<slot></slot>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBrowser } from "cl-admin/utils";
|
||||
|
||||
const { plat } = getBrowser();
|
||||
|
||||
export default {
|
||||
name: "cl-scrollbar",
|
||||
|
||||
props: {
|
||||
native: Boolean,
|
||||
wrapStyle: Object,
|
||||
wrapClass: Object,
|
||||
viewClass: Object,
|
||||
viewStyle: Object,
|
||||
noresize: Boolean,
|
||||
tag: {
|
||||
type: String,
|
||||
default: "div"
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: "vertical" // auto, vertical, horizontal
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
width() {
|
||||
return `calc(100% - ${plat == "iphone" ? "10px" : "0px"})`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,7 +0,0 @@
|
||||
import permission, { checkPerm } from "./permission";
|
||||
|
||||
export { checkPerm };
|
||||
|
||||
export default {
|
||||
permission
|
||||
};
|
||||
@ -1,42 +0,0 @@
|
||||
import store from "@/store";
|
||||
|
||||
function change(el, binding) {
|
||||
el.style.display = checkPerm(binding.value) ? el.getAttribute("_display") : "none";
|
||||
}
|
||||
|
||||
function parse(value) {
|
||||
const permission = store.getters.permission;
|
||||
|
||||
if (typeof value == "string") {
|
||||
return value ? permission.some(e => e.includes(value.replace(/\s/g, ""))) : false;
|
||||
} else {
|
||||
return Boolean(value);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
inserted(el, binding) {
|
||||
el.setAttribute("_display", el.style.display || "");
|
||||
|
||||
change(el, binding);
|
||||
},
|
||||
update: change
|
||||
};
|
||||
|
||||
export const checkPerm = value => {
|
||||
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 => !parse(e)) ? false : true;
|
||||
}
|
||||
}
|
||||
|
||||
return parse(value);
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
export default {
|
||||
default_avatar(url) {
|
||||
if (!url) {
|
||||
return require("../static/images/default-avatar.png");
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
default_name(name) {
|
||||
if (!name) {
|
||||
return "未命名";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
};
|
||||
@ -1,12 +0,0 @@
|
||||
import components from "./components";
|
||||
import filters from "./filters";
|
||||
import pages from "./pages";
|
||||
import views from "./views";
|
||||
import store from "./store";
|
||||
import service from "./service";
|
||||
import directives, { checkPerm } from "./directives";
|
||||
import { iconList } from "./common";
|
||||
import "./static/css/index.scss";
|
||||
|
||||
export { iconList, checkPerm };
|
||||
export default { components, filters, pages, views, store, service, directives };
|
||||
@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="403" desc="您无权访问此页面" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="404" desc="找不到您要查找的页面"></error-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="500" desc="糟糕,出了点问题"></error-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="502" desc="马上回来"></error-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<h1 class="code">{{ code }}</h1>
|
||||
<p class="desc">{{ desc }}</p>
|
||||
|
||||
<template v-if="token || isLogout">
|
||||
<div class="router">
|
||||
<el-select size="medium" filterable prefix-icon="el-icon-search" v-model="url">
|
||||
<el-option v-for="(item, index) in routes" :key="index" :value="item.path">
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right">{{ item.path }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-button round @click="navTo">跳转</el-button>
|
||||
</div>
|
||||
|
||||
<div class="link">
|
||||
<el-link class="to-home" @click="home">回到首页</el-link>
|
||||
<el-link class="to-back" @click="back">返回上一页</el-link>
|
||||
<el-link class="to-login" @click="reLogin">重新登录</el-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="router">
|
||||
<el-button round @click="toLogin">返回登录页</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="copyright">Copyright © cool-admin-pro 2020</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { href } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
code: Number,
|
||||
desc: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
url: "",
|
||||
isLogout: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["routes", "token"])
|
||||
},
|
||||
|
||||
methods: {
|
||||
navTo() {
|
||||
this.$router.push(this.url);
|
||||
},
|
||||
|
||||
toLogin() {
|
||||
this.$router.push("/login");
|
||||
},
|
||||
|
||||
reLogin() {
|
||||
this.isLogout = true;
|
||||
|
||||
this.$store.dispatch("userLogout").done(() => {
|
||||
href("/login");
|
||||
});
|
||||
},
|
||||
|
||||
back() {
|
||||
history.back();
|
||||
},
|
||||
|
||||
home() {
|
||||
this.$router.push("/");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.code {
|
||||
font-size: 120px;
|
||||
font-weight: normal;
|
||||
color: #6c757d;
|
||||
font-family: "Segoe UI";
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #34395e;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.router {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 50px;
|
||||
max-width: 450px;
|
||||
width: 90%;
|
||||
|
||||
.el-select {
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
margin-left: 15px;
|
||||
background-color: $color-primary;
|
||||
border-color: $color-primary;
|
||||
color: #fff;
|
||||
padding: 0 30px;
|
||||
letter-spacing: 1px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-top: 40px;
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
transition: all 0.5s;
|
||||
-webkit-transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 0 15px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.copyright {
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,43 +0,0 @@
|
||||
<template>
|
||||
<div class="page-iframe" v-loading="loading" element-loading-text="拼命加载中">
|
||||
<iframe :src="url" frameborder="0"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
url: ""
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route: {
|
||||
handler({ meta }) {
|
||||
this.url = meta.iframeUrl;
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const iframe = this.$el.querySelector("iframe");
|
||||
this.loading = true;
|
||||
|
||||
iframe.onload = () => {
|
||||
this.loading = false;
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-iframe {
|
||||
iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,22 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
path: "/403",
|
||||
component: () => import("./error-page/403")
|
||||
},
|
||||
{
|
||||
path: "/404",
|
||||
component: () => import("./error-page/404")
|
||||
},
|
||||
{
|
||||
path: "/500",
|
||||
component: () => import("./error-page/500")
|
||||
},
|
||||
{
|
||||
path: "/502",
|
||||
component: () => import("./error-page/502")
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: () => import("./login")
|
||||
}
|
||||
];
|
||||
@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div class="common-captcha" @click="refresh">
|
||||
<div class="svg" v-html="svg" v-if="svg"></div>
|
||||
|
||||
<img class="base64" :src="base64" alt="" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
svg: "",
|
||||
base64: ""
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
methods: {
|
||||
refresh() {
|
||||
this.$service.open
|
||||
.captcha({
|
||||
height: 36,
|
||||
width: 110
|
||||
})
|
||||
.then(({ captchaId, data }) => {
|
||||
if (data.includes(";base64,")) {
|
||||
this.base64 = data;
|
||||
} else {
|
||||
this.svg = data;
|
||||
}
|
||||
|
||||
this.$emit("input", captchaId);
|
||||
this.$emit("change", {
|
||||
base64: this.base64,
|
||||
svg: this.svg,
|
||||
captchaId
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.common-captcha {
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
|
||||
.svg {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.base64 {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,193 +0,0 @@
|
||||
<template>
|
||||
<div class="page-login">
|
||||
<div class="box">
|
||||
<img class="logo" src="../../static/images/logo.png" alt="" />
|
||||
<p class="desc">COOL ADMIN是一款快速开发后台权限管理系统</p>
|
||||
|
||||
<el-form ref="form" class="form" size="medium" :disabled="saving">
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
placeholder="请输入用户名"
|
||||
v-model="form.username"
|
||||
maxlength="20"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码">
|
||||
<el-input
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
v-model="form.password"
|
||||
maxlength="20"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="验证码" class="captcha">
|
||||
<el-input
|
||||
placeholder="请输入图片验证码"
|
||||
maxlength="4"
|
||||
v-model="form.verifyCode"
|
||||
auto-complete="off"
|
||||
@keyup.enter.native="next"
|
||||
></el-input>
|
||||
|
||||
<captcha
|
||||
ref="captcha"
|
||||
class="value"
|
||||
v-model="form.captchaId"
|
||||
@change="captchaChange"
|
||||
></captcha>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-button round size="mini" class="submit-btn" @click="next" :loading="saving"
|
||||
>登录</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Captcha from "./components/captcha";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Captcha
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
username: "",
|
||||
password: "",
|
||||
captchaId: "",
|
||||
verifyCode: ""
|
||||
},
|
||||
saving: false
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
captchaChange() {
|
||||
this.form.verifyCode = "";
|
||||
},
|
||||
|
||||
async next() {
|
||||
const { username, password, verifyCode } = this.form;
|
||||
|
||||
if (!username) {
|
||||
return this.$message.warning("用户名不能为空");
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return this.$message.warning("密码不能为空");
|
||||
}
|
||||
|
||||
if (!verifyCode) {
|
||||
return this.$message.warning("图片验证码不能为空");
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
// 登录
|
||||
await this.$store.dispatch("userLogin", this.form);
|
||||
|
||||
// 用户信息
|
||||
await this.$store.dispatch("userInfo");
|
||||
|
||||
// 权限菜单
|
||||
let [first] = await this.$store.dispatch("permMenu");
|
||||
|
||||
if (!first) {
|
||||
this.$message.error("该账号没有权限");
|
||||
} else {
|
||||
this.$router.push("/");
|
||||
}
|
||||
} catch (err) {
|
||||
this.$message.error(err);
|
||||
this.$refs.captcha.refresh();
|
||||
}
|
||||
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.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;
|
||||
}
|
||||
/deep/.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;
|
||||
border-radius: 30px;
|
||||
padding: 10px 40px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,80 +0,0 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
|
||||
@Service("base/comm")
|
||||
class Common extends BaseService {
|
||||
/**
|
||||
* 文件上传模式
|
||||
*/
|
||||
uploadMode() {
|
||||
return this.request({
|
||||
url: "/uploadMode"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传,如果模式是 cloud,返回对应参数
|
||||
*
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
upload(params) {
|
||||
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) {
|
||||
return this.request({
|
||||
url: "/personUpdate",
|
||||
method: "POST",
|
||||
data: {
|
||||
...params
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限信息
|
||||
*
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
permMenu() {
|
||||
return this.request({
|
||||
url: "/permmenu"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Common;
|
||||
@ -1,25 +0,0 @@
|
||||
import Common from "./common";
|
||||
import Open from "./open";
|
||||
import SysUser from "./system/user";
|
||||
import SysMenu from "./system/menu";
|
||||
import SysRole from "./system/role";
|
||||
import SysDept from "./system/dept";
|
||||
import SysParam from "./system/param";
|
||||
import SysLog from "./system/log";
|
||||
import PluginInfo from "./plugin/info";
|
||||
|
||||
export default {
|
||||
common: new Common(),
|
||||
open: new Open(),
|
||||
system: {
|
||||
user: new SysUser(),
|
||||
menu: new SysMenu(),
|
||||
role: new SysRole(),
|
||||
dept: new SysDept(),
|
||||
param: new SysParam(),
|
||||
log: new SysLog()
|
||||
},
|
||||
plugin: {
|
||||
info: new PluginInfo()
|
||||
}
|
||||
};
|
||||
@ -1,56 +0,0 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
|
||||
@Service("base/open")
|
||||
class Open extends BaseService {
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param {*} { username, password, captchaId, verifyCode }
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
userLogin({ username, password, captchaId, verifyCode }) {
|
||||
return this.request({
|
||||
url: "/login",
|
||||
method: "POST",
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
captchaId,
|
||||
verifyCode
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片验证码 svg
|
||||
*
|
||||
* @param {*} { height, width }
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
captcha({ height, width }) {
|
||||
return this.request({
|
||||
url: "/captcha",
|
||||
params: {
|
||||
height,
|
||||
width
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 token
|
||||
* @param {string} token
|
||||
*/
|
||||
refreshToken(token) {
|
||||
return this.request({
|
||||
url: "/refreshToken",
|
||||
params: {
|
||||
refreshToken: token
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Open;
|
||||
@ -1,32 +0,0 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
|
||||
@Service("base/plugin/info")
|
||||
class PluginInfo extends BaseService {
|
||||
@Permission("config")
|
||||
config(data) {
|
||||
return this.request({
|
||||
url: "/config",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("getConfig")
|
||||
getConfig(params) {
|
||||
return this.request({
|
||||
url: "/getConfig",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("enable")
|
||||
enable(data) {
|
||||
return this.request({
|
||||
url: "/enable",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginInfo;
|
||||
@ -1,15 +0,0 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
|
||||
@Service("base/sys/department")
|
||||
class SysDepartment extends BaseService {
|
||||
@Permission("order")
|
||||
order(data) {
|
||||
return this.request({
|
||||
url: "/order",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SysDepartment;
|
||||
@ -1,32 +0,0 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
|
||||
@Service("base/sys/log")
|
||||
class SysLog extends BaseService {
|
||||
@Permission("clear")
|
||||
clear() {
|
||||
return this.request({
|
||||
url: "/clear",
|
||||
method: "POST"
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("getKeep")
|
||||
getKeep() {
|
||||
return this.request({
|
||||
url: "/getKeep"
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("setKeep")
|
||||
setKeep(value) {
|
||||
return this.request({
|
||||
url: "/setKeep",
|
||||
method: "POST",
|
||||
data: {
|
||||
value
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SysLog;
|
||||
@ -1,6 +0,0 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
|
||||
@Service("base/sys/menu")
|
||||
class SysMenu extends BaseService {}
|
||||
|
||||
export default SysMenu;
|
||||
@ -1,6 +0,0 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
|
||||
@Service("base/sys/param")
|
||||
class SysParam extends BaseService {}
|
||||
|
||||
export default SysParam;
|
||||
@ -1,6 +0,0 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
|
||||
@Service("base/sys/role")
|
||||
class SysRole extends BaseService {}
|
||||
|
||||
export default SysRole;
|
||||
@ -1,15 +0,0 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
|
||||
@Service("base/sys/user")
|
||||
class SysUser extends BaseService {
|
||||
@Permission("move")
|
||||
move(data) {
|
||||
return this.request({
|
||||
url: "/move",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SysUser;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user