mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2024-11-01 06:02:38 +08:00
clear
This commit is contained in:
parent
39946cd784
commit
2d3e219f26
@ -1,5 +0,0 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
@ -1,11 +0,0 @@
|
||||
# 🎨 editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
@ -1 +0,0 @@
|
||||
vite.config.ts
|
76
.eslintrc.js
76
.eslintrc.js
@ -1,76 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
parserOptions: {
|
||||
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/singleline-html-element-content-newline": "off",
|
||||
"vue/attribute-hyphenation": "off",
|
||||
// "vue/html-self-closing": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/html-self-closing": [
|
||||
"error",
|
||||
{
|
||||
html: {
|
||||
void: "always",
|
||||
normal: "never",
|
||||
component: "always"
|
||||
},
|
||||
svg: "always",
|
||||
math: "always"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -1,4 +0,0 @@
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.vue text eol=lf
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
}
|
75
.vscode/crud.code-snippets
vendored
75
.vscode/crud.code-snippets
vendored
@ -1,75 +0,0 @@
|
||||
{
|
||||
"cl-crud": {
|
||||
"prefix": "cl-crud-ts",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <cl-crud :ref=\"setRefs('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 :ref=\"setRefs('table')\" v-bind=\"table\" />",
|
||||
" </el-row>",
|
||||
"",
|
||||
" <el-row type=\"flex\">",
|
||||
" <cl-flex1 />",
|
||||
" <!-- 分页控件 -->",
|
||||
" <cl-pagination />",
|
||||
" </el-row>",
|
||||
"",
|
||||
" <!-- 新增、编辑 -->",
|
||||
" <cl-upsert :ref=\"setRefs('upsert')\" v-bind=\"upsert\" />",
|
||||
" </cl-crud>",
|
||||
"</template>",
|
||||
"",
|
||||
"<script lang=\"ts\">",
|
||||
"import { defineComponent, reactive } from \"vue\";",
|
||||
"import { CrudLoad, Upsert, Table } from \"@cool-vue/crud/types\";",
|
||||
"import { useCool } from \"/@/cool\";",
|
||||
"",
|
||||
"export default defineComponent({",
|
||||
" setup() {",
|
||||
" const { refs, setRefs, service } = useCool();",
|
||||
"",
|
||||
" // 新增、编辑配置",
|
||||
" const upsert = reactive<Upsert>({",
|
||||
" items: []",
|
||||
" });",
|
||||
"",
|
||||
" // 表格配置",
|
||||
" const table = reactive<Table>({",
|
||||
" columns: []",
|
||||
" });",
|
||||
"",
|
||||
" // crud 加载",
|
||||
" function onLoad({ ctx, app }: CrudLoad) {",
|
||||
" // 绑定 service",
|
||||
" ctx.service(service.xx).done();",
|
||||
" app.refresh();",
|
||||
" }",
|
||||
"",
|
||||
" return {",
|
||||
" refs,",
|
||||
" setRefs,",
|
||||
" upsert,",
|
||||
" table,",
|
||||
" onLoad",
|
||||
" };",
|
||||
" }",
|
||||
"});",
|
||||
"</script>",
|
||||
""
|
||||
],
|
||||
"description": "cl-crud snippets"
|
||||
}
|
||||
}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"editor.cursorSmoothCaretAnimation": true
|
||||
}
|
16
Dockerfile
16
Dockerfile
@ -1,16 +0,0 @@
|
||||
FROM node:lts-alpine
|
||||
WORKDIR /build
|
||||
# 设置Node-Sass的镜像地址
|
||||
RUN npm config set sass_binary_site https://repo.huaweicloud.com/node-sass
|
||||
# 设置npm镜像
|
||||
RUN npm config set registry https://repo.huaweicloud.com/repository/npm/
|
||||
COPY package.json /build/package.json
|
||||
RUN npm install
|
||||
COPY ./ /build
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx
|
||||
RUN mkdir /app
|
||||
COPY --from=0 /build/dist /app
|
||||
COPY --from=0 /build/nginx.conf /etc/nginx/nginx.conf
|
||||
EXPOSE 80
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 cool-team-official
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
96
README.md
96
README.md
@ -1,96 +0,0 @@
|
||||
# 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,方便快速构建迭代后台管理系统, 到<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" />
|
||||
<a href=""><img src="https://img.shields.io/github/package-json/v/cool-team-official/cool-admin-vue?style=flat-square" alt="GitHub tag"></a>
|
||||
<img src="https://img.shields.io/github/last-commit/cool-team-official/cool-admin-vue?style=flat-square" alt="GitHub tag"></a>
|
||||
</p>
|
||||
|
||||
## 地址
|
||||
|
||||
- [⚡️ vue2.x + element-ui](https://github.com/cool-team-official/cool-admin-vue)
|
||||
|
||||
- [⚡️ vue3.x + element-plus + ts + webpack](https://github.com/cool-team-official/cool-admin-vue/tree/vue3-ts-webpack)
|
||||
|
||||
- [📌 vue3.x + element-plus + ts + vite](https://github.com/cool-team-official/cool-admin-vue/tree/vue3-ts-vite)
|
||||
|
||||
- [🌐 码云仓库地址](https://gitee.com/cool-team-official/cool-admin-vue)
|
||||
|
||||
## 演示
|
||||
|
||||
[https://show.cool-admin.com](https://show.cool-admin.com)
|
||||
|
||||
账户:admin,密码:123456
|
||||
|
||||
<img src="https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/home-mini.png" alt="Admin Home" ></a>
|
||||
|
||||
## 项目后端
|
||||
|
||||
[https://github.com/cool-team-official/cool-admin-midway](https://github.com/cool-team-official/cool-admin-midway)
|
||||
|
||||
## 微信群
|
||||
|
||||
<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/)
|
||||
|
||||
## 安装项目依赖
|
||||
|
||||
推荐使用 `yarn`:
|
||||
|
||||
```shell
|
||||
yarn
|
||||
```
|
||||
|
||||
解决 `node-sass` 网络慢的方法:
|
||||
|
||||
```shell
|
||||
yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass
|
||||
```
|
||||
|
||||
## 运行应用程序
|
||||
|
||||
安装过程完成后,运行以下命令启动服务。您可以在浏览器中预览网站 [http://localhost:9000](http://localhost:9000)
|
||||
|
||||
```shell
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### 服务器
|
||||
|
||||
#### 腾讯云特供
|
||||
|
||||
不限新老用户,注册过买过都可以享受
|
||||
|
||||
|配置|价格|条件|备注|
|
||||
|---------|-------|-------|-------|
|
||||
|2核2g2M|一年240|个人企业限一台(不限新老用户)||
|
||||
|2核4g2M|一年260、两年380|个人企业限一台(不限新老用户)||
|
||||
|2核4g3M|一年260、三年600|企业(不限新老用户)||
|
||||
|2核4g5M|一年280、三年660|企业(不限新老用户)||
|
||||
|4核8g5M|一年320、三年720|企业(不限新老用户)||
|
||||
|4核8g10M|一年560、三年1520|企业(不限新老用户)||
|
||||
|8核16g5M|一年1800、三年3800|限企业新用户|送独立数据库|
|
||||
|8核16g10M|一年2200、三年6600|限企业新用户|送独立数据库|
|
||||
|16核32g5M|一年2600、三年6900|限企业新用户|送独立数据库|
|
||||
|16核32g10M|一年2900、三年9600|限企业新用户|送独立数据库|
|
||||
|
||||
#### 购买咨询,数量有限!!!
|
||||
|
||||
<img width="260" src="https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/wechat.jpeg?v=1" alt="Admin Wechat"></a>
|
||||
|
||||
#### 阿里云
|
||||
|
||||
[点击链接购买](https://www.aliyun.com/minisite/goods?userCode=pw6cig1f)
|
@ -1,214 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
test: ["avatar", "img", "image", "pic", "photo", "picture", "head", "icon"],
|
||||
table: {
|
||||
name: "cl-image",
|
||||
props: {
|
||||
size: 60
|
||||
}
|
||||
},
|
||||
form: {
|
||||
name: "cl-upload"
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["avatars", "imgs", "images", "pics", "photos", "pictures", "heads", "icons"],
|
||||
table: {
|
||||
name: "cl-image",
|
||||
props: {
|
||||
size: 60
|
||||
}
|
||||
},
|
||||
form: {
|
||||
name: "cl-upload",
|
||||
props: {
|
||||
listType: "picture-card",
|
||||
multiple: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["file", "attachment", "attach", "url", "video", "music"],
|
||||
table: {
|
||||
name: "cl-link"
|
||||
},
|
||||
form: {
|
||||
name: "cl-upload",
|
||||
props: {
|
||||
listType: "text",
|
||||
limit: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["files", "attachments", "attachs", "urls", "videos", "musics"],
|
||||
table: {
|
||||
name: "cl-link"
|
||||
},
|
||||
form: {
|
||||
name: "cl-upload",
|
||||
props: {
|
||||
listType: "text",
|
||||
multiple: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["enable", "status"],
|
||||
table: {
|
||||
name: "cl-switch"
|
||||
},
|
||||
form: {
|
||||
name: "el-switch"
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["type", "classify", "category"],
|
||||
handler: "dict"
|
||||
},
|
||||
{
|
||||
test: ["types", "classifys", "categorys"],
|
||||
handler: "dict_multiple"
|
||||
},
|
||||
{
|
||||
test: ["date"],
|
||||
table: {
|
||||
name: "cl-date",
|
||||
props: {
|
||||
format: "YYYY-MM-DD"
|
||||
}
|
||||
},
|
||||
form: {
|
||||
name: "el-date-picker",
|
||||
props: {
|
||||
type: "date",
|
||||
valueFormat: "YYYY-MM-DD"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["dates", "dateRange", "dateScope"],
|
||||
table: {
|
||||
name: "cl-date",
|
||||
props: {
|
||||
format: "YYYY-MM-DD"
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
name: "el-date-picker",
|
||||
props: {
|
||||
type: "daterange",
|
||||
valueFormat: "YYYY-MM-DD"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["time"],
|
||||
form: {
|
||||
name: "el-date-picker",
|
||||
props: {
|
||||
type: "datetime",
|
||||
valueFormat: "YYYY-MM-DD HH:mm:ss"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["times", "timeRange", "timeScope"],
|
||||
form: {
|
||||
component: {
|
||||
name: "el-date-picker",
|
||||
props: {
|
||||
type: "datetimerange",
|
||||
valueFormat: "YYYY-MM-DD HH:mm:ss",
|
||||
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["star", "stars"],
|
||||
table: {
|
||||
name: "el-rate",
|
||||
props: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
form: {
|
||||
name: "el-rate"
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["progress", "rate", "ratio"],
|
||||
table: {
|
||||
name: "el-progress"
|
||||
},
|
||||
form: {
|
||||
name: "el-slider",
|
||||
props: {
|
||||
style: {
|
||||
width: "200px"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["num", "price", "age", "amount"],
|
||||
form: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["remark", "desc"],
|
||||
table: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
form: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["rich", "text", "html", "content", "introduce", "description", "desc"],
|
||||
table: {
|
||||
name: "cl-editor-quill"
|
||||
},
|
||||
form: {
|
||||
name: "cl-editor-quill",
|
||||
props: {
|
||||
height: 400
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["code", "codes"],
|
||||
table: {
|
||||
name: "cl-codemirror"
|
||||
},
|
||||
form: {
|
||||
name: "cl-codemirror",
|
||||
props: {
|
||||
height: 400
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["createTime"],
|
||||
table: {
|
||||
sortable: "desc"
|
||||
}
|
||||
},
|
||||
{
|
||||
test: ["updateTime"],
|
||||
table: {
|
||||
sortable: "custom"
|
||||
}
|
||||
}
|
||||
];
|
@ -1,432 +0,0 @@
|
||||
import { Plugin } from "vite";
|
||||
import prettier from "prettier";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { isFunction, isRegExp, isString } from "lodash";
|
||||
import rules from "../config/rules";
|
||||
|
||||
// 根路径
|
||||
const coolPath = path.join(__dirname, `../../src/cool`);
|
||||
|
||||
// 格式化
|
||||
function format(data: any) {
|
||||
return {
|
||||
label: data.label,
|
||||
prop: data.prop,
|
||||
...data,
|
||||
component: data.component
|
||||
};
|
||||
}
|
||||
|
||||
// 颜色
|
||||
const colors = [
|
||||
"#409EFF",
|
||||
"#67C23A",
|
||||
"#E6A23C",
|
||||
"#F56C6C",
|
||||
"#909399",
|
||||
"#B0CFEB",
|
||||
"#FF9B91",
|
||||
"#E6A23C",
|
||||
"#BFAD6F",
|
||||
"#FB78F2"
|
||||
];
|
||||
|
||||
// 组件处理器
|
||||
const handler = {
|
||||
// 单选
|
||||
dict({ comment }) {
|
||||
const [label, ...arr] = comment.split(" ");
|
||||
|
||||
// 选择列表
|
||||
const list = arr.map((e: string, i: number) => {
|
||||
const [value, label] = e.split("-");
|
||||
const d: any = {
|
||||
label,
|
||||
value: isNaN(Number(value)) ? value : Number(value)
|
||||
};
|
||||
|
||||
if (i > 0 && colors[i]) {
|
||||
d.color = colors[i];
|
||||
}
|
||||
|
||||
return d;
|
||||
});
|
||||
|
||||
const d: any = {
|
||||
table: {
|
||||
label,
|
||||
dict: list
|
||||
},
|
||||
form: {
|
||||
label,
|
||||
component: {
|
||||
name: "",
|
||||
options: list
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 默认值
|
||||
if (list[0]) {
|
||||
d.form.value = list[0].value;
|
||||
}
|
||||
|
||||
// 匹配组件
|
||||
d.form.component.name = arr.length > 4 ? "el-select" : "el-radio-group";
|
||||
|
||||
return d;
|
||||
},
|
||||
|
||||
// 多选
|
||||
dict_multiple({ comment }) {
|
||||
const { table, form }: any = handler.dict({ comment });
|
||||
|
||||
if (!form.component.props) {
|
||||
form.component.props = {};
|
||||
}
|
||||
|
||||
if (!form.value) {
|
||||
form.value = [];
|
||||
}
|
||||
|
||||
switch (form.component.name) {
|
||||
case "el-select":
|
||||
form.component.props.multiple = true;
|
||||
form.component.props.filterable = true;
|
||||
break;
|
||||
case "el-radio-group":
|
||||
form.component.name = "el-checkbox-group";
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
table,
|
||||
form
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 解析body
|
||||
function parseJson(req: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let d = "";
|
||||
req.on("data", function (chunk: Buffer) {
|
||||
d += chunk;
|
||||
});
|
||||
req.on("end", function () {
|
||||
try {
|
||||
resolve(JSON.parse(d));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 创建组件
|
||||
function createComponent(item: any) {
|
||||
const { propertyName: prop, comment: label } = item;
|
||||
|
||||
let d = null;
|
||||
|
||||
rules.forEach((r: any) => {
|
||||
const s = r.test.find((e: any) => {
|
||||
if (isRegExp(e)) {
|
||||
return e.test(prop);
|
||||
}
|
||||
|
||||
if (isFunction(e)) {
|
||||
return e(prop);
|
||||
}
|
||||
|
||||
if (isString(e)) {
|
||||
const re = new RegExp(`${e}$`);
|
||||
return re.test(prop.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (s) {
|
||||
if (r.handler) {
|
||||
const fn = isString(r.handler) ? handler[r.handler] : r.handler;
|
||||
|
||||
if (isFunction(fn)) {
|
||||
d = fn(item);
|
||||
}
|
||||
} else {
|
||||
d = {
|
||||
...r,
|
||||
test: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function parse(v: any) {
|
||||
if (v?.name) {
|
||||
return {
|
||||
prop,
|
||||
label,
|
||||
component: v
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
prop,
|
||||
label,
|
||||
...v
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
column: parse(d?.table),
|
||||
item: parse(d?.form)
|
||||
};
|
||||
}
|
||||
|
||||
// 获取页面标识
|
||||
function getPageName(router: string) {
|
||||
if (router.indexOf("/") === 0) {
|
||||
router = router.substr(1, router.length);
|
||||
}
|
||||
|
||||
return router ? `name: "${router.replace("/", "-")}",` : "";
|
||||
}
|
||||
|
||||
// 时间合并
|
||||
function datetimeMerge({ columns, item }: any) {
|
||||
if (["startTime", "startDate"].includes(item.prop)) {
|
||||
const key = item.prop.replace("start", "");
|
||||
|
||||
if (columns.find((e: any) => e.propertyName == "end" + key)) {
|
||||
item.prop = key.toLocaleLowerCase();
|
||||
const isTime = item.prop == "time";
|
||||
item.label = isTime ? "时间范围" : "日期范围";
|
||||
item.hook = "datetimeRange";
|
||||
item.component = {
|
||||
name: "el-date-picker",
|
||||
props: {
|
||||
type: isTime ? "datetimerange" : "daterange",
|
||||
valueFormat: isTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD 00:00:00",
|
||||
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文件
|
||||
function createVue({ router, columns, prefix, api, module, filename }: any): void {
|
||||
const upsert: any = {
|
||||
items: []
|
||||
};
|
||||
|
||||
const table: any = {
|
||||
columns: []
|
||||
};
|
||||
|
||||
// 遍历
|
||||
columns.forEach((e: any) => {
|
||||
// 组件
|
||||
const { item, column }: any = createComponent(e);
|
||||
|
||||
// 验证规则
|
||||
if (!e.nullable) {
|
||||
item.required = true;
|
||||
}
|
||||
|
||||
// 忽略部分字段
|
||||
if (!["createTime", "updateTime", "id", "endTime", "endDate"].includes(item.prop)) {
|
||||
datetimeMerge({ columns, item });
|
||||
|
||||
if (!item.component) {
|
||||
item.component = {
|
||||
name: "el-input"
|
||||
};
|
||||
}
|
||||
|
||||
upsert.items.push(format(item));
|
||||
}
|
||||
|
||||
if (!["cl-codemirror", "cl-editor-quill"].includes(column.component?.name)) {
|
||||
table.columns.push(format(column));
|
||||
}
|
||||
});
|
||||
|
||||
// 服务
|
||||
const service = prefix.replace("/admin", "service").replace(/\//g, ".");
|
||||
|
||||
// 请求路径
|
||||
const paths = api.map((e: any) => e.path);
|
||||
|
||||
// 权限
|
||||
const permission: any = {
|
||||
add: paths.includes("/add"),
|
||||
del: paths.includes("/delete"),
|
||||
update: paths.includes("/info") && paths.includes("/update"),
|
||||
page: paths.includes("/page"),
|
||||
upsert: true
|
||||
};
|
||||
permission.upsert = permission.add || permission.update;
|
||||
|
||||
// 是否有操作栏
|
||||
if (permission.del || permission.upsert) {
|
||||
const d: any = {
|
||||
type: "op",
|
||||
buttons: []
|
||||
};
|
||||
|
||||
if (permission.upsert) {
|
||||
d.buttons.push("edit");
|
||||
}
|
||||
|
||||
if (permission.del) {
|
||||
d.buttons.push("delete");
|
||||
}
|
||||
|
||||
table.columns.push(d);
|
||||
}
|
||||
|
||||
// 是否多选、序号
|
||||
if (permission.del) {
|
||||
table.columns.unshift({
|
||||
type: "selection"
|
||||
});
|
||||
} else {
|
||||
table.columns.unshift({
|
||||
label: "#",
|
||||
type: "index"
|
||||
});
|
||||
}
|
||||
|
||||
// 代码模板
|
||||
const temp = `<template>
|
||||
<cl-crud :ref="setRefs('crud')" @load="onLoad">
|
||||
<el-row type="flex" align="middle">
|
||||
<!-- 刷新按钮 -->
|
||||
<cl-refresh-btn />
|
||||
${permission.add ? "<!-- 新增按钮 -->\n<cl-add-btn />" : ""}
|
||||
${permission.del ? "<!-- 删除按钮 -->\n<cl-multi-delete-btn />" : ""}
|
||||
<cl-flex1 />
|
||||
<!-- 关键字搜索 -->
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<!-- 数据表格 -->
|
||||
<cl-table :ref="setRefs('table')" v-bind="table" />
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<!-- 分页控件 -->
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
|
||||
${
|
||||
permission.update
|
||||
? '<!-- 新增、编辑 -->\n<cl-upsert :ref="setRefs(\'upsert\')" v-bind="upsert" />'
|
||||
: ""
|
||||
}
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive } from "vue";
|
||||
import { CrudLoad, Table${permission.upsert ? ", Upsert" : ""} } from "@cool-vue/crud/types";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
${getPageName(router)}
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, service } = useCool();
|
||||
|
||||
${
|
||||
permission.upsert
|
||||
? "// 新增、编辑配置\nconst upsert = reactive<Upsert>(" +
|
||||
JSON.stringify(upsert) +
|
||||
");"
|
||||
: ""
|
||||
}
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>(${JSON.stringify(table)});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
// 绑定 service
|
||||
ctx.service(${service}).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
setRefs,${permission.upsert ? "upsert," : ""}
|
||||
table,
|
||||
onLoad
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>`;
|
||||
|
||||
const content = prettier.format(temp, {
|
||||
parser: "vue",
|
||||
useTabs: true,
|
||||
tabWidth: 4,
|
||||
endOfLine: "lf",
|
||||
semi: true,
|
||||
jsxBracketSameLine: true,
|
||||
singleQuote: false,
|
||||
printWidth: 100,
|
||||
trailingComma: "none"
|
||||
});
|
||||
|
||||
// views 目录是否存在
|
||||
const dir = path.join(coolPath, `modules/${module}/views`);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
||||
|
||||
// 创建文件
|
||||
fs.createWriteStream(path.join(dir, `${filename}.vue`), {
|
||||
flags: "w"
|
||||
}).write(content);
|
||||
}
|
||||
|
||||
export const cool = (): Plugin | null => {
|
||||
return {
|
||||
name: "vite-cool",
|
||||
configureServer(server) {
|
||||
server.middlewares.use(async (req, res, next) => {
|
||||
function done(data) {
|
||||
res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
|
||||
res.end(JSON.stringify(data));
|
||||
}
|
||||
|
||||
if (req.url.includes("/__cool_createMenu")) {
|
||||
try {
|
||||
const body: any = await parseJson(req);
|
||||
await createVue(body);
|
||||
done({
|
||||
code: 1000
|
||||
});
|
||||
} catch (e) {
|
||||
done({
|
||||
code: 1001,
|
||||
message: e.message
|
||||
});
|
||||
}
|
||||
} else if (req.url.includes("/__cool_modules")) {
|
||||
const dirs = fs.readdirSync(path.join(coolPath, "modules"));
|
||||
done({
|
||||
code: 1000,
|
||||
data: dirs.filter((e) => !e.includes("."))
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
@ -1,71 +0,0 @@
|
||||
import { Plugin } from "vite";
|
||||
import { readFileSync, readdirSync } from "fs";
|
||||
|
||||
let idPerfix = "";
|
||||
const svgTitle = /<svg([^>+].*?)>/;
|
||||
const clearHeightWidth = /(width|height)="([^>+].*?)"/g;
|
||||
|
||||
const hasViewBox = /(viewBox="[^>+].*?")/g;
|
||||
|
||||
const clearReturn = /(\r)|(\n)/g;
|
||||
|
||||
function findSvgFile(dir: string): string[] {
|
||||
const svgRes = [];
|
||||
const dirents = readdirSync(dir, {
|
||||
withFileTypes: true
|
||||
});
|
||||
for (const dirent of dirents) {
|
||||
if (dirent.isDirectory()) {
|
||||
svgRes.push(...findSvgFile(dir + dirent.name + "/"));
|
||||
} else {
|
||||
const svg = readFileSync(dir + dirent.name)
|
||||
.toString()
|
||||
.replace(clearReturn, "")
|
||||
.replace(svgTitle, (_: any, $2: any) => {
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let content = $2.replace(clearHeightWidth, (_: any, s2: any, s3: any) => {
|
||||
if (s2 === "width") {
|
||||
width = s3;
|
||||
} else if (s2 === "height") {
|
||||
height = s3;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
if (!hasViewBox.test($2)) {
|
||||
content += `viewBox="0 0 ${width} ${height}"`;
|
||||
}
|
||||
return `<symbol id="${idPerfix}-${dirent.name.replace(
|
||||
".svg",
|
||||
""
|
||||
)}" ${content}>`;
|
||||
})
|
||||
.replace("</svg>", "</symbol>");
|
||||
svgRes.push(svg);
|
||||
}
|
||||
}
|
||||
return svgRes;
|
||||
}
|
||||
|
||||
export const svgBuilder = (path: string, perfix = "icon"): Plugin | null => {
|
||||
if (path !== "") {
|
||||
idPerfix = perfix;
|
||||
const res = findSvgFile(path);
|
||||
return {
|
||||
name: "svg-transform",
|
||||
transformIndexHtml(html): string {
|
||||
return html.replace(
|
||||
"<body>",
|
||||
`
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
|
||||
${res.join("")}
|
||||
</svg>
|
||||
`
|
||||
);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
157
index.html
157
index.html
@ -1,157 +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"
|
||||
/>
|
||||
<title>COOL-ADMIN</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 {
|
||||
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;
|
||||
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 id="app">
|
||||
<div class="preload">
|
||||
<div class="container">
|
||||
<p class="name">COOL-ADMIN</p>
|
||||
<div class="loading"></div>
|
||||
<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>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
123
nginx.conf
123
nginx.conf
@ -1,123 +0,0 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
upstream backend {
|
||||
server midway:7001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location /api/
|
||||
{
|
||||
proxy_pass http://backend/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
|
||||
#缓存相关配置
|
||||
#proxy_cache cache_one;
|
||||
#proxy_cache_key $host$request_uri$is_args$args;
|
||||
#proxy_cache_valid 200 304 301 302 1h;
|
||||
|
||||
#持久化连接相关配置
|
||||
proxy_connect_timeout 3000s;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 3000s;
|
||||
#proxy_http_version 1.1;
|
||||
#proxy_set_header Upgrade $http_upgrade;
|
||||
#proxy_set_header Connection "upgrade";
|
||||
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
|
||||
#expires 12h;
|
||||
}
|
||||
location /im {
|
||||
proxy_pass http://backend/im;
|
||||
proxy_connect_timeout 3600s; #配置点1
|
||||
proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
|
||||
proxy_send_timeout 3600s; #配置点3
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
#proxy_bind $remote_addr transparent;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
# rewrite /socket/(.*) /$1 break;
|
||||
proxy_redirect off;
|
||||
|
||||
}
|
||||
|
||||
location /socket {
|
||||
proxy_pass http://backend/socket;
|
||||
proxy_connect_timeout 3600s; #配置点1
|
||||
proxy_read_timeout 3600s; #配置点2,如果没效,可以考虑这个时间配置长一点
|
||||
proxy_send_timeout 3600s; #配置点3
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
#proxy_bind $remote_addr transparent;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
rewrite /socket/(.*) /$1 break;
|
||||
proxy_redirect off;
|
||||
|
||||
}
|
||||
|
||||
|
||||
location /adminer/
|
||||
{
|
||||
proxy_pass http://adminer:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
|
||||
#缓存相关配置
|
||||
#proxy_cache cache_one;
|
||||
#proxy_cache_key $host$request_uri$is_args$args;
|
||||
#proxy_cache_valid 200 304 301 302 1h;
|
||||
|
||||
#持久化连接相关配置
|
||||
proxy_connect_timeout 3000s;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 3000s;
|
||||
#proxy_http_version 1.1;
|
||||
#proxy_set_header Upgrade $http_upgrade;
|
||||
#proxy_set_header Connection "upgrade";
|
||||
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
|
||||
#expires 12h;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
62
package.json
62
package.json
@ -1,62 +0,0 @@
|
||||
{
|
||||
"name": "front-next",
|
||||
"version": "4.0.1",
|
||||
"scripts": {
|
||||
"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,mock}/**/*.{vue,ts,tsx}\" --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cool-vue/crud": "^1.0.6",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"axios": "^0.21.1",
|
||||
"clipboard": "^2.0.8",
|
||||
"codemirror": "^5.62.0",
|
||||
"core-js": "^3.6.5",
|
||||
"echarts": "^5.0.2",
|
||||
"element-plus": "^1.1.0-beta.20",
|
||||
"file-saver": "^2.0.5",
|
||||
"js-beautify": "^1.13.5",
|
||||
"mitt": "^2.1.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"quill": "^1.3.7",
|
||||
"socket.io-client": "^4.1.2",
|
||||
"store": "^2.0.12",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.2.20",
|
||||
"vue-echarts": "^6.0.0-rc.3",
|
||||
"vue-router": "^4.0.5",
|
||||
"vuedraggable": "^4.0.1",
|
||||
"vuex": "^4.0.0-0",
|
||||
"xlsx": "^0.16.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^16.10.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
||||
"@typescript-eslint/parser": "^4.20.0",
|
||||
"@vitejs/plugin-vue": "1.9.2",
|
||||
"@vitejs/plugin-vue-jsx": "^1.1.6",
|
||||
"@vue/compiler-sfc": "3.2.19",
|
||||
"@vue/composition-api": "^1.0.0-rc.13",
|
||||
"eslint": "^7.23.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.13.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"prettier": "^2.2.1",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^11.1.1",
|
||||
"svg-sprite-loader": "^6.0.2",
|
||||
"typescript": "4.4.3",
|
||||
"unplugin-vue-components": "0.15.4",
|
||||
"vite": "2.6.7",
|
||||
"vite-plugin-compression": "^0.3.5",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-style-import": "^1.0.1",
|
||||
"vite-svg-loader": "^2.1.0"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
12044
public/theme/black.css
12044
public/theme/black.css
File diff suppressed because it is too large
Load Diff
12008
public/theme/blue.css
12008
public/theme/blue.css
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
12008
public/theme/green.css
12008
public/theme/green.css
File diff suppressed because it is too large
Load Diff
12035
public/theme/purple.css
12035
public/theme/purple.css
File diff suppressed because it is too large
Load Diff
56
src/App.vue
56
src/App.vue
@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<el-config-provider :locale="locale">
|
||||
<div class="preload" v-if="loading">
|
||||
<div class="container">
|
||||
<p class="name">{{ app.name }}</p>
|
||||
<div class="loading"></div>
|
||||
<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>
|
||||
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
[ElConfigProvider.name]: ElConfigProvider
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { store, app } = useCool();
|
||||
const locale = zhCn;
|
||||
const loading = computed(() => store.getters.appLoading);
|
||||
|
||||
return {
|
||||
locale,
|
||||
loading,
|
||||
app
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./assets/css/index.scss"></style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.preload {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
@ -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,35 +0,0 @@
|
||||
$color-primary: #4165d7;
|
||||
$color-success: #67c23a;
|
||||
$color-danger: #f56c6c;
|
||||
$color-info: #909399;
|
||||
$color-warning: #e6a23c;
|
||||
|
||||
$--colors: (
|
||||
"primary": (
|
||||
"base": $color-primary
|
||||
),
|
||||
"success": (
|
||||
"base": $color-success
|
||||
),
|
||||
"warning": (
|
||||
"base": $color-success
|
||||
),
|
||||
"danger": (
|
||||
"base": $color-danger
|
||||
),
|
||||
"info": (
|
||||
"base": $color-info
|
||||
)
|
||||
);
|
||||
|
||||
@forward "element-plus/theme-chalk/src/common/var.scss" with (
|
||||
$colors: $--colors
|
||||
);
|
||||
|
||||
:export {
|
||||
colorPrimary: $color-primary;
|
||||
colorSuccess: $color-success;
|
||||
colorDanger: $color-danger;
|
||||
colorInfo: $color-info;
|
||||
colorWarning: $color-warning;
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input {
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0px 1000px white inset;
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
@ -1,53 +0,0 @@
|
||||
import store from "store";
|
||||
import { getUrlParam } from "/@/cool/utils";
|
||||
import { MenuItem } from "/$/base/types";
|
||||
|
||||
// 路由模式
|
||||
const routerMode: String = "history";
|
||||
|
||||
// 开发模式
|
||||
const isDev: Boolean = import.meta.env.MODE === "development";
|
||||
|
||||
// Host
|
||||
const host: String = "https://show.cool-admin.com";
|
||||
|
||||
// 请求地址
|
||||
const baseUrl: String = (function () {
|
||||
let proxy = getUrlParam("proxy");
|
||||
|
||||
if (proxy) {
|
||||
store.set("proxy", proxy);
|
||||
} else {
|
||||
proxy = store.get("proxy") || "dev";
|
||||
}
|
||||
|
||||
return isDev ? `/${proxy}/admin` : `/api/admin`;
|
||||
})();
|
||||
|
||||
// Socket
|
||||
const socketUrl: String = (isDev ? `${host}` : "") + "/socket";
|
||||
|
||||
// 阿里字体图标库 https://at.alicdn.com/t/**.css
|
||||
const iconfontUrl = ``;
|
||||
|
||||
// 程序配置参数
|
||||
const app: any = store.get("__app__") || {
|
||||
name: "COOL-ADMIN",
|
||||
|
||||
conf: {
|
||||
showAMenu: false, // 是否显示一级菜单栏
|
||||
showRouteNav: true, // 是否显示路由导航栏
|
||||
showProcess: true, // 是否显示页面进程栏
|
||||
customMenu: false // 自定义菜单
|
||||
},
|
||||
|
||||
theme: {
|
||||
color: "", // 主题色
|
||||
url: "" // 主题样式地址
|
||||
}
|
||||
};
|
||||
|
||||
// 自定义菜单列表
|
||||
const menuList: MenuItem[] = [];
|
||||
|
||||
export { routerMode, baseUrl, socketUrl, iconfontUrl, app, isDev, menuList };
|
@ -1,49 +0,0 @@
|
||||
import { onBeforeUpdate, ref, inject, computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
export function useRefs() {
|
||||
const refs: any = ref<any[]>([]);
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
refs.value = [];
|
||||
});
|
||||
|
||||
const setRefs = (index: string) => (el: any) => {
|
||||
refs.value[index] = el;
|
||||
};
|
||||
|
||||
return { refs, setRefs };
|
||||
}
|
||||
|
||||
export function useCool() {
|
||||
const { refs, setRefs } = useRefs();
|
||||
const service = inject<any>("service");
|
||||
const mitt = inject<any>("mitt");
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const app = computed(() => store.getters.app);
|
||||
|
||||
return {
|
||||
store,
|
||||
route,
|
||||
router,
|
||||
refs,
|
||||
setRefs,
|
||||
service,
|
||||
mitt,
|
||||
app
|
||||
};
|
||||
}
|
||||
|
||||
export function useModule() {
|
||||
const store = useStore();
|
||||
const moduleList = computed(() => store.getters.moduleList);
|
||||
const modules = computed(() => store.getters.modules);
|
||||
|
||||
return {
|
||||
moduleList,
|
||||
modules
|
||||
};
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import router from "/@/router";
|
||||
import store from "/@/store";
|
||||
import { service } from "./service";
|
||||
import { useRouter } from "./router";
|
||||
import { useModule } from "./module";
|
||||
|
||||
async function bootstrap(app: any) {
|
||||
app.config.globalProperties.service = store.service = service;
|
||||
app.provide("service", service);
|
||||
|
||||
useRouter();
|
||||
useModule(app);
|
||||
|
||||
router.$plugin?.addViews(store.getters.routes || []);
|
||||
}
|
||||
|
||||
function usePermission(list: any[]) {
|
||||
function deep(d: any) {
|
||||
if (d.permission) {
|
||||
d._permission = {};
|
||||
for (const i in d.permission) {
|
||||
d._permission[i] =
|
||||
list.findIndex((e: string) =>
|
||||
e.replace(/:/g, "/").includes(`${d.namespace}/${i}`)
|
||||
) >= 0;
|
||||
}
|
||||
} else {
|
||||
for (const i in d) {
|
||||
deep(d[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deep(service);
|
||||
}
|
||||
|
||||
export { service, bootstrap, usePermission };
|
||||
export { BaseService, Service, Permission, useEps } from "./service";
|
||||
export * from "./hook";
|
@ -1,198 +0,0 @@
|
||||
import { modules as mods } from "/@/cool/modules";
|
||||
import store from "/@/store";
|
||||
import router from "/@/router";
|
||||
import { deepMerge, isFunction, isObject, isEmpty } from "../utils";
|
||||
import { deepFiles } from "../service";
|
||||
|
||||
// 模块列表
|
||||
const modules: any[] = [...mods];
|
||||
|
||||
function useModule(app: any) {
|
||||
// 安装模块
|
||||
function install(mod: any) {
|
||||
const { store: _store, service, directives, components, pages, views, name } = mod;
|
||||
|
||||
try {
|
||||
// 注册vuex模块
|
||||
if (_store) {
|
||||
for (const i in _store) {
|
||||
store.registerModule(`${name}-${i}`, _store[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册请求服务
|
||||
if (service) {
|
||||
// @ts-ignore
|
||||
deepMerge(store.service, service);
|
||||
}
|
||||
|
||||
// 注册组件
|
||||
if (components) {
|
||||
for (const i in components) {
|
||||
if (components[i]) {
|
||||
if (components[i].cool?.global || i.indexOf("cl-") === 0) {
|
||||
app.component(components[i].name, components[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册指令
|
||||
if (directives) {
|
||||
for (const i in directives) {
|
||||
app.directive(i, directives[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册页面
|
||||
if (pages) {
|
||||
pages.forEach((e: any) => {
|
||||
router.addRoute(e);
|
||||
});
|
||||
}
|
||||
|
||||
// 注册视图
|
||||
if (views) {
|
||||
views.forEach((e: any) => {
|
||||
if (!e.meta) {
|
||||
e.meta = {};
|
||||
}
|
||||
|
||||
if (e.path) {
|
||||
router.$plugin?.addViews([e]);
|
||||
} else {
|
||||
console.error(`[${name}-views]:缺少 path 参数`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`模块 ${name} 异常`, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描文件
|
||||
const files = import.meta.globEager("/src/cool/modules/**/*");
|
||||
|
||||
for (const i in files) {
|
||||
const [, , , , name, fn, cname] = i.split("/");
|
||||
const value: any = files[i].default;
|
||||
const fname: string = (cname || "").split(".")[0];
|
||||
|
||||
if (name == "index.ts") {
|
||||
continue;
|
||||
}
|
||||
|
||||
function next(d: any) {
|
||||
// 配置参数入口
|
||||
if (fn == "config.ts") {
|
||||
d.options = value || {};
|
||||
}
|
||||
|
||||
// 模块入口
|
||||
if (fn == "index.ts") {
|
||||
if (value) {
|
||||
// 阻止往下加载
|
||||
d.isLoaded = true;
|
||||
|
||||
// 之前
|
||||
d._beforeFn = (e: any) => {
|
||||
if (e.components) {
|
||||
for (const i in e.components) {
|
||||
// 全局注册
|
||||
e.components[i].cool = {
|
||||
global: true
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
d.value = value;
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
// 其他功能
|
||||
switch (fn) {
|
||||
case "service":
|
||||
d._services.push({
|
||||
path: i.replace(`/src/cool/modules/${name}/service`, `${name}`),
|
||||
value: new value()
|
||||
});
|
||||
break;
|
||||
|
||||
case "pages":
|
||||
case "views":
|
||||
if (value.cool) {
|
||||
d[fn].push({
|
||||
...value.cool.route,
|
||||
component: value
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "components":
|
||||
d.components[value.name] = value;
|
||||
break;
|
||||
|
||||
case "store":
|
||||
d.store[fname] = value;
|
||||
break;
|
||||
|
||||
case "directives":
|
||||
d.directives[fname] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
const item: any = modules.find((e) => e.name === name);
|
||||
|
||||
if (item) {
|
||||
if (!item.isLoaded) {
|
||||
next(item);
|
||||
}
|
||||
} else {
|
||||
modules.push(
|
||||
next({
|
||||
name,
|
||||
options: {},
|
||||
directives: {},
|
||||
components: {},
|
||||
pages: [],
|
||||
views: [],
|
||||
store: {},
|
||||
_services: [],
|
||||
_local: true
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 模块安装
|
||||
modules.forEach((e: any) => {
|
||||
if (!isEmpty(e._services)) {
|
||||
e.service = deepFiles(e._services);
|
||||
}
|
||||
|
||||
if (isObject(e.value)) {
|
||||
if (isFunction(e.value.install)) {
|
||||
Object.assign(e, e.value.install(app, e.options));
|
||||
} else {
|
||||
Object.assign(e, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (e._beforeFn) {
|
||||
e._beforeFn(e);
|
||||
}
|
||||
|
||||
install(e);
|
||||
});
|
||||
|
||||
// 缓存模块
|
||||
store.commit("SET_MODULE", modules);
|
||||
}
|
||||
|
||||
export { useModule };
|
@ -1,99 +0,0 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
import store from "/@/store";
|
||||
import router, { ignore } from "/@/router";
|
||||
import { cloneDeep, storage } from "../utils";
|
||||
|
||||
const views = import.meta.globEager("/src/**/views/**/*.vue");
|
||||
|
||||
for (const i in views) {
|
||||
views[i.slice(5)] = views[i];
|
||||
delete views[i];
|
||||
}
|
||||
|
||||
function useRouter() {
|
||||
router.$plugin = {
|
||||
addViews: (list: Array<any>, options: any) => {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// Parse route config
|
||||
list.forEach((e: any) => {
|
||||
const d: any = cloneDeep(e);
|
||||
|
||||
// avoid router repeat
|
||||
d.name = d.router;
|
||||
|
||||
if (!d.component) {
|
||||
const url = d.viewPath;
|
||||
|
||||
if (url) {
|
||||
if (
|
||||
/^(http[s]?:\/\/)([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i.test(
|
||||
url
|
||||
)
|
||||
) {
|
||||
d.meta.iframeUrl = url;
|
||||
d.component = () => import(`/$/base/pages/iframe/index.vue`);
|
||||
} else {
|
||||
d.component = () => Promise.resolve(views[url]);
|
||||
}
|
||||
} else {
|
||||
d.redirect = "/404";
|
||||
}
|
||||
}
|
||||
|
||||
// Batch add route
|
||||
router.addRoute("index", d);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
router.beforeEach((to: any, from: any, next: any) => {
|
||||
const { token, browser } = store.getters;
|
||||
|
||||
if (token) {
|
||||
if (to.path.indexOf("/login") === 0) {
|
||||
// 登录成功且 token 未过期,回到首页
|
||||
if (!storage.isExpired("token")) {
|
||||
return next("/");
|
||||
}
|
||||
} else {
|
||||
// 添加路由进程
|
||||
store.commit("ADD_PROCESS", {
|
||||
keepAlive: to.meta?.keepAlive,
|
||||
label: to.meta?.label || to.name,
|
||||
value: to.fullPath
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!ignore.token.some((e: string) => to.path.indexOf(e) === 0)) {
|
||||
return next("/login");
|
||||
}
|
||||
}
|
||||
|
||||
// H5 下关闭左侧菜单
|
||||
if (browser && browser.isMini) {
|
||||
store.commit("COLLAPSE_MENU", true);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
let lock = false;
|
||||
|
||||
router.onError((err: any) => {
|
||||
if (!lock) {
|
||||
lock = true;
|
||||
|
||||
ElMessage.error(`页面不存在或者未配置`);
|
||||
console.error(err);
|
||||
|
||||
setTimeout(() => {
|
||||
lock = false;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { useRouter };
|
@ -1,104 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import request from "/@/service/request";
|
||||
import { baseUrl, isDev } from "/@/config/env";
|
||||
|
||||
export default class BaseService {
|
||||
constructor(options: any = {}) {
|
||||
const crud: any = {
|
||||
page: "page",
|
||||
list: "list",
|
||||
info: "info",
|
||||
add: "add",
|
||||
delete: "delete",
|
||||
update: "update"
|
||||
};
|
||||
|
||||
if (options?.namespace) {
|
||||
this.namespace = options?.namespace;
|
||||
}
|
||||
|
||||
if (!this.permission) this.permission = {};
|
||||
|
||||
for (const i in crud) {
|
||||
if (this.namespace) {
|
||||
this.permission[i] = this.namespace.replace(/\//g, ":") + ":" + crud[i];
|
||||
} else {
|
||||
this.permission[i] = crud[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request(options: any = {}) {
|
||||
if (!options.params) options.params = {};
|
||||
|
||||
let ns = "";
|
||||
|
||||
// 是否 mock 模式
|
||||
if (!this.mock) {
|
||||
if (isDev) {
|
||||
ns = this.proxy || baseUrl;
|
||||
} else {
|
||||
ns = this.proxy ? this.url : baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// 拼接前缀
|
||||
if (this.namespace) {
|
||||
ns += "/" + this.namespace;
|
||||
}
|
||||
|
||||
// 处理 http
|
||||
if (options.url.indexOf("http") !== 0) {
|
||||
options.url = ns + options.url;
|
||||
}
|
||||
|
||||
return request(options);
|
||||
}
|
||||
|
||||
list(data: any) {
|
||||
return this.request({
|
||||
url: "/list",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
page(data: any) {
|
||||
return this.request({
|
||||
url: "/page",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
info(params: any) {
|
||||
return this.request({
|
||||
url: "/info",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
update(data: any) {
|
||||
return this.request({
|
||||
url: "/update",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
delete(data: any) {
|
||||
return this.request({
|
||||
url: "/delete",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
add(data: any) {
|
||||
return this.request({
|
||||
url: "/add",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import { isObject } from "../utils";
|
||||
|
||||
export function Permission(value: string) {
|
||||
return function (target: any, key: any, descriptor: any) {
|
||||
if (!target.permission) {
|
||||
target.permission = {};
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
target.permission[key] = (
|
||||
(target.namespace ? target.namespace + "/" : "") + value
|
||||
).replace(/\//g, ":");
|
||||
}, 0);
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function Service(value: any) {
|
||||
return function (target: any) {
|
||||
// 命名
|
||||
if (typeof value == "string") {
|
||||
target.prototype.namespace = value;
|
||||
}
|
||||
|
||||
// 复杂项
|
||||
if (isObject(value)) {
|
||||
const { proxy, namespace, url, mock } = value;
|
||||
const item = __PROXY_LIST__[proxy];
|
||||
|
||||
if (proxy && !item) {
|
||||
console.error(`${proxy} 指向的地址不存在!`);
|
||||
}
|
||||
|
||||
target.prototype.namespace = namespace;
|
||||
target.prototype.mock = mock;
|
||||
|
||||
if (proxy) {
|
||||
target.prototype.proxy = proxy;
|
||||
target.prototype.url = url || (item ? item.target : null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
import BaseService from "./base";
|
||||
import { Service, Permission } from "./decorator";
|
||||
import { basename } from "../utils";
|
||||
|
||||
function deepFiles(list: any[]) {
|
||||
const modules: any = {};
|
||||
|
||||
list.forEach((e) => {
|
||||
const arr: any[] = e.path.split("/");
|
||||
const parents: any[] = arr.slice(0, arr.length - 1);
|
||||
const name: string = basename(e.path).replace(".ts", "");
|
||||
|
||||
let curr: any = modules;
|
||||
let prev: any = null;
|
||||
let key: any = null;
|
||||
|
||||
parents.forEach((k) => {
|
||||
if (!curr[k]) {
|
||||
curr[k] = {};
|
||||
}
|
||||
|
||||
prev = curr;
|
||||
curr = curr[k];
|
||||
key = k;
|
||||
});
|
||||
|
||||
if (name == "index") {
|
||||
prev[key] = e.value;
|
||||
} else {
|
||||
curr[name] = e.value;
|
||||
}
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
function useService() {
|
||||
const files = import.meta.globEager("/src/service/**/*.ts");
|
||||
const d: any = [];
|
||||
|
||||
for (const i in files) {
|
||||
if (!i.includes("request.ts")) {
|
||||
const value = files[i].default;
|
||||
d.push({
|
||||
path: i.replace("/src/service/", ""),
|
||||
value: new value()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const s = deepFiles(d);
|
||||
s.request = new BaseService().request;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
const service = useService();
|
||||
|
||||
function useEps() {
|
||||
return service.base.common
|
||||
.eps()
|
||||
.then((res: any) => {
|
||||
for (const i in res) {
|
||||
res[i].forEach((e: any) => {
|
||||
// 分隔路径
|
||||
const arr = e.prefix
|
||||
.replace(/\//, "")
|
||||
.replace("admin", "")
|
||||
.split("/")
|
||||
.filter(Boolean);
|
||||
|
||||
function deep(d: any, i: number) {
|
||||
const k = arr[i];
|
||||
|
||||
if (k) {
|
||||
// 是否最后一个
|
||||
if (arr[i + 1]) {
|
||||
if (!d[k]) {
|
||||
d[k] = {};
|
||||
}
|
||||
|
||||
deep(d[k], i + 1);
|
||||
} else {
|
||||
// 本地不存在则创建实例
|
||||
if (!d[k]) {
|
||||
d[k] = new BaseService({
|
||||
namespace: e.prefix.replace("/admin/", "")
|
||||
});
|
||||
}
|
||||
|
||||
// 创建方法
|
||||
e.api.forEach((a: any) => {
|
||||
const n = a.path.replace("/", "");
|
||||
|
||||
if (
|
||||
![
|
||||
"add",
|
||||
"info",
|
||||
"update",
|
||||
"page",
|
||||
"list",
|
||||
"delete"
|
||||
].includes(n)
|
||||
) {
|
||||
// 设置权限
|
||||
d[k].permission[n] = (
|
||||
(d[k].namespace ? d[k].namespace + "/" : "") + n
|
||||
).replace(/\//g, ":");
|
||||
|
||||
// 本地不存在则创建
|
||||
if (!d[k][n]) {
|
||||
d[k][n] = function (data: any) {
|
||||
return this.request({
|
||||
url: a.path,
|
||||
method: a.method,
|
||||
[a.method.toLocaleLowerCase() == "post"
|
||||
? "data"
|
||||
: "params"]: data
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deep(service, 0);
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
})
|
||||
.catch((err: string) => {
|
||||
console.error("Eps error", err);
|
||||
});
|
||||
}
|
||||
|
||||
export { BaseService, Service, Permission, service, deepFiles, useService, useEps };
|
@ -1,301 +0,0 @@
|
||||
import { routerMode } from "/@/config/env";
|
||||
import storage from "./storage";
|
||||
|
||||
export function isArray(value: any) {
|
||||
if (typeof Array.isArray === "function") {
|
||||
return Array.isArray(value);
|
||||
} else {
|
||||
return Object.prototype.toString.call(value) === "[object Array]";
|
||||
}
|
||||
}
|
||||
|
||||
export function isObject(value: any) {
|
||||
return Object.prototype.toString.call(value) === "[object Object]";
|
||||
}
|
||||
|
||||
export function isNumber(value: any) {
|
||||
return !isNaN(Number(value));
|
||||
}
|
||||
|
||||
export function isFunction(value: any) {
|
||||
return typeof value == "function";
|
||||
}
|
||||
|
||||
export function isString(value: any) {
|
||||
return typeof value == "string";
|
||||
}
|
||||
|
||||
export function isEmpty(value: any) {
|
||||
if (isArray(value)) {
|
||||
return value.length === 0;
|
||||
}
|
||||
|
||||
if (isObject(value)) {
|
||||
return Object.keys(value).length === 0;
|
||||
}
|
||||
|
||||
return value === "" || value === undefined || value === null;
|
||||
}
|
||||
|
||||
export function isBoolean(value: any) {
|
||||
return typeof value === "boolean";
|
||||
}
|
||||
|
||||
export function last(data: any) {
|
||||
if (isArray(data) || isString(data)) {
|
||||
return data[data.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneDeep(obj: any) {
|
||||
const d = isArray(obj) ? obj : {};
|
||||
|
||||
if (isObject(obj)) {
|
||||
for (const key in obj) {
|
||||
if (obj[key]) {
|
||||
if (obj[key] && typeof obj[key] === "object") {
|
||||
d[key] = cloneDeep(obj[key]);
|
||||
} else {
|
||||
d[key] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
export function clone(obj: any) {
|
||||
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
|
||||
}
|
||||
|
||||
export function deepMerge(a: any, b: any) {
|
||||
let k;
|
||||
for (k in b) {
|
||||
a[k] =
|
||||
a[k] && a[k].toString() === "[object Object]" ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
export function contains(parent: any, node: any) {
|
||||
while (node && (node = node.parentNode)) if (node === parent) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getUrlParam(name: string) {
|
||||
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||
const r = window.location.search.substr(1).match(reg);
|
||||
if (r != null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isPc() {
|
||||
const userAgentInfo = navigator.userAgent;
|
||||
const Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
|
||||
let flag = true;
|
||||
for (let v = 0; v < Agents.length; v++) {
|
||||
if (userAgentInfo.indexOf(Agents[v]) > 0) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
export function getBrowser() {
|
||||
const { clientHeight, clientWidth } = document.documentElement;
|
||||
|
||||
// 浏览器信息
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
|
||||
// 浏览器类型
|
||||
let type = (ua.match(/firefox|chrome|safari|opera/g) || "other")[0];
|
||||
|
||||
if ((ua.match(/msie|trident/g) || [])[0]) {
|
||||
type = "msie";
|
||||
}
|
||||
|
||||
// 平台标签
|
||||
let tag = "";
|
||||
|
||||
const isTocuh =
|
||||
"ontouchstart" in window || ua.indexOf("touch") !== -1 || ua.indexOf("mobile") !== -1;
|
||||
if (isTocuh) {
|
||||
if (ua.indexOf("ipad") !== -1) {
|
||||
tag = "pad";
|
||||
} else if (ua.indexOf("mobile") !== -1) {
|
||||
tag = "mobile";
|
||||
} else if (ua.indexOf("android") !== -1) {
|
||||
tag = "androidPad";
|
||||
} else {
|
||||
tag = "pc";
|
||||
}
|
||||
} else {
|
||||
tag = "pc";
|
||||
}
|
||||
|
||||
// 浏览器内核
|
||||
let prefix = "";
|
||||
|
||||
switch (type) {
|
||||
case "chrome":
|
||||
case "safari":
|
||||
case "mobile":
|
||||
prefix = "webkit";
|
||||
break;
|
||||
case "msie":
|
||||
prefix = "ms";
|
||||
break;
|
||||
case "firefox":
|
||||
prefix = "Moz";
|
||||
break;
|
||||
case "opera":
|
||||
prefix = "O";
|
||||
break;
|
||||
default:
|
||||
prefix = "webkit";
|
||||
break;
|
||||
}
|
||||
|
||||
// 操作平台
|
||||
const plat = ua.indexOf("android") > 0 ? "android" : navigator.platform.toLowerCase();
|
||||
|
||||
// 屏幕信息
|
||||
let screen = "full";
|
||||
|
||||
if (clientWidth < 768) {
|
||||
screen = "xs";
|
||||
} else if (clientWidth < 992) {
|
||||
screen = "sm";
|
||||
} else if (clientWidth < 1200) {
|
||||
screen = "md";
|
||||
} else if (clientWidth < 1920) {
|
||||
screen = "xl";
|
||||
} else {
|
||||
screen = "full";
|
||||
}
|
||||
|
||||
// 是否 ios
|
||||
const isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
|
||||
|
||||
// 浏览器版本
|
||||
const version = (ua.match(/[\s\S]+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1];
|
||||
|
||||
// 是否 PC 端
|
||||
const isPC = tag === "pc";
|
||||
|
||||
// 是否移动端
|
||||
const isMobile = isPC ? false : true;
|
||||
|
||||
// 是否移动端 + 屏幕宽过小
|
||||
const isMini = screen === "xs" || isMobile;
|
||||
|
||||
return {
|
||||
height: clientHeight,
|
||||
width: clientWidth,
|
||||
version,
|
||||
type,
|
||||
plat,
|
||||
tag,
|
||||
prefix,
|
||||
isMobile,
|
||||
isIOS,
|
||||
isPC,
|
||||
isMini,
|
||||
screen
|
||||
};
|
||||
}
|
||||
|
||||
export function href(path: string, newWindow?: boolean) {
|
||||
const { origin, pathname } = window.location;
|
||||
|
||||
if (pathname == path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let url = "";
|
||||
|
||||
if (routerMode == "history") {
|
||||
url = origin + import.meta.env.BASE_URL + path.substr(1);
|
||||
} else {
|
||||
url = origin + import.meta.env.BASE_URL + "#" + path;
|
||||
}
|
||||
|
||||
if (newWindow) {
|
||||
window.open(url);
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
export function orderBy(list: Array<any>, key: any) {
|
||||
return list.sort((a, b) => a[key] - b[key]);
|
||||
}
|
||||
|
||||
export function deepTree(list: Array<any>) {
|
||||
const newList: Array<any> = [];
|
||||
const map: any = {};
|
||||
|
||||
list.forEach((e) => (map[e.id] = e));
|
||||
|
||||
list.forEach((e) => {
|
||||
const parent = map[e.parentId];
|
||||
|
||||
if (parent) {
|
||||
(parent.children || (parent.children = [])).push(e);
|
||||
} else {
|
||||
newList.push(e);
|
||||
}
|
||||
});
|
||||
|
||||
const fn = (list: Array<any>) => {
|
||||
list.map((e) => {
|
||||
if (e.children instanceof Array) {
|
||||
e.children = orderBy(e.children, "orderNum");
|
||||
|
||||
fn(e.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fn(newList);
|
||||
|
||||
return orderBy(newList, "orderNum");
|
||||
}
|
||||
|
||||
export function revDeepTree(list: Array<any> = []) {
|
||||
const d: Array<any> = [];
|
||||
let id = 0;
|
||||
|
||||
const deep = (list: Array<any>, parentId: any) => {
|
||||
list.forEach((e) => {
|
||||
if (!e.id) {
|
||||
e.id = id++;
|
||||
}
|
||||
|
||||
e.parentId = parentId;
|
||||
|
||||
d.push(e);
|
||||
|
||||
if (e.children && isArray(e.children)) {
|
||||
deep(e.children, e.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
deep(list || [], null);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
export function basename(path: string) {
|
||||
let index = path.lastIndexOf("/");
|
||||
index = index > -1 ? index : path.lastIndexOf("\\");
|
||||
if (index < 0) {
|
||||
return path;
|
||||
}
|
||||
return path.substring(index + 1);
|
||||
}
|
||||
|
||||
export { storage };
|
@ -1,81 +0,0 @@
|
||||
import store from "store";
|
||||
|
||||
export default {
|
||||
// 后缀标识
|
||||
suffix: "_deadtime",
|
||||
|
||||
/**
|
||||
* 获取
|
||||
* @param {string} key 关键字
|
||||
*/
|
||||
get(key: string) {
|
||||
return store.get(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取全部
|
||||
*/
|
||||
info() {
|
||||
const d: any = {};
|
||||
|
||||
store.each(function (value: any, key: any) {
|
||||
d[key] = value;
|
||||
});
|
||||
|
||||
return d;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置
|
||||
* @param {string} key 关键字
|
||||
* @param {*} value 值
|
||||
* @param {number} expires 过期时间
|
||||
*/
|
||||
set(key: string, value: any, expires?: any) {
|
||||
store.set(key, value);
|
||||
|
||||
if (expires) {
|
||||
store.set(`${key}${this.suffix}`, Date.parse(String(new Date())) + expires * 1000);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否过期
|
||||
* @param {string} key 关键字
|
||||
*/
|
||||
isExpired(key: string) {
|
||||
return (this.getExpiration(key) || 0) - Date.parse(String(new Date())) <= 2000;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取到期时间
|
||||
* @param {string} key 关键字
|
||||
*/
|
||||
getExpiration(key: string) {
|
||||
return this.get(key + this.suffix);
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除
|
||||
* @param {string} key 关键字
|
||||
*/
|
||||
remove(key: string) {
|
||||
store.remove(key);
|
||||
this.removeExpiration(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除到期时间
|
||||
* @param {string} key 关键字
|
||||
*/
|
||||
removeExpiration(key: string) {
|
||||
store.remove(key + this.suffix);
|
||||
},
|
||||
|
||||
/**
|
||||
* 清理
|
||||
*/
|
||||
clearAll() {
|
||||
store.clearAll();
|
||||
}
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
export * from "./core";
|
||||
export * from "./modules";
|
@ -1,4 +0,0 @@
|
||||
import { iconList } from "./theme";
|
||||
import "./resize";
|
||||
|
||||
export { iconList };
|
@ -1,52 +0,0 @@
|
||||
import store from "/@/store";
|
||||
|
||||
const lock: any = {
|
||||
menuCollapse: null,
|
||||
showAMenu: null
|
||||
};
|
||||
|
||||
function resize() {
|
||||
// 更新数据
|
||||
store.commit("SET_BROWSER");
|
||||
|
||||
const { browser, menuCollapse, app } = store.getters;
|
||||
|
||||
if (browser.isMini) {
|
||||
// 小屏幕下隐藏一级菜单
|
||||
if (lock.showAMenu === null) {
|
||||
lock.showAMenu = app.conf.showAMenu;
|
||||
store.commit("UPDATE_APP", {
|
||||
conf: {
|
||||
showAMenu: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 小屏幕下收起左侧菜单
|
||||
if (lock.menuCollapse === null) {
|
||||
lock.menuCollapse = menuCollapse;
|
||||
store.commit("COLLAPSE_MENU", true);
|
||||
}
|
||||
} else {
|
||||
// 大屏幕下显示一级菜单
|
||||
if (lock.showAMenu !== null) {
|
||||
store.commit("UPDATE_APP", {
|
||||
conf: {
|
||||
showAMenu: lock.showAMenu
|
||||
}
|
||||
});
|
||||
lock.showAMenu = null;
|
||||
}
|
||||
|
||||
// 大屏幕下展开左侧菜单
|
||||
if (lock.menuCollapse !== null) {
|
||||
store.commit("COLLAPSE_MENU", lock.menuCollapse);
|
||||
lock.menuCollapse = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
window.addEventListener("resize", resize);
|
||||
resize();
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
import { iconfontUrl, app } from "/@/config/env";
|
||||
import { basename } from "/@/cool/utils";
|
||||
import { createLink } from "../utils";
|
||||
|
||||
// 主题初始化
|
||||
|
||||
if (app.theme) {
|
||||
const { url, color } = app.theme;
|
||||
|
||||
if (url) {
|
||||
createLink(url, "theme-style");
|
||||
}
|
||||
|
||||
document.getElementsByTagName("body")[0].style.setProperty("--color-primary", color);
|
||||
}
|
||||
|
||||
// 字体图标库加载
|
||||
|
||||
if (iconfontUrl) {
|
||||
createLink(iconfontUrl);
|
||||
}
|
||||
|
||||
// svg 图标加载
|
||||
|
||||
const svgFiles = import.meta.globEager("/src/icons/svg/**/*.svg");
|
||||
|
||||
function iconList() {
|
||||
const list: string[] = [];
|
||||
|
||||
for (const i in svgFiles) {
|
||||
list.push(basename(i).replace(".svg", ""));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export { iconList };
|
@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-avatar" :class="[size, shape]" :style="[style]">
|
||||
<el-image :src="src || modelValue" alt="">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="el-icon-user-solid" :style="{ color }"></i>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { isNumber } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-avatar",
|
||||
|
||||
props: {
|
||||
modelValue: String,
|
||||
src: String,
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 36
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
default: "circle"
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#f7f7f7"
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#ccc"
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const size = isNumber(props.size) ? props.size + "px" : props.size;
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
height: size,
|
||||
width: size,
|
||||
backgroundColor: props.backgroundColor
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
style
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-avatar {
|
||||
overflow: hidden;
|
||||
|
||||
&.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%;
|
||||
|
||||
.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,142 +0,0 @@
|
||||
<template>
|
||||
<div ref="editorRef" class="cl-codemirror">
|
||||
<textarea id="editor" class="cl-code" :height="height" :width="width"></textarea>
|
||||
|
||||
<div class="cl-codemirror__tools">
|
||||
<el-button size="mini" @click="formatCode">格式化</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick, onMounted, ref, watch } from "vue";
|
||||
import CodeMirror from "codemirror";
|
||||
import beautifyJs from "js-beautify";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/theme/hopscotch.css";
|
||||
import "codemirror/addon/hint/javascript-hint";
|
||||
import "codemirror/mode/javascript/javascript";
|
||||
import { deepMerge } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-codemirror",
|
||||
|
||||
props: {
|
||||
modelValue: null,
|
||||
height: String,
|
||||
width: String,
|
||||
options: Object
|
||||
},
|
||||
|
||||
emits: ["update:modelValue", "load"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const editorRef = ref<any>(null);
|
||||
|
||||
let editor: any = null;
|
||||
|
||||
// 获取内容
|
||||
function getValue() {
|
||||
if (editor) {
|
||||
return editor.getValue();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// 设置内容
|
||||
function setValue(val?: string) {
|
||||
if (editor) {
|
||||
editor.setValue(val || "");
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化
|
||||
function formatCode() {
|
||||
if (editor) {
|
||||
editor.setValue(beautifyJs(getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// 监听内容变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string) => {
|
||||
if (editor && val != getValue()) {
|
||||
setValue(val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(function () {
|
||||
nextTick(() => {
|
||||
// 实例化
|
||||
editor = CodeMirror.fromTextArea(
|
||||
editorRef.value.querySelector("#editor"),
|
||||
deepMerge(
|
||||
{
|
||||
mode: "javascript",
|
||||
theme: "hopscotch",
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
indentWithTabs: true,
|
||||
indentUnit: 4,
|
||||
extraKeys: { Ctrl: "autocomplete" },
|
||||
foldGutter: true,
|
||||
autofocus: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
gutters: [
|
||||
"CodeMirror-linenumbers",
|
||||
"CodeMirror-foldgutter",
|
||||
"CodeMirror-lint-markers"
|
||||
]
|
||||
},
|
||||
props.options
|
||||
)
|
||||
);
|
||||
|
||||
// 输入监听
|
||||
editor.on("change", (e: any) => {
|
||||
emit("update:modelValue", e.getValue());
|
||||
});
|
||||
|
||||
// 设置内容
|
||||
setValue(props.modelValue);
|
||||
|
||||
// 格式化
|
||||
formatCode();
|
||||
|
||||
// 加载回调
|
||||
emit("load", editor);
|
||||
|
||||
// 设置编辑框大小
|
||||
editor.setSize(props.width || "auto", props.height || "auto");
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
editorRef,
|
||||
formatCode
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-codemirror {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #dcdfe6;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
line-height: 150%;
|
||||
|
||||
&__tools {
|
||||
background-color: #322931;
|
||||
padding: 10px;
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<span class="cl-date">{{ value }}</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-date",
|
||||
|
||||
props: {
|
||||
modelValue: [String, Number],
|
||||
format: {
|
||||
type: String,
|
||||
default: "YYYY-MM-DD HH:mm:ss"
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const value = computed(() => {
|
||||
return props.modelValue ? dayjs(props.modelValue).format(props.format) : "";
|
||||
});
|
||||
|
||||
return {
|
||||
value
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,178 +0,0 @@
|
||||
<template>
|
||||
<div v-loading="loading" class="cl-dept-check">
|
||||
<p v-if="title">{{ title }}</p>
|
||||
|
||||
<div class="cl-dept-check__search">
|
||||
<el-input v-model="keyword" placeholder="输入关键字进行过滤" size="small" />
|
||||
<el-switch
|
||||
v-model="form.relevance"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="onCheckStrictlyChange"
|
||||
/>是否关联上下级
|
||||
</div>
|
||||
|
||||
<div v-if="visible" class="cl-dept-check__tree">
|
||||
<el-tree
|
||||
:ref="setRefs('tree')"
|
||||
highlight-current
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
:data="list"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
:check-strictly="!form.relevance"
|
||||
@check-change="onCheckChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { deepTree } from "/@/cool/utils";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, inject, nextTick, onMounted, ref, watch } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-dept-check",
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
title: String
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { service, refs, setRefs } = useCool();
|
||||
|
||||
// 表单值
|
||||
const form = inject<any>("form");
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 已选列表
|
||||
const checked = ref<any>([]);
|
||||
|
||||
// 关键字搜素
|
||||
const keyword = ref<string>("");
|
||||
|
||||
// 加载中
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(true);
|
||||
|
||||
// 刷新已选列表
|
||||
function refreshTree(val: any[]) {
|
||||
checked.value = val || [];
|
||||
}
|
||||
|
||||
// 刷新树形列表
|
||||
function refresh() {
|
||||
service.base.sys.department
|
||||
.list()
|
||||
.then((res: any[]) => {
|
||||
list.value = deepTree(res);
|
||||
refreshTree(props.modelValue);
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤节点
|
||||
function filterNode(val: string, data: any) {
|
||||
if (!val) return true;
|
||||
return data.name.includes(val);
|
||||
}
|
||||
|
||||
// 是否关联上下级
|
||||
function onCheckStrictlyChange() {
|
||||
visible.value = false;
|
||||
checked.value = [];
|
||||
emit("update:modelValue", []);
|
||||
|
||||
nextTick(() => {
|
||||
visible.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
// 监听选择
|
||||
function onCheckChange() {
|
||||
if (refs.value.tree) {
|
||||
emit("update:modelValue", refs.value.tree.getCheckedKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// 监听过滤
|
||||
watch(keyword, (val: string) => {
|
||||
refs.value.tree.filter(val);
|
||||
});
|
||||
|
||||
// 刷新树
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: any[]) => {
|
||||
refreshTree(val);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
setRefs,
|
||||
form,
|
||||
list,
|
||||
checked,
|
||||
keyword,
|
||||
loading,
|
||||
visible,
|
||||
refresh,
|
||||
filterNode,
|
||||
onCheckStrictlyChange,
|
||||
onCheckChange
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-dept-check {
|
||||
&__search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.el-input {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.el-switch {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__tree {
|
||||
border: 1px solid #dcdfe6;
|
||||
margin-top: 5px;
|
||||
border-radius: 3px;
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,121 +0,0 @@
|
||||
import { useCool } from "/@/cool";
|
||||
import { deepTree } from "/@/cool/utils";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { defineComponent, h, ref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-dept-move",
|
||||
|
||||
emits: ["success", "error"],
|
||||
|
||||
setup(_: any, { emit }) {
|
||||
const { refs, setRefs, service }: any = useCool();
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 刷新列表
|
||||
async function refresh() {
|
||||
return await service.base.sys.department.list().then(deepTree);
|
||||
}
|
||||
|
||||
// 转移
|
||||
async function toMove(ids: any[]) {
|
||||
list.value = await refresh();
|
||||
|
||||
refs.value.form.open({
|
||||
props: {
|
||||
title: "部门转移",
|
||||
width: "600px",
|
||||
labelWidth: "80px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "选择部门",
|
||||
prop: "dept",
|
||||
component: {
|
||||
name: "slot-move"
|
||||
}
|
||||
}
|
||||
],
|
||||
on: {
|
||||
submit: (data: any, { done, close }: any) => {
|
||||
if (!data.dept) {
|
||||
ElMessage.warning("请选择部门");
|
||||
return done();
|
||||
}
|
||||
|
||||
const { name, id } = data.dept;
|
||||
|
||||
ElMessageBox.confirm(`是否将用户转移到部门 ${name} 下`, "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
service.base.sys.user
|
||||
.move({
|
||||
departmentId: id,
|
||||
userIds: ids
|
||||
})
|
||||
.then((res: any) => {
|
||||
ElMessage.success("转移成功");
|
||||
emit("success", res);
|
||||
close();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
ElMessage.error(err);
|
||||
emit("error", err);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
list,
|
||||
setRefs,
|
||||
refresh,
|
||||
toMove
|
||||
};
|
||||
},
|
||||
|
||||
render(ctx: any) {
|
||||
return (
|
||||
<div class="cl-dept-move">
|
||||
{h(
|
||||
<cl-form ref={ctx.setRefs("form")}></cl-form>,
|
||||
{},
|
||||
{
|
||||
"slot-move"({ scope }: any) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #eee",
|
||||
borderRadius: "3px",
|
||||
padding: "2px"
|
||||
}}
|
||||
>
|
||||
<el-tree
|
||||
data={ctx.list}
|
||||
props={{
|
||||
label: "name"
|
||||
}}
|
||||
node-key="id"
|
||||
highlight-current
|
||||
onNodeClick={(e: any) => {
|
||||
scope["dept"] = e;
|
||||
}}
|
||||
></el-tree>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,445 +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 && !isMini">
|
||||
<el-tooltip content="拖动排序">
|
||||
<i class="el-icon-s-operation" @click="isDrag = true"></i>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
|
||||
<li v-show="isDrag" class="no">
|
||||
<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.stop.prevent="openCM">
|
||||
<el-tree
|
||||
v-loading="loading"
|
||||
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"
|
||||
@node-contextmenu="openCM"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="cl-dept-tree__node">
|
||||
<span class="cl-dept-tree__node-label" @click="rowClick(data)">{{
|
||||
node.label
|
||||
}}</span>
|
||||
<span
|
||||
v-if="isMini"
|
||||
class="cl-dept-tree__node-icon"
|
||||
@click="openCM($event, data, node)"
|
||||
>
|
||||
<i class="el-icon-more"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
|
||||
<cl-form :ref="setRefs('form')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { ContextMenu } from "@cool-vue/crud";
|
||||
import { useCool } from "/@/cool";
|
||||
import { deepTree, isArray, revDeepTree, isPc } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-dept-tree",
|
||||
|
||||
props: {
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 99
|
||||
}
|
||||
},
|
||||
|
||||
emits: ["list-change", "row-click", "user-add"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { refs, setRefs, service } = useCool();
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 加载中
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// 是否能拖动
|
||||
const isDrag = ref<boolean>(false);
|
||||
|
||||
// 允许托的规则
|
||||
function allowDrag({ data }: any) {
|
||||
return data.parentId;
|
||||
}
|
||||
|
||||
// 允许放的规则
|
||||
function allowDrop(_: any, dropNode: any) {
|
||||
return dropNode.data.parentId;
|
||||
}
|
||||
|
||||
// 刷新
|
||||
async function refresh() {
|
||||
isDrag.value = false;
|
||||
loading.value = true;
|
||||
|
||||
await service.base.sys.department.list().then((res: any[]) => {
|
||||
list.value = deepTree(res);
|
||||
emit("list-change", list.value);
|
||||
});
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 获取 ids
|
||||
function rowClick(e: any) {
|
||||
const ids = e.children ? revDeepTree(e.children).map((e) => e.id) : [];
|
||||
ids.unshift(e.id);
|
||||
emit("row-click", { item: e, ids });
|
||||
}
|
||||
|
||||
// 编辑部门
|
||||
function rowEdit(e: any) {
|
||||
const method = e.id ? "update" : "add";
|
||||
|
||||
refs.value.form.open({
|
||||
title: "编辑部门",
|
||||
width: "550px",
|
||||
props: {
|
||||
labelWidth: "100px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "部门名称",
|
||||
prop: "name",
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写部门名称"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "部门名称不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "上级部门",
|
||||
prop: "parentName",
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "orderNum",
|
||||
component: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
"controls-position": "right",
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
form: e,
|
||||
on: {
|
||||
submit: (data: any, { done, close }: any) => {
|
||||
service.base.sys.department[method]({
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
name: data.name,
|
||||
orderNum: data.orderNum
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success(`新增部门${data.name}成功`);
|
||||
close();
|
||||
refresh();
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 删除部门
|
||||
function rowDel(e: any) {
|
||||
const del = async (f: boolean) => {
|
||||
await service.base.sys.department
|
||||
.delete({
|
||||
ids: [e.id],
|
||||
deleteUser: f
|
||||
})
|
||||
.then(() => {
|
||||
if (f) {
|
||||
ElMessage.success("删除成功");
|
||||
} else {
|
||||
ElMessageBox.confirm(
|
||||
`“${e.name}” 部门的用户已成功转移到 “${e.parentName}” 部门。`,
|
||||
"删除成功"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
refresh();
|
||||
};
|
||||
|
||||
ElMessageBox.confirm(`该操作会删除 “${e.name}” 部门的所有用户,是否确认?`, "提示", {
|
||||
type: "warning",
|
||||
confirmButtonText: "直接删除",
|
||||
cancelButtonText: "保留用户",
|
||||
distinguishCancelAndClose: true
|
||||
})
|
||||
.then(() => {
|
||||
del(true);
|
||||
})
|
||||
.catch((action: string) => {
|
||||
if (action == "cancel") {
|
||||
del(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 部门排序
|
||||
function treeOrder(f: boolean) {
|
||||
if (f) {
|
||||
ElMessageBox.confirm("部门架构已发生改变,是否保存?", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(async () => {
|
||||
const ids: any[] = [];
|
||||
|
||||
const deep = (list: any[], pid: any) => {
|
||||
list.forEach((e) => {
|
||||
e.parentId = pid;
|
||||
ids.push(e);
|
||||
|
||||
if (e.children && isArray(e.children)) {
|
||||
deep(e.children, e.id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
deep(list.value, null);
|
||||
|
||||
await service.base.sys.department
|
||||
.order(
|
||||
ids.map((e, i) => {
|
||||
return {
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
orderNum: i
|
||||
};
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
ElMessage.success("更新排序成功");
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
|
||||
refresh();
|
||||
isDrag.value = false;
|
||||
})
|
||||
.catch(() => null);
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// 右键菜单
|
||||
function openCM(e: any, d?: any, n?: any) {
|
||||
if (!d) {
|
||||
d = list.value[0] || {};
|
||||
}
|
||||
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "新增",
|
||||
"suffix-icon": "el-icon-plus",
|
||||
hidden:
|
||||
(n && n.level >= props.level) ||
|
||||
!service.base.sys.department._permission.add,
|
||||
callback: (_: any, done: Function) => {
|
||||
rowEdit({
|
||||
name: "",
|
||||
parentName: d.name,
|
||||
parentId: d.id
|
||||
});
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "编辑",
|
||||
"suffix-icon": "el-icon-edit",
|
||||
hidden: !service.base.sys.department._permission.update,
|
||||
callback: (_: any, done: Function) => {
|
||||
rowEdit(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
"suffix-icon": "el-icon-delete",
|
||||
hidden: !d.parentId || !service.base.sys.department._permission.delete,
|
||||
callback: (_: any, done: Function) => {
|
||||
rowDel(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "新增成员",
|
||||
"suffix-icon": "el-icon-user",
|
||||
hidden: !service.base.sys.user._permission.add,
|
||||
callback: (_: any, done: Function) => {
|
||||
emit("user-add", d);
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(function () {
|
||||
refresh();
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
list,
|
||||
loading,
|
||||
isDrag,
|
||||
isMini: !isPc(),
|
||||
setRefs,
|
||||
openCM,
|
||||
allowDrag,
|
||||
allowDrop,
|
||||
refresh,
|
||||
rowClick,
|
||||
rowEdit,
|
||||
rowDel,
|
||||
treeOrder
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
&__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;
|
||||
|
||||
.el-tree-node__content {
|
||||
height: 36px;
|
||||
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,257 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-editor-quill">
|
||||
<div :ref="setRefs('editor')" class="editor" :style="style"></div>
|
||||
|
||||
<cl-upload-space
|
||||
:ref="setRefs('upload-space')"
|
||||
detail-data
|
||||
:show-button="false"
|
||||
@confirm="onUploadSpaceConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, watch } from "vue";
|
||||
import Quill from "quill";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import { isNumber } from "/@/cool/utils";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-editor-quill",
|
||||
|
||||
props: {
|
||||
options: Object,
|
||||
modelValue: null,
|
||||
height: [String, Number],
|
||||
width: [String, Number]
|
||||
},
|
||||
|
||||
emits: ["update:modelValue", "load"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { refs, setRefs }: any = useCool();
|
||||
|
||||
let quill: any = null;
|
||||
|
||||
// 文本内容
|
||||
const content = ref<string>("");
|
||||
|
||||
// 光标位置
|
||||
const cursorIndex = ref<number>(0);
|
||||
|
||||
// 上传处理
|
||||
function uploadFileHandler() {
|
||||
const selection = quill.getSelection();
|
||||
|
||||
if (selection) {
|
||||
cursorIndex.value = selection.index;
|
||||
}
|
||||
|
||||
refs.value["upload-space"].open();
|
||||
}
|
||||
|
||||
// 文件确认
|
||||
function onUploadSpaceConfirm(files: any[]) {
|
||||
if (files.length > 0) {
|
||||
// 批量插入图片
|
||||
files.forEach((file, i) => {
|
||||
const [type] = file.type.split("/");
|
||||
|
||||
quill.insertEmbed(cursorIndex.value + i, type, file.url, Quill.sources.USER);
|
||||
});
|
||||
|
||||
// 移动光标到图片后一位
|
||||
quill.setSelection(cursorIndex.value + files.length);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置内容
|
||||
function setContent(val: string) {
|
||||
quill.root.innerHTML = val || "";
|
||||
}
|
||||
|
||||
// 编辑框样式
|
||||
const style = computed<any>(() => {
|
||||
const height = isNumber(props.height) ? props.height + "px" : props.height;
|
||||
const width = isNumber(props.width) ? props.width + "px" : props.width;
|
||||
|
||||
return {
|
||||
height,
|
||||
width
|
||||
};
|
||||
});
|
||||
|
||||
// 监听绑定值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string) => {
|
||||
if (val) {
|
||||
if (val !== content.value) {
|
||||
setContent(val);
|
||||
}
|
||||
} else {
|
||||
setContent("");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(function () {
|
||||
// 实例化
|
||||
quill = new Quill(refs.value.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"]
|
||||
]
|
||||
},
|
||||
...props.options
|
||||
});
|
||||
|
||||
// 添加图片工具
|
||||
quill.getModule("toolbar").addHandler("image", uploadFileHandler);
|
||||
|
||||
// 监听输入
|
||||
quill.on("text-change", () => {
|
||||
content.value = quill.root.innerHTML;
|
||||
emit("update:modelValue", content.value);
|
||||
});
|
||||
|
||||
// 设置内容
|
||||
setContent(props.modelValue);
|
||||
|
||||
// 加载回调
|
||||
emit("load", quill);
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
content,
|
||||
quill,
|
||||
cursorIndex,
|
||||
style,
|
||||
setRefs,
|
||||
setContent,
|
||||
onUploadSpaceConfirm
|
||||
};
|
||||
}
|
||||
});
|
||||
</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,57 +0,0 @@
|
||||
<template>
|
||||
<svg :class="svgClass" :style="style" aria-hidden="true">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import { isNumber } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "icon-svg",
|
||||
|
||||
cool: {
|
||||
global: true
|
||||
},
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
className: {
|
||||
type: String
|
||||
},
|
||||
size: {
|
||||
type: [String, Number]
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const style = ref<any>({
|
||||
fontSize: isNumber(props.size) ? props.size + "px" : props.size
|
||||
});
|
||||
|
||||
const iconName = computed<string>(() => `#icon-${props.name}`);
|
||||
const svgClass = computed<Array<string>>(() => {
|
||||
return ["icon-svg", `icon-svg__${props.name}`, String(props.className || "")];
|
||||
});
|
||||
|
||||
return {
|
||||
style,
|
||||
iconName,
|
||||
svgClass
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
@ -1,110 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="cl-image"
|
||||
:style="{
|
||||
justifyContent: justify,
|
||||
height: sty.h
|
||||
}"
|
||||
>
|
||||
<el-image
|
||||
:src="urls[0]"
|
||||
:fit="fit"
|
||||
:lazy="lazy"
|
||||
:preview-src-list="urls"
|
||||
:style="{
|
||||
height: sty.h,
|
||||
width: sty.w
|
||||
}"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { isArray, isNumber, isString } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-image",
|
||||
|
||||
props: {
|
||||
modelValue: [String, Array],
|
||||
src: [String, Array],
|
||||
size: {
|
||||
type: [Number, Array],
|
||||
default: 100
|
||||
},
|
||||
lazy: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
fit: {
|
||||
type: String,
|
||||
default: "cover"
|
||||
},
|
||||
justify: {
|
||||
type: String,
|
||||
default: "center"
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const urls = computed(() => {
|
||||
const urls: any = props.modelValue || props.src;
|
||||
|
||||
if (isArray(urls)) {
|
||||
return urls;
|
||||
}
|
||||
|
||||
if (isString(urls)) {
|
||||
return (urls || "").split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const sty = computed(() => {
|
||||
const [h, w]: any = isNumber(props.size) ? [props.size, props.size] : props.size;
|
||||
|
||||
return {
|
||||
h: isNumber(h) ? h + "px" : h,
|
||||
w: isNumber(w) ? w + "px" : w
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
urls,
|
||||
sty
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.el-image {
|
||||
display: block;
|
||||
|
||||
.image-slot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 3px;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<a v-for="item in urls" :key="item" class="cl-link" :href="item" :target="target">
|
||||
<el-icon><icon-link /></el-icon>{{ filename(item) }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from "vue";
|
||||
import { isArray, isString, last } from "/@/cool/utils";
|
||||
import { Link } from "@element-plus/icons";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-link",
|
||||
|
||||
components: {
|
||||
"icon-link": Link
|
||||
},
|
||||
|
||||
props: {
|
||||
modelValue: [String, Array],
|
||||
href: [String, Array],
|
||||
text: {
|
||||
type: String,
|
||||
default: "查看"
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
default: "_blank"
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const urls = computed(() => {
|
||||
const urls: any = props.modelValue || props.href;
|
||||
|
||||
if (isArray(urls)) {
|
||||
return urls;
|
||||
}
|
||||
|
||||
if (isString(urls)) {
|
||||
return (urls || "").split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
function filename(url: string) {
|
||||
return last(url.split("/"));
|
||||
}
|
||||
|
||||
return {
|
||||
urls,
|
||||
filename
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
background-color: $color-primary;
|
||||
color: #fff;
|
||||
padding: 0 5px;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
margin: 2px;
|
||||
|
||||
.el-icon {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-file">
|
||||
<el-select v-model="path" allow-create filterable clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:label="item.value"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
|
||||
// 扫描文件
|
||||
function findFiles() {
|
||||
const files = import.meta.globEager("/**/views/**/*.vue");
|
||||
let list = [];
|
||||
|
||||
for (const i in files) {
|
||||
if (!i.includes("components")) {
|
||||
list.push({
|
||||
value: i.substr(5)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-file",
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
// 路径
|
||||
const path = ref<string>(props.modelValue);
|
||||
|
||||
// 数据列表
|
||||
const list = ref<any[]>(findFiles());
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
path.value = val || "";
|
||||
}
|
||||
);
|
||||
|
||||
watch(path, (val) => {
|
||||
emit("update:modelValue", val);
|
||||
});
|
||||
|
||||
return {
|
||||
path,
|
||||
list
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-menu-file {
|
||||
width: 100%;
|
||||
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__module {
|
||||
display: inline-flex;
|
||||
|
||||
.label {
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-icons__wrap">
|
||||
<el-popover
|
||||
v-model:visible="visible"
|
||||
placement="bottom-start"
|
||||
width="480px"
|
||||
popper-class="cl-menu-icons"
|
||||
>
|
||||
<el-row :gutter="10" class="list scroller1">
|
||||
<el-col v-for="(item, index) in list" :key="index" :span="3" :xs="4">
|
||||
<el-button
|
||||
size="mini"
|
||||
:class="{ 'is-active': item === name }"
|
||||
@click="onChange(item)"
|
||||
>
|
||||
<icon-svg :name="item" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template #reference>
|
||||
<el-input
|
||||
v-model="name"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
@click="open"
|
||||
@input="onChange"
|
||||
/>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { iconList } from "/$/base";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-icons",
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
// 图标列表
|
||||
const list = ref<any[]>(iconList());
|
||||
|
||||
// 已选图标
|
||||
const name = ref<string>(props.modelValue);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
name.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
function open() {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function onChange(val: string) {
|
||||
emit("update:modelValue", val);
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
list,
|
||||
visible,
|
||||
open,
|
||||
close,
|
||||
onChange
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
.
|
||||
<style lang="scss">
|
||||
.cl-menu-icons {
|
||||
max-width: 90%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
margin-bottom: 10px;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
|
||||
.icon-svg {
|
||||
font-size: 18px;
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,124 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-perms">
|
||||
<el-cascader
|
||||
v-model="value"
|
||||
separator=":"
|
||||
clearable
|
||||
filterable
|
||||
:options="options"
|
||||
:props="{ multiple: true }"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-perms",
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { service } = useCool();
|
||||
|
||||
// 绑定值
|
||||
const value = ref<any[]>([]);
|
||||
|
||||
// 权限列表
|
||||
const options = ref<any[]>([]);
|
||||
|
||||
// 监听改变
|
||||
function onChange(row: any) {
|
||||
emit("update:modelValue", row.map((e: any) => e.join(":")).join(","));
|
||||
}
|
||||
|
||||
// 解析权限
|
||||
(function parsePerm() {
|
||||
const list: any[] = [];
|
||||
let perms: any[] = [];
|
||||
|
||||
const flat = (obj: any) => {
|
||||
for (const i in obj) {
|
||||
const { permission } = obj[i];
|
||||
|
||||
if (permission) {
|
||||
perms = [...perms, Object.values(permission)].flat();
|
||||
} else {
|
||||
flat(obj[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flat(service);
|
||||
|
||||
perms
|
||||
.filter((e) => e.includes(":"))
|
||||
.map((e) => e.split(":"))
|
||||
.forEach((arr) => {
|
||||
const col = (i: number, d: any[]) => {
|
||||
const key = arr[i];
|
||||
|
||||
if (d) {
|
||||
const index = d.findIndex((e: any) => e.label == key);
|
||||
|
||||
if (index >= 0) {
|
||||
col(i + 1, d[index].children);
|
||||
} else {
|
||||
const 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, list);
|
||||
});
|
||||
|
||||
options.value = list;
|
||||
})();
|
||||
|
||||
// 监听值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string) => {
|
||||
value.value = val ? val.split(",").map((e: string) => e.split(":")) : [];
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
value,
|
||||
options,
|
||||
onChange
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-menu-perms {
|
||||
.el-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,243 +0,0 @@
|
||||
<template>
|
||||
<el-button type="success" size="mini" @click="create">快速创建</el-button>
|
||||
|
||||
<cl-form :ref="setRefs('form')" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { last, isEmpty } from "/@/cool/utils";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-quick",
|
||||
|
||||
emits: ["success"],
|
||||
|
||||
setup(_, { emit }) {
|
||||
const { service, refs, setRefs } = useCool();
|
||||
|
||||
// 快速创建
|
||||
async function create() {
|
||||
// 模块列表
|
||||
const modules = await service.request({
|
||||
url: "/__cool_modules"
|
||||
});
|
||||
|
||||
// 数据结构列表
|
||||
const eps: any[] = await service.base.common.eps();
|
||||
const entities: any[] = [];
|
||||
|
||||
for (const i in eps) {
|
||||
eps[i].forEach((e: any) => {
|
||||
if (!isEmpty(e.columns)) {
|
||||
entities.push({
|
||||
label: `${e.name}(${e.prefix})`,
|
||||
value: entities.length,
|
||||
filename: last(e.prefix.split("/")),
|
||||
...e
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 打开
|
||||
refs.value.form.open({
|
||||
title: "快速创建",
|
||||
width: "900px",
|
||||
items: [
|
||||
{
|
||||
prop: "module",
|
||||
label: {
|
||||
text: "模块名称",
|
||||
tip: "菜单文件存放在所选模块的 views 目录下",
|
||||
icon: "el-icon-question"
|
||||
},
|
||||
span: 9,
|
||||
component: {
|
||||
name: "el-select",
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true
|
||||
},
|
||||
options: modules.map((e: string) => {
|
||||
return {
|
||||
label: e,
|
||||
value: e
|
||||
};
|
||||
})
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
prop: "entity",
|
||||
label: {
|
||||
text: "数据结构",
|
||||
tip: "所选实体会通过规则配置自动转换",
|
||||
icon: "el-icon-question"
|
||||
},
|
||||
span: 15,
|
||||
component: {
|
||||
name: "el-select",
|
||||
props: {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
onChange(i: number) {
|
||||
refs.value.form.setForm(
|
||||
"router",
|
||||
"/" +
|
||||
(refs.value.form.getForm("module") || "test") +
|
||||
"/" +
|
||||
entities[i].filename
|
||||
);
|
||||
}
|
||||
},
|
||||
options: entities
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "菜单名称",
|
||||
span: 9,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入菜单名称"
|
||||
}
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
prop: "router",
|
||||
label: "菜单路由",
|
||||
span: 15,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入菜单路由,如:/test"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "parentId",
|
||||
label: "上级节点",
|
||||
component: {
|
||||
name: "cl-menu-tree"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "keepAlive",
|
||||
value: true,
|
||||
label: "路由缓存",
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "开启",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "关闭",
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "icon",
|
||||
label: "菜单图标",
|
||||
component: {
|
||||
name: "cl-menu-icons"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "orderNum",
|
||||
label: "排序号",
|
||||
component: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
placeholder: "请填写排序号",
|
||||
min: 0,
|
||||
max: 99,
|
||||
"controls-position": "right"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
on: {
|
||||
submit(data: any, { done, close }: any) {
|
||||
// 选择的数据结构
|
||||
const item = entities[data.entity];
|
||||
|
||||
// 插入菜单
|
||||
service.base.sys.menu
|
||||
.add({
|
||||
type: 1,
|
||||
isShow: true,
|
||||
viewPath: `cool/modules/${data.module}/views/${item.filename}.vue`,
|
||||
...data
|
||||
})
|
||||
.then((res: any) => {
|
||||
// 权限列表
|
||||
const perms: any[] = [];
|
||||
|
||||
item.api.forEach((e: any) => {
|
||||
const d: any = {
|
||||
type: 2,
|
||||
parentId: res.id,
|
||||
name: e.summary || e.path,
|
||||
perms: [e.path]
|
||||
};
|
||||
|
||||
if (e.path == "/update") {
|
||||
if (item.api.find((a: any) => a.path == "/info")) {
|
||||
d.perms.push("/info");
|
||||
}
|
||||
}
|
||||
|
||||
d.perms = d.perms
|
||||
.map((e: string) =>
|
||||
(item.prefix.replace("/admin/", "") + e).replace(
|
||||
/\//g,
|
||||
":"
|
||||
)
|
||||
)
|
||||
.join(",");
|
||||
|
||||
perms.push(d);
|
||||
});
|
||||
|
||||
// 批量插入权限
|
||||
service.base.sys.menu.add(perms).then(() => {
|
||||
emit("success");
|
||||
close();
|
||||
|
||||
service.request({
|
||||
url: "/__cool_createMenu",
|
||||
method: "POST",
|
||||
data: {
|
||||
...item,
|
||||
...data
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
setRefs,
|
||||
create
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,90 +0,0 @@
|
||||
.cl-slider-menu {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: 0;
|
||||
background-color: transparent;
|
||||
|
||||
.el-sub-menu__title,
|
||||
&-item {
|
||||
&.is-active,
|
||||
&:hover {
|
||||
background-color: $color-primary !important;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu__title,
|
||||
&-item,
|
||||
&__title {
|
||||
color: #eee;
|
||||
letter-spacing: 0.5px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
|
||||
.icon-svg {
|
||||
font-size: 16px;
|
||||
margin: 0 15px 0 5px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&--collapse {
|
||||
.el-sub-menu__title {
|
||||
.icon-svg {
|
||||
margin-left: 2px;
|
||||
font-size: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__popup {
|
||||
.icon-svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-slider-menu__submenu {
|
||||
background-color: #fff;
|
||||
|
||||
&.el-menu {
|
||||
&--vertical {
|
||||
.el-sub-menu {
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-svg {
|
||||
font-size: 18px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-svg {
|
||||
font-size: 18px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
import { computed, defineComponent, h, ref, watch } from "vue";
|
||||
import "./index.scss";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-slider",
|
||||
|
||||
setup() {
|
||||
const { router, route, store } = useCool();
|
||||
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(true);
|
||||
// 菜单列表
|
||||
const menuList = computed(() => store.getters.menuList);
|
||||
// 菜单是否折叠
|
||||
const menuCollapse = computed(() => store.getters.menuCollapse);
|
||||
// 浏览器信息
|
||||
const browser: any = computed(() => store.getters.browser);
|
||||
|
||||
// 页面跳转
|
||||
function toView(url: string) {
|
||||
if (url != route.path) {
|
||||
router.push(url);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新菜单
|
||||
function refresh() {
|
||||
visible.value = false;
|
||||
|
||||
setTimeout(() => {
|
||||
visible.value = true;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 监听菜单变化
|
||||
watch(menuList, refresh);
|
||||
|
||||
return {
|
||||
route,
|
||||
visible,
|
||||
menuList,
|
||||
menuCollapse,
|
||||
browser,
|
||||
toView,
|
||||
refresh
|
||||
};
|
||||
},
|
||||
|
||||
render(ctx: any) {
|
||||
function deepMenu(list: any, index: number) {
|
||||
return list
|
||||
.filter((e: any) => e.isShow)
|
||||
.map((e: any) => {
|
||||
let html = null;
|
||||
|
||||
if (e.type == 0) {
|
||||
html = h(
|
||||
<el-sub-menu></el-sub-menu>,
|
||||
{
|
||||
index: String(e.id),
|
||||
key: e.id,
|
||||
"popper-class": "cl-slider-menu__popup"
|
||||
},
|
||||
{
|
||||
title: () => {
|
||||
return ctx.menuCollapse && index == 1 ? (
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
) : (
|
||||
<span>
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
<span>{e.name}</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
default() {
|
||||
return deepMenu(e.children, index + 1);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
html = h(
|
||||
<el-menu-item></el-menu-item>,
|
||||
{
|
||||
index: e.path,
|
||||
key: e.id
|
||||
},
|
||||
{
|
||||
title() {
|
||||
return <span>{e.name}</span>;
|
||||
},
|
||||
default() {
|
||||
return <icon-svg name={e.icon}></icon-svg>;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return html;
|
||||
});
|
||||
}
|
||||
|
||||
const children = deepMenu(ctx.menuList, 1);
|
||||
|
||||
return (
|
||||
ctx.visible && (
|
||||
<div class="cl-slider-menu">
|
||||
<el-menu
|
||||
default-active={ctx.route.path}
|
||||
background-color="transparent"
|
||||
collapse-transition={false}
|
||||
collapse={ctx.browser.isMini ? false : ctx.menuCollapse}
|
||||
onSelect={ctx.toView}
|
||||
>
|
||||
{children}
|
||||
</el-menu>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
@ -1,111 +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" :key="index" :index="`${index}`">
|
||||
<icon-svg v-if="item.icon" :name="item.icon" :size="18" />
|
||||
<span>{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref } from "vue";
|
||||
import { firstMenu } from "../../utils";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-topbar",
|
||||
|
||||
setup() {
|
||||
const { store, router, route } = useCool();
|
||||
|
||||
// 选中标识
|
||||
const index = ref<string>("0");
|
||||
|
||||
// 菜单列表
|
||||
const list = computed(() => store.getters.menuGroup.filter((e: any) => e.isShow));
|
||||
|
||||
// 选择导航
|
||||
function onSelect(index: number) {
|
||||
store.commit("SET_MENU_LIST", index);
|
||||
|
||||
// 获取第一个菜单地址
|
||||
const url = firstMenu(list.value[index].children);
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
onMounted(function () {
|
||||
// 设置默认
|
||||
function deep(e: any, i: number) {
|
||||
switch (e.type) {
|
||||
case 0:
|
||||
e.children.forEach((e: any) => {
|
||||
deep(e, i);
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
if (route.path.includes(e.path)) {
|
||||
index.value = String(i);
|
||||
store.commit("SET_MENU_LIST", i);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list.value.forEach((e: any, i: number) => {
|
||||
deep(e, i);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
index,
|
||||
list,
|
||||
onSelect
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-menu-topbar {
|
||||
margin-right: 10px;
|
||||
|
||||
.el-menu {
|
||||
height: 50px;
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
.el-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
border-bottom: 0 !important;
|
||||
padding: 0 20px;
|
||||
background: transparent;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
margin-left: 3px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.icon-svg {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-menu-tree__wrap">
|
||||
<el-popover
|
||||
v-model:visible="visible"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
width="500px"
|
||||
popper-class="cl-menu-tree"
|
||||
>
|
||||
<el-input v-model="keyword" size="small">
|
||||
<template #prefix>
|
||||
<i class="el-input__icon el-icon-search"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<div class="cl-menu-tree__scroller scroller1">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="menuId"
|
||||
:data="treeList"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:default-expanded-keys="expandedKeys"
|
||||
:filter-node-method="filterNode"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #reference>
|
||||
<el-input v-model="name" readonly placeholder="请选择" @click="visible = true" />
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, watch } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { deepTree } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-tree",
|
||||
|
||||
props: {
|
||||
modelValue: [Number, String]
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
// 请求服务
|
||||
const { service } = useCool();
|
||||
|
||||
// 关键字
|
||||
const keyword = ref<string>("");
|
||||
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 展开值
|
||||
const expandedKeys = ref<any[]>([]);
|
||||
|
||||
// el-tree 组件
|
||||
const treeRef = ref<any>({});
|
||||
|
||||
// 绑定值回调
|
||||
function onCurrentChange({ id }: any) {
|
||||
emit("update:modelValue", id);
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function refresh() {
|
||||
service.base.sys.menu.list().then((res: any) => {
|
||||
const _list = res.filter((e: any) => e.type != 2);
|
||||
|
||||
_list.unshift({
|
||||
name: "一级菜单",
|
||||
id: null
|
||||
});
|
||||
|
||||
list.value = _list;
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤节点
|
||||
function filterNode(value: string, data: any) {
|
||||
if (!value) return true;
|
||||
return data.name.indexOf(value) !== -1;
|
||||
}
|
||||
|
||||
// 节点名称
|
||||
const name = computed(() => {
|
||||
const item = list.value.find((e) => e.id == props.modelValue);
|
||||
return item ? item.name : "一级菜单";
|
||||
});
|
||||
|
||||
// 树形列表
|
||||
const treeList = computed(() => deepTree(list.value));
|
||||
|
||||
// 监听关键字过滤
|
||||
watch(keyword, (val: string) => {
|
||||
treeRef.value.filter(val);
|
||||
});
|
||||
|
||||
onMounted(function () {
|
||||
refresh();
|
||||
});
|
||||
|
||||
return {
|
||||
visible,
|
||||
keyword,
|
||||
list,
|
||||
expandedKeys,
|
||||
treeRef,
|
||||
name,
|
||||
treeList,
|
||||
refresh,
|
||||
filterNode,
|
||||
onCurrentChange
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-menu-tree {
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-input {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__scroller {
|
||||
max-height: 400px;
|
||||
overflow: hidden auto;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,255 +0,0 @@
|
||||
<template>
|
||||
<div class="app-process">
|
||||
<div class="app-process__left hidden-xs-only" @click="toScroll(true)">
|
||||
<i class="el-icon-arrow-left"></i>
|
||||
</div>
|
||||
|
||||
<div :ref="setRefs('scroller')" class="app-process__scroller">
|
||||
<div
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:ref="setRefs(`item-${index}`)"
|
||||
class="app-process__item"
|
||||
:class="{ active: item.active }"
|
||||
:data-index="index"
|
||||
@click="onTap(item, Number(index))"
|
||||
@contextmenu.stop.prevent="openCM($event, item)"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
<i
|
||||
v-if="index > 0"
|
||||
class="el-icon-close"
|
||||
@mousedown.stop="onDel(Number(index))"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-process__right hidden-xs-only" @click="toScroll(false)">
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import { last } from "/@/cool/utils";
|
||||
import { useCool } from "/@/cool";
|
||||
import { ContextMenu } from "@cool-vue/crud";
|
||||
|
||||
export default {
|
||||
name: "cl-process",
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, store, route, router }: any = useCool();
|
||||
|
||||
// 参数配置
|
||||
const menu = reactive<any>({
|
||||
current: {}
|
||||
});
|
||||
|
||||
// 数据列表
|
||||
const list = computed(() => store.getters.processList);
|
||||
|
||||
// 跳转
|
||||
function toPath() {
|
||||
const active = list.value.find((e: any) => e.active);
|
||||
|
||||
if (!active) {
|
||||
const next = last(list.value);
|
||||
router.push(next ? next.value : "/");
|
||||
}
|
||||
}
|
||||
|
||||
// 移动到
|
||||
function scrollTo(left: number) {
|
||||
refs.value.scroller.scrollTo({
|
||||
left,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
|
||||
// 左右移动
|
||||
function toScroll(f: boolean) {
|
||||
scrollTo(refs.value.scroller.scrollLeft + (f ? -100 : 100));
|
||||
}
|
||||
|
||||
// 调整滚动位置
|
||||
function adScroll(index: number) {
|
||||
const el = refs.value[`item-${index}`];
|
||||
|
||||
if (el) {
|
||||
scrollTo(el.offsetLeft + el.clientWidth - refs.value.scroller.clientWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// 选择
|
||||
function onTap(item: any, index: number) {
|
||||
adScroll(index);
|
||||
router.push(item.value);
|
||||
}
|
||||
|
||||
// 删除
|
||||
function onDel(index: number) {
|
||||
store.commit("DEL_PROCESS", index);
|
||||
toPath();
|
||||
}
|
||||
|
||||
// 右键菜单
|
||||
function openCM(e: any, item: any) {
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "关闭当前",
|
||||
hidden: item.value !== route.path,
|
||||
callback: (_: any, done: Function) => {
|
||||
onDel(list.value.findIndex((e: any) => e.value == item.value));
|
||||
done();
|
||||
toPath();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "关闭其他",
|
||||
callback: (_: any, done: Function) => {
|
||||
store.commit(
|
||||
"SET_PROCESS",
|
||||
list.value.filter(
|
||||
(e: any) => e.value == item.value || e.value == "/"
|
||||
)
|
||||
);
|
||||
done();
|
||||
toPath();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "关闭所有",
|
||||
callback: (_: any, done: Function) => {
|
||||
store.commit(
|
||||
"SET_PROCESS",
|
||||
list.value.filter((e: any) => e.value == "/")
|
||||
);
|
||||
done();
|
||||
toPath();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
function (val) {
|
||||
adScroll(list.value.findIndex((e: any) => e.value === val) || 0);
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
refs,
|
||||
setRefs,
|
||||
menu,
|
||||
list,
|
||||
onTap,
|
||||
onDel,
|
||||
toPath,
|
||||
toScroll,
|
||||
adScroll,
|
||||
scrollTo,
|
||||
openCM
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-process {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
|
||||
&__left,
|
||||
&__right {
|
||||
background-color: #fff;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 2px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
&__left {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&__right {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&__scroller {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
background-color: #fff;
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.el-icon-close {
|
||||
width: 14px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
span {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
i {
|
||||
width: auto;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
background-color: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,151 +0,0 @@
|
||||
<template>
|
||||
<div v-loading="loading" class="cl-role-perms">
|
||||
<p v-if="title">{{ title }}</p>
|
||||
|
||||
<el-input v-model="keyword" placeholder="输入关键字进行过滤" size="small" />
|
||||
|
||||
<div class="scroller">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
highlight-current
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
:data="list"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
@check-change="save"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { deepTree } from "/@/cool/utils";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-role-perms",
|
||||
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
title: String
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { service } = useCool();
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 已选列表
|
||||
const checked = ref<any[]>([]);
|
||||
|
||||
// 搜索关键字
|
||||
const keyword = ref<string>("");
|
||||
|
||||
// 加载中
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// el-tree 组件
|
||||
const treeRef = ref<any>({});
|
||||
|
||||
// 刷新树
|
||||
function refreshTree(val: any[]) {
|
||||
if (!val) {
|
||||
checked.value = [];
|
||||
}
|
||||
|
||||
const ids: any[] = [];
|
||||
|
||||
// 处理半选状态
|
||||
const fn = (list: any[]) => {
|
||||
list.forEach((e) => {
|
||||
if (e.children) {
|
||||
fn(e.children);
|
||||
} else {
|
||||
ids.push(Number(e.id));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fn(list.value);
|
||||
|
||||
checked.value = ids.filter((id) => (val || []).includes(id));
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function refresh() {
|
||||
service.base.sys.menu
|
||||
.list()
|
||||
.then((res: any[]) => {
|
||||
list.value = deepTree(res);
|
||||
refreshTree(props.modelValue);
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤节点
|
||||
function filterNode(val: string, data: any) {
|
||||
if (!val) return true;
|
||||
return data.name.includes(val);
|
||||
}
|
||||
|
||||
// 保存
|
||||
function save() {
|
||||
// 选中的节点
|
||||
const checked = treeRef.value.getCheckedKeys();
|
||||
// 半选中的节点
|
||||
const halfChecked = treeRef.value.getHalfCheckedKeys();
|
||||
|
||||
emit("update:modelValue", [...checked, ...halfChecked]);
|
||||
}
|
||||
|
||||
// 过滤监听
|
||||
watch(keyword, (val: string) => {
|
||||
treeRef.value.filter(val);
|
||||
});
|
||||
|
||||
// 刷新监听
|
||||
watch(() => props.modelValue, refreshTree);
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
|
||||
return {
|
||||
list,
|
||||
checked,
|
||||
keyword,
|
||||
loading,
|
||||
treeRef,
|
||||
filterNode,
|
||||
save
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scroller {
|
||||
border: 1px solid #dcdfe6;
|
||||
margin-top: 5px;
|
||||
border-radius: 3px;
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<el-select v-model="value" v-bind="props" multiple @change="onChange">
|
||||
<el-option v-for="(item, index) in list" :key="index" :value="item.id" :label="item.name" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, watch } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { isArray } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-role-select",
|
||||
|
||||
props: {
|
||||
modelValue: [String, Number, Array],
|
||||
props: Object
|
||||
},
|
||||
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
// 请求服务
|
||||
const { service } = useCool();
|
||||
|
||||
// 数据列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 绑定值
|
||||
const value = ref<any>();
|
||||
|
||||
// 绑定值回调
|
||||
function onChange(val: any) {
|
||||
emit("update:modelValue", val);
|
||||
}
|
||||
|
||||
// 解析值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: any) => {
|
||||
value.value = (isArray(val) ? val : [val]).filter(Boolean);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
list.value = await service.base.sys.role.list();
|
||||
});
|
||||
|
||||
return {
|
||||
list,
|
||||
value,
|
||||
onChange
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,109 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-route-nav">
|
||||
<p v-if="browser.isMini" class="title">
|
||||
{{ lastName }}
|
||||
</p>
|
||||
|
||||
<template v-else>
|
||||
<el-breadcrumb>
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(item, index) in list" :key="index">{{
|
||||
(item.meta && item.meta.label) || item.name
|
||||
}}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch } from "vue";
|
||||
import _ from "lodash";
|
||||
import { isEmpty } from "/@/cool/utils";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-route-nav",
|
||||
|
||||
setup() {
|
||||
const { store, route } = useCool();
|
||||
|
||||
// 数据列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 监听路由变化
|
||||
watch(
|
||||
() => route,
|
||||
(val: any) => {
|
||||
const deep = (item: any) => {
|
||||
if (route.path === "/") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.path == route.path) {
|
||||
return item;
|
||||
} else {
|
||||
if (item.children) {
|
||||
const ret = item.children.map(deep).find(Boolean);
|
||||
|
||||
if (ret) {
|
||||
return [item, ret];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
list.value = _(store.getters.menuGroup)
|
||||
.map(deep)
|
||||
.filter(Boolean)
|
||||
.flattenDeep()
|
||||
.value();
|
||||
|
||||
if (isEmpty(list.value)) {
|
||||
list.value.push(val);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// 最后一个节点名称
|
||||
const lastName = computed(() => _.last(list.value).name);
|
||||
|
||||
const browser = computed(() => store.getters.browser);
|
||||
|
||||
return {
|
||||
list,
|
||||
lastName,
|
||||
browser
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-route-nav {
|
||||
white-space: nowrap;
|
||||
|
||||
.el-breadcrumb {
|
||||
margin: 0 10px;
|
||||
|
||||
&__inner {
|
||||
font-size: 13px;
|
||||
padding: 0 10px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,58 +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 lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { getBrowser } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { plat } = getBrowser();
|
||||
|
||||
const width = computed(() => {
|
||||
return `calc(100% - ${plat == "iphone" ? "10px" : "0px"})`;
|
||||
});
|
||||
|
||||
return {
|
||||
width
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-switch">
|
||||
<el-switch
|
||||
:ref="setRefs('switch')"
|
||||
:model-value="status"
|
||||
:disabled="disabled"
|
||||
:loading="loading"
|
||||
:width="width"
|
||||
:inline-prompt="inlinePrompt"
|
||||
:active-icon="activeIcon"
|
||||
:inactive-icon="inactiveIcon"
|
||||
:active-text="activeText"
|
||||
:inactive-text="inactiveText"
|
||||
:active-value="activeValue"
|
||||
:inactive-value="inactiveValue"
|
||||
:active-color="activeColor"
|
||||
:inactive-color="inactiveColor"
|
||||
:border-color="borderColor"
|
||||
:string="string"
|
||||
:validate-event="validateEvent"
|
||||
:before-change="beforeChange"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, inject, ref, watch } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-switch",
|
||||
|
||||
props: {
|
||||
scope: null,
|
||||
column: null,
|
||||
modelValue: [Boolean, String, Number],
|
||||
disabled: Boolean,
|
||||
loading: Boolean,
|
||||
width: Number,
|
||||
inlinePrompt: Boolean,
|
||||
activeIcon: String,
|
||||
inactiveIcon: String,
|
||||
activeText: String,
|
||||
inactiveText: String,
|
||||
activeValue: {
|
||||
type: [Boolean, String, Number],
|
||||
default: 1
|
||||
},
|
||||
inactiveValue: {
|
||||
type: [Boolean, String, Number],
|
||||
default: 0
|
||||
},
|
||||
activeColor: String,
|
||||
inactiveColor: String,
|
||||
borderColor: String,
|
||||
string: String,
|
||||
validateEvent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
beforeChange: Function
|
||||
},
|
||||
|
||||
emits: ["update:modelValue", "change"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { refs, setRefs } = useCool();
|
||||
const crud = inject<any>("crud");
|
||||
|
||||
// 状态
|
||||
const status = ref<any>(props.modelValue);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: any) => {
|
||||
status.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
function focus() {
|
||||
refs.value.switch.focus();
|
||||
}
|
||||
|
||||
function onChange(val: boolean | string | number) {
|
||||
crud.service
|
||||
.update({
|
||||
...props.scope,
|
||||
[props.column.property]: val
|
||||
})
|
||||
.then(() => {
|
||||
emit("update:modelValue", val);
|
||||
emit("change", val);
|
||||
status.value = val;
|
||||
ElMessage.success("更新成功");
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
setRefs,
|
||||
focus,
|
||||
onChange,
|
||||
status
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,43 +0,0 @@
|
||||
import store from "/@/store";
|
||||
|
||||
function parse(value: any) {
|
||||
const permission = store.getters.permission;
|
||||
|
||||
if (typeof value == "string") {
|
||||
return value ? permission.some((e: any) => e.includes(value.replace(/\s/g, ""))) : false;
|
||||
} else {
|
||||
return Boolean(value);
|
||||
}
|
||||
}
|
||||
|
||||
function checkPerm(value: any) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(value) === "[object Object]") {
|
||||
if (value.or) {
|
||||
return value.or.some(parse);
|
||||
}
|
||||
|
||||
if (value.and) {
|
||||
return value.and.some((e: any) => !parse(e)) ? false : true;
|
||||
}
|
||||
}
|
||||
|
||||
return parse(value);
|
||||
}
|
||||
|
||||
function change(el: any, binding: any) {
|
||||
el.style.display = checkPerm(binding.value) ? el.getAttribute("_display") : "none";
|
||||
}
|
||||
|
||||
export default {
|
||||
beforeMount(el: any, binding: any) {
|
||||
el.setAttribute("_display", el.style.display || "");
|
||||
change(el, binding);
|
||||
},
|
||||
updated: change
|
||||
};
|
||||
|
||||
export { checkPerm };
|
@ -1,5 +0,0 @@
|
||||
import { checkPerm } from "./directives/permission";
|
||||
import { iconList } from "./common";
|
||||
import "./static/css/index.scss";
|
||||
|
||||
export { iconList, checkPerm };
|
@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="403" desc="您无权访问此页面" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page.vue";
|
||||
|
||||
export default {
|
||||
cool: {
|
||||
route: {
|
||||
path: "/403"
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="404" desc="找不到您要查找的页面" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page.vue";
|
||||
|
||||
export default {
|
||||
cool: {
|
||||
route: {
|
||||
path: "/404"
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="500" desc="糟糕,出了点问题" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page.vue";
|
||||
|
||||
export default {
|
||||
cool: {
|
||||
route: {
|
||||
path: "/500"
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<error-page :code="502" desc="马上回来" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from "./components/error-page.vue";
|
||||
|
||||
export default {
|
||||
cool: {
|
||||
route: {
|
||||
path: "/502"
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,168 +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 v-model="url" size="medium" filterable prefix-icon="el-icon-search">
|
||||
<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>
|
||||
|
||||
<ul class="link">
|
||||
<li @click="home">回到首页</li>
|
||||
<li @click="back">返回上一页</li>
|
||||
<li @click="reLogin">重新登录</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="router">
|
||||
<el-button round @click="toLogin">返回登录页</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="copyright">Copyright © cool-admin-next 2021</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import { href } from "/@/cool/utils";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
code: Number,
|
||||
desc: String
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { store, router } = useCool();
|
||||
|
||||
const url = ref<string>("");
|
||||
const isLogout = ref<boolean>(false);
|
||||
|
||||
const routes = computed(() => store.getters.routes);
|
||||
const token = computed(() => store.getters.token);
|
||||
|
||||
function navTo() {
|
||||
router.push(url.value);
|
||||
}
|
||||
|
||||
function toLogin() {
|
||||
router.push("/login");
|
||||
}
|
||||
|
||||
async function reLogin() {
|
||||
isLogout.value = true;
|
||||
await store.dispatch("userLogout");
|
||||
href("/login");
|
||||
}
|
||||
|
||||
function back() {
|
||||
history.back();
|
||||
}
|
||||
|
||||
function home() {
|
||||
router.push("/");
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
isLogout,
|
||||
routes,
|
||||
token,
|
||||
navTo,
|
||||
toLogin,
|
||||
reLogin,
|
||||
back,
|
||||
home
|
||||
};
|
||||
}
|
||||
});
|
||||
</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 {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
|
||||
li {
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 0 20px;
|
||||
list-style: none;
|
||||
|
||||
&:hover {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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 v-loading="loading" class="page-iframe" 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,70 +0,0 @@
|
||||
<template>
|
||||
<div class="login-captcha" @click="refresh">
|
||||
<div v-if="svg" class="svg" v-html="svg"></div>
|
||||
<img v-else class="base64" :src="base64" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
emits: ["update:modelValue", "change"],
|
||||
|
||||
setup(_, { emit }) {
|
||||
const { service } = useCool();
|
||||
const base64 = ref("");
|
||||
const svg = ref("");
|
||||
|
||||
const refresh = () => {
|
||||
service.base.open
|
||||
.captcha({
|
||||
height: 36,
|
||||
width: 110
|
||||
})
|
||||
.then(({ captchaId, data }: any) => {
|
||||
if (data.includes(";base64,")) {
|
||||
base64.value = data;
|
||||
} else {
|
||||
svg.value = data;
|
||||
}
|
||||
|
||||
emit("update:modelValue", captchaId);
|
||||
emit("change", {
|
||||
base64,
|
||||
svg,
|
||||
captchaId
|
||||
});
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
refresh();
|
||||
|
||||
return {
|
||||
base64,
|
||||
svg,
|
||||
refresh
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-captcha {
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
|
||||
.svg {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.base64 {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,224 +0,0 @@
|
||||
<template>
|
||||
<div class="page-login">
|
||||
<div class="box">
|
||||
<img class="logo" src="../../static/images/logo.png" alt="" />
|
||||
<p class="desc">{{ app.name }}是一款快速开发后台权限管理系统</p>
|
||||
|
||||
<el-form label-position="top" class="form" size="medium" :disabled="saving">
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请输入用户名"
|
||||
maxlength="20"
|
||||
auto-complete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
maxlength="20"
|
||||
auto-complete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="验证码" class="captcha">
|
||||
<el-input
|
||||
v-model="form.verifyCode"
|
||||
placeholder="请输入图片验证码"
|
||||
maxlength="4"
|
||||
auto-complete="off"
|
||||
@keyup.enter="toLogin"
|
||||
/>
|
||||
|
||||
<captcha
|
||||
:ref="setRefs('captcha')"
|
||||
v-model="form.captchaId"
|
||||
class="value"
|
||||
@change="
|
||||
() => {
|
||||
form.verifyCode = '';
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-button round size="small" class="submit-btn" :loading="saving" @click="toLogin"
|
||||
>登录</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Captcha from "./components/captcha.vue";
|
||||
import { useEps, useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
cool: {
|
||||
route: {
|
||||
path: "/login"
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
Captcha
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, store, router, app }: any = useCool();
|
||||
|
||||
const saving = ref<boolean>(false);
|
||||
|
||||
// 登录表单数据
|
||||
const form = reactive({
|
||||
username: "",
|
||||
password: "",
|
||||
captchaId: "",
|
||||
verifyCode: ""
|
||||
});
|
||||
|
||||
// 登录
|
||||
async function toLogin() {
|
||||
if (!form.username) {
|
||||
return ElMessage.warning("用户名不能为空");
|
||||
}
|
||||
|
||||
if (!form.password) {
|
||||
return ElMessage.warning("密码不能为空");
|
||||
}
|
||||
|
||||
if (!form.verifyCode) {
|
||||
return ElMessage.warning("图片验证码不能为空");
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
// 登录
|
||||
await store.dispatch("userLogin", form);
|
||||
|
||||
// 用户信息
|
||||
await store.dispatch("userInfo");
|
||||
|
||||
// 读取Eps
|
||||
await useEps();
|
||||
|
||||
// 权限菜单
|
||||
const [first] = await store.dispatch("permMenu");
|
||||
|
||||
if (!first) {
|
||||
ElMessage.error("该账号没有权限");
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err);
|
||||
refs.value.captcha.refresh();
|
||||
}
|
||||
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
setRefs,
|
||||
form,
|
||||
saving,
|
||||
toLogin,
|
||||
app
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-login {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
position: relative;
|
||||
background-color: #2f3447;
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
position: absolute;
|
||||
left: calc(50% - 250px);
|
||||
top: calc(50% - 250px);
|
||||
|
||||
.logo {
|
||||
height: 50px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
margin-bottom: 60px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.el-form {
|
||||
width: 300px;
|
||||
border-radius: 3px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__label {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input {
|
||||
.el-input__inner {
|
||||
border: 0;
|
||||
border-bottom: 0.5px solid #999;
|
||||
border-radius: 0;
|
||||
padding: 0 5px;
|
||||
background-color: transparent;
|
||||
color: #ccc;
|
||||
transition: border-color 0.3s;
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
border-color: #fff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:-webkit-autofill {
|
||||
-webkit-text-fill-color: #fff !important;
|
||||
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
|
||||
transition: background-color 50000s ease-in-out 0s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.captcha {
|
||||
position: relative;
|
||||
|
||||
.value {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 40px;
|
||||
padding: 9px 40px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,89 +0,0 @@
|
||||
import { BaseService, Service } from "/@/cool";
|
||||
|
||||
@Service("base/comm")
|
||||
class Common extends BaseService {
|
||||
/**
|
||||
* 文件上传模式
|
||||
*/
|
||||
uploadMode() {
|
||||
return this.request({
|
||||
url: "/uploadMode"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传,如果模式是 cloud,返回对应参数
|
||||
*
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
upload(params: any) {
|
||||
return this.request({
|
||||
url: "/upload",
|
||||
method: "POST",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户退出
|
||||
*/
|
||||
userLogout() {
|
||||
return this.request({
|
||||
url: "/logout",
|
||||
method: "POST"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
userInfo() {
|
||||
return this.request({
|
||||
url: "/person"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息修改
|
||||
*
|
||||
* @param {*} params
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
userUpdate(params: any) {
|
||||
return this.request({
|
||||
url: "/personUpdate",
|
||||
method: "POST",
|
||||
data: {
|
||||
...params
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限信息
|
||||
*
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
permMenu() {
|
||||
return this.request({
|
||||
url: "/permmenu"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据接口
|
||||
*/
|
||||
eps() {
|
||||
return this.request({
|
||||
url: "/eps"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Common;
|
@ -1,56 +0,0 @@
|
||||
import { BaseService, Service } from "/@/cool";
|
||||
|
||||
@Service("base/open")
|
||||
class Open extends BaseService {
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param {*} { username, password, captchaId, verifyCode }
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
userLogin({ username, password, captchaId, verifyCode }: any) {
|
||||
return this.request({
|
||||
url: "/login",
|
||||
method: "POST",
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
captchaId,
|
||||
verifyCode
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片验证码 svg
|
||||
*
|
||||
* @param {*} { height, width }
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
captcha({ height, width }: any) {
|
||||
return this.request({
|
||||
url: "/captcha",
|
||||
params: {
|
||||
height,
|
||||
width
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 token
|
||||
* @param {string} token
|
||||
*/
|
||||
refreshToken(token: string) {
|
||||
return this.request({
|
||||
url: "/refreshToken",
|
||||
params: {
|
||||
refreshToken: token
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Open;
|
@ -1 +0,0 @@
|
||||
@import "./theme.scss";
|
@ -1,50 +0,0 @@
|
||||
// customize style
|
||||
.scroller1 {
|
||||
overflow: hidden auto;
|
||||
position: relative;
|
||||
z-index: 9;
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
// Element-ui theme
|
||||
.el-input-number {
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
border: 0 !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.el-message {
|
||||
&.el-message--success,
|
||||
&.el-message--error,
|
||||
&.el-message--info,
|
||||
&.el-message--warning {
|
||||
min-width: auto;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
border: 0;
|
||||
padding: 12px 20px 12px 15px;
|
||||
|
||||
.el-message__icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.el-message__content {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB |
@ -1,78 +0,0 @@
|
||||
import store from "store";
|
||||
import { deepMerge, getBrowser } from "/@/cool/utils";
|
||||
import { app } from "/@/config/env";
|
||||
import { useEps } from "/@/cool";
|
||||
|
||||
const browser = getBrowser();
|
||||
|
||||
const state = {
|
||||
info: {
|
||||
...app
|
||||
},
|
||||
browser,
|
||||
collapse: browser.isMini ? true : false,
|
||||
loading: false
|
||||
};
|
||||
|
||||
const getters = {
|
||||
// 程序加载
|
||||
appLoading: (state: any) => state.loading,
|
||||
// 应用配置
|
||||
app: (state: any) => state.info,
|
||||
// 浏览器信息
|
||||
browser: (state: any) => state.browser,
|
||||
// 左侧菜单是否收起
|
||||
menuCollapse: (state: any) => state.collapse
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async appLoad({ getters, dispatch, commit }: any) {
|
||||
if (getters.token) {
|
||||
commit("SHOW_LOADING");
|
||||
|
||||
// 读取Eps
|
||||
await useEps();
|
||||
|
||||
// 读取菜单权限
|
||||
await dispatch("permMenu");
|
||||
|
||||
// 获取用户信息
|
||||
dispatch("userInfo");
|
||||
|
||||
commit("HIDE_LOADING");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SHOW_LOADING(state: any) {
|
||||
state.loading = true;
|
||||
},
|
||||
|
||||
HIDE_LOADING(state: any) {
|
||||
state.loading = false;
|
||||
},
|
||||
|
||||
// 设置浏览器信息
|
||||
SET_BROWSER(state: any) {
|
||||
state.browser = getBrowser();
|
||||
},
|
||||
|
||||
// 收起左侧菜单
|
||||
COLLAPSE_MENU(state: any, val = false) {
|
||||
state.collapse = val;
|
||||
},
|
||||
|
||||
// 更新应用配置
|
||||
UPDATE_APP(state: any, val: any) {
|
||||
deepMerge(state.info, val);
|
||||
store.set("__app__", state.info);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
};
|
@ -1,154 +0,0 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
import storage from "store";
|
||||
import store from "/@/store";
|
||||
import router from "/@/router";
|
||||
import { deepTree, revDeepTree, isArray, isEmpty } from "/@/cool/utils";
|
||||
import { menuList } from "/@/config/env";
|
||||
import { revisePath } from "../utils";
|
||||
import { MenuItem } from "../types";
|
||||
import { usePermission } from "/@/cool";
|
||||
|
||||
const state = {
|
||||
// 视图路由,type=1
|
||||
routes: storage.get("viewRoutes") || [],
|
||||
// 树形菜单
|
||||
group: storage.get("menuGroup") || [],
|
||||
// showAMenu 模式下,顶级菜单的序号
|
||||
index: 0,
|
||||
// 左侧菜单
|
||||
menu: [],
|
||||
// 权限列表
|
||||
permission: storage.get("permission") || []
|
||||
};
|
||||
|
||||
const getters = {
|
||||
// 树形菜单列表
|
||||
menuGroup: (state: any) => state.group,
|
||||
// 左侧菜单
|
||||
menuList: (state: any) => state.menu,
|
||||
// 视图路由
|
||||
routes: (state: any) => state.routes,
|
||||
// 权限列表
|
||||
permission: (state: any) => state.permission
|
||||
};
|
||||
|
||||
const actions = {
|
||||
// 设置菜单、权限
|
||||
permMenu({ commit, state, getters }: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const next = (res: any) => {
|
||||
if (!isArray(res.menus)) {
|
||||
res.menus = [];
|
||||
}
|
||||
|
||||
if (!isArray(res.perms)) {
|
||||
res.perms = [];
|
||||
}
|
||||
|
||||
const routes = res.menus
|
||||
.filter((e: MenuItem) => e.type != 2)
|
||||
.map((e: MenuItem) => {
|
||||
return {
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
path: revisePath(e.router || String(e.id)),
|
||||
viewPath: e.viewPath,
|
||||
type: e.type,
|
||||
name: e.name,
|
||||
icon: e.icon,
|
||||
orderNum: e.orderNum,
|
||||
isShow: isEmpty(e.isShow) ? true : e.isShow,
|
||||
meta: {
|
||||
label: e.name,
|
||||
keepAlive: e.keepAlive
|
||||
},
|
||||
children: []
|
||||
};
|
||||
});
|
||||
|
||||
// 转成树形菜单
|
||||
const menuGroup = deepTree(routes);
|
||||
|
||||
// 设置权限
|
||||
commit("SET_PERMIESSION", res.perms);
|
||||
// 设置菜单组
|
||||
commit("SET_MENU_GROUP", menuGroup);
|
||||
// 设置视图路由
|
||||
commit(
|
||||
"SET_VIEW_ROUTES",
|
||||
routes.filter((e: MenuItem) => e.type == 1)
|
||||
);
|
||||
// 设置菜单
|
||||
commit("SET_MENU_LIST", state.index);
|
||||
|
||||
resolve(menuGroup);
|
||||
};
|
||||
|
||||
// 监测自定义菜单
|
||||
if (!getters.app.conf.customMenu) {
|
||||
store.service.base.common
|
||||
.permMenu()
|
||||
.then((res: any) => {
|
||||
next(res);
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error("菜单加载异常");
|
||||
console.error(err);
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
next({
|
||||
menus: revDeepTree(menuList)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
// 设置树形菜单列表
|
||||
SET_MENU_GROUP(state: any, list: MenuItem[]) {
|
||||
state.group = list;
|
||||
storage.set("menuGroup", list);
|
||||
},
|
||||
|
||||
// 设置视图路由
|
||||
SET_VIEW_ROUTES(state: any, list: MenuItem[]) {
|
||||
router.$plugin?.addViews(list);
|
||||
|
||||
state.routes = list;
|
||||
storage.set("viewRoutes", list);
|
||||
},
|
||||
|
||||
// 设置左侧菜单
|
||||
SET_MENU_LIST(state: any, index: number) {
|
||||
const { showAMenu } = store.getters.app.conf;
|
||||
|
||||
if (isEmpty(index)) {
|
||||
index = state.index;
|
||||
}
|
||||
|
||||
if (showAMenu) {
|
||||
const { children = [] } = state.group[index] || {};
|
||||
|
||||
state.index = index;
|
||||
state.menu = children;
|
||||
} else {
|
||||
state.menu = state.group;
|
||||
}
|
||||
},
|
||||
|
||||
// 设置权限
|
||||
SET_PERMIESSION(state: any, list: Array<any>) {
|
||||
state.permission = list;
|
||||
storage.set("permission", list);
|
||||
usePermission(list);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
const state = {
|
||||
info: {},
|
||||
list: []
|
||||
};
|
||||
|
||||
const getters = {
|
||||
// 模块信息
|
||||
modules: (state: any) => state.info,
|
||||
// 模块列表
|
||||
moduleList: (state: any) => state.list
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SET_MODULE(state: any, list: Array<any>) {
|
||||
const d: any = {};
|
||||
|
||||
list.forEach((e: any) => {
|
||||
d[e.name] = e;
|
||||
});
|
||||
|
||||
state.list = list;
|
||||
state.info = d;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
mutations
|
||||
};
|
@ -1,66 +0,0 @@
|
||||
const fMenu = {
|
||||
label: "首页",
|
||||
value: "/",
|
||||
active: true
|
||||
};
|
||||
|
||||
const state = {
|
||||
list: [fMenu]
|
||||
};
|
||||
|
||||
const getters = {
|
||||
// 页面进程列表
|
||||
processList: (state: any) => state.list
|
||||
};
|
||||
|
||||
const actions = {};
|
||||
|
||||
const mutations = {
|
||||
ADD_PROCESS(state: any, item: any) {
|
||||
const index = state.list.findIndex(
|
||||
(e: any) => e.value.split("?")[0] === item.value.split("?")[0]
|
||||
);
|
||||
|
||||
state.list.map((e: any) => {
|
||||
e.active = e.value == item.value;
|
||||
});
|
||||
|
||||
if (index < 0) {
|
||||
if (item.value == "/") {
|
||||
item.label = fMenu.label;
|
||||
}
|
||||
|
||||
if (item.label) {
|
||||
state.list.push({
|
||||
...item,
|
||||
active: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
state.list[index].active = true;
|
||||
state.list[index].label = item.label;
|
||||
state.list[index].value = item.value;
|
||||
}
|
||||
},
|
||||
|
||||
DEL_PROCESS(state: any, index: number) {
|
||||
if (index != 0) {
|
||||
state.list.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
SET_PROCESS(state: any, list: Array<any>) {
|
||||
state.list = list;
|
||||
},
|
||||
|
||||
RESET_PROCESS(state: any) {
|
||||
state.list = [fMenu];
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
};
|
@ -1,104 +0,0 @@
|
||||
import { storage, href } from "/@/cool/utils";
|
||||
import store from "/@/store";
|
||||
import { Token } from "../types";
|
||||
|
||||
const state: any = {
|
||||
// 授权标识
|
||||
token: storage.get("token") || null,
|
||||
// 用户信息
|
||||
info: storage.get("userInfo") || {}
|
||||
};
|
||||
|
||||
const getters = {
|
||||
userInfo: (state: any) => state.info,
|
||||
token: (state: any) => state.token
|
||||
};
|
||||
|
||||
const actions = {
|
||||
// 用户登录
|
||||
userLogin({ commit }: any, form: any): Promise<any> {
|
||||
return store.service.base.open.userLogin(form).then((res: Token) => {
|
||||
commit("SET_TOKEN", res);
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
// 用户退出
|
||||
async userLogout({ dispatch }: any): Promise<any> {
|
||||
await store.service.base.common.userLogout();
|
||||
return dispatch("userRemove");
|
||||
},
|
||||
|
||||
// 用户信息
|
||||
userInfo({ commit }: any): Promise<any> {
|
||||
return store.service.base.common.userInfo().then((res: any) => {
|
||||
commit("SET_USERINFO", res);
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
// 用户移除
|
||||
userRemove({ commit }: any) {
|
||||
commit("CLEAR_USER");
|
||||
commit("CLEAR_TOKEN");
|
||||
commit("RESET_PROCESS");
|
||||
commit("SET_MENU_GROUP", []);
|
||||
commit("SET_VIEW_ROUTES", []);
|
||||
commit("SET_MENU_LIST", 0);
|
||||
},
|
||||
|
||||
// 刷新token
|
||||
refreshToken({ commit, dispatch }: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
store.service.base.open
|
||||
.refreshToken(storage.get("refreshToken"))
|
||||
.then((res: any) => {
|
||||
commit("SET_TOKEN", res);
|
||||
resolve(res.token);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
dispatch("userRemove");
|
||||
href("/login");
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
// 设置用户信息
|
||||
SET_USERINFO(state: any, val: any) {
|
||||
state.info = val;
|
||||
storage.set("userInfo", val);
|
||||
},
|
||||
|
||||
// 设置授权标识
|
||||
SET_TOKEN(state: any, { token, expire, refreshToken, refreshExpire }: Token) {
|
||||
// 请求的唯一标识
|
||||
state.token = token;
|
||||
storage.set("token", token, expire);
|
||||
|
||||
// 刷新 token 的唯一标识
|
||||
storage.set("refreshToken", refreshToken, refreshExpire);
|
||||
},
|
||||
|
||||
// 移除授权标识
|
||||
CLEAR_TOKEN(state: any) {
|
||||
state.token = null;
|
||||
storage.remove("token");
|
||||
storage.remove("refreshToken");
|
||||
},
|
||||
|
||||
// 移除用户信息
|
||||
CLEAR_USER(state: any) {
|
||||
state.info = {};
|
||||
storage.remove("userInfo");
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
};
|
31
src/cool/modules/base/types/index.d.ts
vendored
31
src/cool/modules/base/types/index.d.ts
vendored
@ -1,31 +0,0 @@
|
||||
export declare interface Token {
|
||||
expire: number;
|
||||
refreshExpire: number;
|
||||
refreshToken: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export declare enum MenuType {
|
||||
"目录" = 0,
|
||||
"菜单" = 1,
|
||||
"权限" = 2
|
||||
}
|
||||
|
||||
export declare interface MenuItem {
|
||||
id: number;
|
||||
parentId: number;
|
||||
path: string;
|
||||
router?: string;
|
||||
viewPath?: string;
|
||||
type: MenuType;
|
||||
name: string;
|
||||
icon: string;
|
||||
orderNum: number;
|
||||
isShow: number;
|
||||
keepAlive?: number;
|
||||
meta?: {
|
||||
label: string;
|
||||
keepAlive: number;
|
||||
};
|
||||
children?: MenuItem[];
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
export const revisePath = (path: string) => {
|
||||
if (!path) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (path[0] == "/") {
|
||||
return path;
|
||||
} else {
|
||||
return `/${path}`;
|
||||
}
|
||||
};
|
||||
|
||||
export function firstMenu(list: Array<any>) {
|
||||
let path = "";
|
||||
|
||||
const fn = (arr: Array<any>) => {
|
||||
arr.forEach((e: any) => {
|
||||
if (e.type == 1) {
|
||||
if (!path) {
|
||||
path = e.path;
|
||||
}
|
||||
} else {
|
||||
fn(e.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fn(list);
|
||||
|
||||
return path || "/404";
|
||||
}
|
||||
|
||||
export function createLink(url: string, id?: string) {
|
||||
const link = document.createElement("link");
|
||||
link.href = url;
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
if (id) {
|
||||
link.id = id;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementsByTagName("head").item(0)?.appendChild(link);
|
||||
}, 0);
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<div class="page-my-info">
|
||||
<div class="title">基本信息</div>
|
||||
|
||||
<el-form size="small" label-width="100px" :model="form" :disabled="saving">
|
||||
<el-form-item label="头像">
|
||||
<cl-upload v-model="form.headImg" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="form.nickName" placeholder="请填写昵称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="">
|
||||
<el-button type="primary" :disabled="saving" @click="save">保存修改</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, reactive, ref } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { cloneDeep } from "/@/cool/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-info",
|
||||
|
||||
cool: {
|
||||
route: {
|
||||
path: "/my/info",
|
||||
meta: {
|
||||
label: "个人中心"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { store, service } = useCool();
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<any>(cloneDeep(store.getters.userInfo));
|
||||
|
||||
// 保存状态
|
||||
const saving = ref<boolean>(false);
|
||||
|
||||
// 保存
|
||||
async function save() {
|
||||
const { headImg, nickName, password } = form;
|
||||
|
||||
saving.value = true;
|
||||
|
||||
await service.base.common
|
||||
.userUpdate({
|
||||
headImg,
|
||||
nickName,
|
||||
password
|
||||
})
|
||||
.then(() => {
|
||||
form.password = "";
|
||||
ElMessage.success("修改成功");
|
||||
store.dispatch("userInfo");
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
saving,
|
||||
save
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-my-info {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-form {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #000;
|
||||
margin-bottom: 30px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,161 +0,0 @@
|
||||
<template>
|
||||
<cl-crud :ref="setRefs('crud')" @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn />
|
||||
|
||||
<el-button
|
||||
v-permission="service.base.sys.log.permission.clear"
|
||||
size="mini"
|
||||
type="danger"
|
||||
@click="clear"
|
||||
>
|
||||
清空
|
||||
</el-button>
|
||||
|
||||
<cl-filter label="日志保存天数">
|
||||
<el-input-number
|
||||
v-model="day"
|
||||
controls-position="right"
|
||||
size="mini"
|
||||
:max="10000"
|
||||
:min="1"
|
||||
@blur="saveDay"
|
||||
/>
|
||||
</cl-filter>
|
||||
|
||||
<cl-flex1 />
|
||||
<cl-search-key placeholder="请输入请求地址, 参数,ip地址" />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table v-bind="table" />
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useCool } from "/@/cool";
|
||||
import { CrudLoad, Table } from "@cool-vue/crud/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-log",
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, service }: any = useCool();
|
||||
|
||||
// 天数
|
||||
const day = ref<number>(1);
|
||||
|
||||
// cl-table 配置
|
||||
const table = reactive<Table>({
|
||||
"context-menu": ["refresh"],
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
order: "descending"
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: "index",
|
||||
label: "#",
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
prop: "userId",
|
||||
label: "用户ID"
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "昵称",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "action",
|
||||
label: "请求地址",
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: "params",
|
||||
label: "参数",
|
||||
align: "center",
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: "ip",
|
||||
label: "ip",
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
prop: "ipAddr",
|
||||
label: "ip地址",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "createTime",
|
||||
label: "创建时间",
|
||||
minWidth: 150,
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service(service.base.sys.log).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
// 保存天数
|
||||
function saveDay() {
|
||||
service.base.sys.log.setKeep({ value: day.value }).then(() => {
|
||||
ElMessage.success("保存成功");
|
||||
});
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
function clear() {
|
||||
ElMessageBox.confirm("是否要清空日志", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
service.base.sys.log
|
||||
.clear()
|
||||
.then(() => {
|
||||
ElMessage.success("清空成功");
|
||||
refs.value.crud.refresh();
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
// 获取天数
|
||||
service.base.sys.log.getKeep().then((res: number) => {
|
||||
day.value = Number(res);
|
||||
});
|
||||
|
||||
return {
|
||||
service,
|
||||
refs,
|
||||
day,
|
||||
table,
|
||||
setRefs,
|
||||
onLoad,
|
||||
saveDay,
|
||||
clear
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,398 +0,0 @@
|
||||
<template>
|
||||
<cl-crud :ref="setRefs('crud')" :on-refresh="onRefresh" @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn />
|
||||
<cl-add-btn />
|
||||
<cl-menu-quick @success="refresh()" v-if="isDev" />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table :ref="setRefs('table')" v-bind="table" @row-click="onRowClick">
|
||||
<!-- 名称 -->
|
||||
<template #column-name="{ scope }">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
<el-tag
|
||||
v-if="!scope.row.isShow"
|
||||
size="mini"
|
||||
effect="dark"
|
||||
type="danger"
|
||||
style="margin-left: 10px"
|
||||
>隐藏</el-tag
|
||||
>
|
||||
</template>
|
||||
|
||||
<!-- 图标 -->
|
||||
<template #column-icon="{ scope }">
|
||||
<icon-svg :name="scope.row.icon" size="16px" style="margin-top: 5px" />
|
||||
</template>
|
||||
|
||||
<!-- 权限 -->
|
||||
<template #column-perms="{ scope }">
|
||||
<el-tag
|
||||
v-for="(item, index) in scope.row.permList"
|
||||
:key="index"
|
||||
size="mini"
|
||||
effect="dark"
|
||||
style="margin: 2px; letter-spacing: 0.5px"
|
||||
>{{ item }}</el-tag
|
||||
>
|
||||
</template>
|
||||
|
||||
<!-- 路由 -->
|
||||
<template #column-router="{ scope }">
|
||||
<el-link v-if="scope.row.type == 1" type="primary" :href="scope.row.router">{{
|
||||
scope.row.router
|
||||
}}</el-link>
|
||||
<span v-else>{{ scope.row.router }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 路由缓存 -->
|
||||
<template #column-keepAlive="{ scope }">
|
||||
<template v-if="scope.row.type == 1">
|
||||
<i v-if="scope.row.keepAlive" class="el-icon-check"></i>
|
||||
<i v-else class="el-icon-close"></i>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 行新增 -->
|
||||
<template #slot-add="{ scope }">
|
||||
<el-button
|
||||
v-if="scope.row.type != 2"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="upsertAppend(scope.row)"
|
||||
>新增</el-button
|
||||
>
|
||||
</template>
|
||||
</cl-table>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<cl-pagination :props="{ layout: 'total' }" />
|
||||
</el-row>
|
||||
|
||||
<!-- 编辑 -->
|
||||
<cl-upsert v-bind="upsert" />
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useCool } from "/@/cool";
|
||||
import { deepTree } from "/@/cool/utils";
|
||||
import { defineComponent, reactive } from "vue";
|
||||
import { CrudLoad, Table, Upsert, RefreshOp } from "@cool-vue/crud/types";
|
||||
import { isDev } from "/@/config/env";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-menu",
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, service, router } = useCool();
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service(service.base.sys.menu).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
// 刷新监听
|
||||
function onRefresh(_: any, { render }: RefreshOp) {
|
||||
service.base.sys.menu.list().then((list: any[]) => {
|
||||
list.map((e) => {
|
||||
e.permList = e.perms ? e.perms.split(",") : [];
|
||||
});
|
||||
|
||||
render(deepTree(list), {
|
||||
total: list.length
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 行点击展开
|
||||
function onRowClick(row: any, column: any) {
|
||||
if (column.property && row.children) {
|
||||
refs.value.table.toggleRowExpansion(row);
|
||||
}
|
||||
}
|
||||
|
||||
// 子集新增
|
||||
function upsertAppend({ type, id }: any) {
|
||||
refs.value.crud.rowAppend({
|
||||
parentId: id,
|
||||
type: type + 1
|
||||
});
|
||||
}
|
||||
|
||||
// 设置权限
|
||||
function setPermission({ id }: any) {
|
||||
refs.value.crud.rowAppend({
|
||||
parentId: id,
|
||||
type: 2
|
||||
});
|
||||
}
|
||||
|
||||
// 跳转
|
||||
function toUrl(url: string) {
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
// 刷新
|
||||
function refresh() {
|
||||
refs.value.crud.refresh();
|
||||
}
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"row-key": "id"
|
||||
},
|
||||
"context-menu": [
|
||||
(row: any) => {
|
||||
return {
|
||||
label: "新增",
|
||||
hidden: row.type == 2,
|
||||
callback: (_: any, done: Function) => {
|
||||
upsertAppend(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
},
|
||||
"update",
|
||||
"delete",
|
||||
(row: any) => {
|
||||
return {
|
||||
label: "权限",
|
||||
hidden: row.type != 1,
|
||||
callback: (_: any, done: Function) => {
|
||||
setPermission(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
align: "left",
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
prop: "icon",
|
||||
label: "图标",
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: "type",
|
||||
label: "类型",
|
||||
width: 100,
|
||||
dict: [
|
||||
{
|
||||
label: "目录",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: "菜单",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "权限",
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
prop: "router",
|
||||
label: "节点路由",
|
||||
minWidth: 160
|
||||
},
|
||||
{
|
||||
prop: "keepAlive",
|
||||
label: "路由缓存",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "viewPath",
|
||||
label: "文件路径",
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: "perms",
|
||||
label: "权限",
|
||||
headerAlign: "center",
|
||||
minWidth: 300
|
||||
},
|
||||
{
|
||||
prop: "orderNum",
|
||||
label: "排序号",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
prop: "updateTime",
|
||||
label: "更新时间",
|
||||
sortable: "custom",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
type: "op",
|
||||
buttons: ["slot-add", "edit", "delete"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 新增、编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
dialog: {
|
||||
width: "800px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
prop: "type",
|
||||
value: 0,
|
||||
label: "节点类型",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "目录",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: "菜单",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "权限",
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "节点名称",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入节点名称"
|
||||
}
|
||||
},
|
||||
required: true
|
||||
},
|
||||
{
|
||||
prop: "parentId",
|
||||
label: "上级节点",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "cl-menu-tree"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "router",
|
||||
label: "节点路由",
|
||||
span: 24,
|
||||
hidden: ({ scope }: any) => scope.type != 1,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入节点路由,如:/test"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "keepAlive",
|
||||
value: true,
|
||||
label: "路由缓存",
|
||||
span: 24,
|
||||
hidden: ({ scope }: any) => scope.type != 1,
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "开启",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "关闭",
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "isShow",
|
||||
label: "是否显示",
|
||||
span: 24,
|
||||
value: true,
|
||||
hidden: ({ scope }: any) => scope.type == 2,
|
||||
flex: false,
|
||||
component: {
|
||||
name: "el-switch"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "viewPath",
|
||||
label: "文件路径",
|
||||
span: 24,
|
||||
hidden: ({ scope }: any) => scope.type != 1,
|
||||
component: {
|
||||
name: "cl-menu-file"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "icon",
|
||||
label: "节点图标",
|
||||
span: 24,
|
||||
hidden: ({ scope }: any) => scope.type == 2,
|
||||
component: {
|
||||
name: "cl-menu-icons"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "orderNum",
|
||||
label: "排序号",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
placeholder: "请填写排序号",
|
||||
min: 0,
|
||||
max: 99,
|
||||
"controls-position": "right"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "perms",
|
||||
label: "权限",
|
||||
span: 24,
|
||||
hidden: ({ scope }: any) => scope.type != 2,
|
||||
component: {
|
||||
name: "cl-menu-perms"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
table,
|
||||
upsert,
|
||||
setRefs,
|
||||
onLoad,
|
||||
onRefresh,
|
||||
onRowClick,
|
||||
upsertAppend,
|
||||
setPermission,
|
||||
toUrl,
|
||||
refresh,
|
||||
isDev
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,218 +0,0 @@
|
||||
<template>
|
||||
<cl-crud @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn />
|
||||
<cl-add-btn />
|
||||
<cl-multi-delete-btn />
|
||||
<cl-flex1 />
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table v-bind="table" />
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
|
||||
<cl-upsert :ref="setRefs('upsert')" v-bind="upsert" @opened="onUpsertOpen">
|
||||
<template #slot-content="{ scope }">
|
||||
<div v-for="(item, index) in tab.list" :key="index" class="editor">
|
||||
<template v-if="tab.index == index">
|
||||
<el-button class="change-btn" size="mini" @click="changeTab(item.to)">{{
|
||||
item.label
|
||||
}}</el-button>
|
||||
|
||||
<component :is="item.component" v-model="scope.data" height="300px" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</cl-upsert>
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { defineComponent, nextTick, reactive } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { CrudLoad, Table, Upsert } from "@cool-vue/crud/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-param",
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, service } = useCool();
|
||||
|
||||
// 选项卡
|
||||
const tab = reactive<any>({
|
||||
index: null,
|
||||
|
||||
list: [
|
||||
{
|
||||
label: "切换富文本编辑器",
|
||||
to: 1,
|
||||
component: "cl-codemirror"
|
||||
},
|
||||
{
|
||||
label: "切换代码编辑器",
|
||||
to: 0,
|
||||
component: "cl-editor-quill"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
columns: [
|
||||
{
|
||||
type: "selection",
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
label: "名称",
|
||||
prop: "name",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "keyName",
|
||||
prop: "keyName",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "数据",
|
||||
prop: "data",
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
label: "备注",
|
||||
prop: "remark",
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
type: "op"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 新增编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
dialog: {
|
||||
width: "1000px"
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入名称"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "名称不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "keyName",
|
||||
label: "keyName",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入Key"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "Key不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "data",
|
||||
label: "数据",
|
||||
component: {
|
||||
name: "slot-content"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请输入备注",
|
||||
rows: 3,
|
||||
type: "textarea"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service(service.base.sys.param).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
// 切换编辑器
|
||||
function changeTab(i: number) {
|
||||
ElMessageBox.confirm("切换编辑器会清空输入内容,是否继续?", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
tab.index = i;
|
||||
refs.value.upsert.setForm("data", "");
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
// 监听打开
|
||||
function onUpsertOpen(isEdit: boolean, data: any) {
|
||||
tab.index = null;
|
||||
|
||||
nextTick(() => {
|
||||
if (isEdit) {
|
||||
tab.index = /<*>/g.test(data.data) ? 1 : 0;
|
||||
} else {
|
||||
tab.index = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
tab,
|
||||
table,
|
||||
upsert,
|
||||
setRefs,
|
||||
onLoad,
|
||||
changeTab,
|
||||
onUpsertOpen
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.change-btn {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.editor {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
</style>
|
@ -1,263 +0,0 @@
|
||||
<template>
|
||||
<div class="view-plugin">
|
||||
<cl-crud ref="crud" :on-refresh="onRefresh" @load="onLoad">
|
||||
<el-row type="flex" align="middle">
|
||||
<!-- 刷新按钮 -->
|
||||
<cl-refresh-btn />
|
||||
<cl-flex1 />
|
||||
<!-- 关键字搜索 -->
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<!-- 数据表格 -->
|
||||
<cl-table v-bind="table">
|
||||
<template #column-enable="{ scope }">
|
||||
<el-switch
|
||||
v-model="scope.row._enable"
|
||||
size="mini"
|
||||
:disabled="!perms.enable"
|
||||
@change="onEnableChange($event, scope.row)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 配置按钮 -->
|
||||
<template #slot-conf="{ scope }">
|
||||
<el-button
|
||||
v-if="scope.row.view && perms.edit"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="openConf(scope.row)"
|
||||
>配置</el-button
|
||||
>
|
||||
</template>
|
||||
</cl-table>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<!-- 分页控件 -->
|
||||
<cl-pagination layout="total" />
|
||||
</el-row>
|
||||
</cl-crud>
|
||||
|
||||
<!-- 表单 -->
|
||||
<cl-form :ref="setRefs('form')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, reactive } from "vue";
|
||||
import { checkPerm } from "/$/base";
|
||||
import { useCool } from "/@/cool";
|
||||
import { CrudLoad, RefreshOp, Table } from "@cool-vue/crud/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "plugin",
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, service } = useCool();
|
||||
|
||||
// 编辑权限
|
||||
const { config, getConfig, enable } = service.base.plugin.info.permission;
|
||||
|
||||
const perms = reactive<any>({
|
||||
edit: checkPerm({
|
||||
and: [config, getConfig]
|
||||
}),
|
||||
enable: checkPerm(enable)
|
||||
});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service(service.base.plugin.info)
|
||||
.set("dict", {
|
||||
api: {
|
||||
page: "list"
|
||||
}
|
||||
})
|
||||
.done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
// 刷新钩子
|
||||
function onRefresh(params: any, { next, render }: RefreshOp) {
|
||||
next(params).then((res: any) => {
|
||||
const list = res.map((e: any) => {
|
||||
e._enable = e.enable ? true : false;
|
||||
return e;
|
||||
});
|
||||
|
||||
render(list, {
|
||||
total: res.length
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 开启、关闭
|
||||
function onEnableChange(val: boolean, item: any) {
|
||||
service.base.plugin.info
|
||||
.enable({
|
||||
namespace: item.namespace,
|
||||
enable: val
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success(val ? "开启成功" : "关闭成功");
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
// 打开配置
|
||||
async function openConf({ name, namespace, view }: any) {
|
||||
const form = await service.base.plugin.info.getConfig({
|
||||
namespace
|
||||
});
|
||||
|
||||
let items = [];
|
||||
|
||||
try {
|
||||
items = JSON.parse(view);
|
||||
} catch (e) {
|
||||
items = [];
|
||||
}
|
||||
|
||||
refs.value.form.open({
|
||||
title: `${name}配置`,
|
||||
items,
|
||||
form,
|
||||
on: {
|
||||
submit: (data: any, { close, done }: any) => {
|
||||
service.base.plugin.info
|
||||
.config({
|
||||
namespace,
|
||||
config: data
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success("保存成功");
|
||||
close();
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
order: "descending"
|
||||
}
|
||||
},
|
||||
"context-menu": [
|
||||
"refresh",
|
||||
(scope: any) => {
|
||||
return {
|
||||
label: "配置",
|
||||
hidden: !perms.edit || !scope.view,
|
||||
callback: (_: any, done: Function) => {
|
||||
openConf(scope);
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
label: "名称",
|
||||
prop: "name",
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
label: "作者",
|
||||
prop: "author",
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
label: "联系方式",
|
||||
prop: "contact",
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: "功能描述",
|
||||
prop: "description",
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "版本号",
|
||||
prop: "version",
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
label: "是否启用",
|
||||
prop: "enable",
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
label: "命名空间",
|
||||
prop: "namespace",
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
width: 150,
|
||||
dict: [
|
||||
{
|
||||
label: "缺少配置",
|
||||
value: 0,
|
||||
type: "warning"
|
||||
},
|
||||
{
|
||||
label: "可用",
|
||||
value: 1,
|
||||
type: "success"
|
||||
},
|
||||
{
|
||||
label: "配置错误",
|
||||
value: 2,
|
||||
type: "danger"
|
||||
},
|
||||
{
|
||||
label: "未知错误",
|
||||
value: 3,
|
||||
type: "danger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
prop: "createTime",
|
||||
width: 150,
|
||||
sortable: "custom"
|
||||
},
|
||||
{
|
||||
type: "op",
|
||||
width: 120,
|
||||
buttons: ["slot-conf"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
perms,
|
||||
table,
|
||||
setRefs,
|
||||
onLoad,
|
||||
onRefresh,
|
||||
onEnableChange,
|
||||
openConf
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,171 +0,0 @@
|
||||
<template>
|
||||
<cl-crud @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn />
|
||||
<cl-add-btn />
|
||||
<cl-multi-delete-btn />
|
||||
<cl-flex1 />
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table v-bind="table" />
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
|
||||
<cl-upsert v-model="form" v-bind="upsert" />
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { CrudLoad, Table, Upsert } from "@cool-vue/crud/types";
|
||||
import { defineComponent, reactive } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-role",
|
||||
|
||||
setup() {
|
||||
const { service } = useCool();
|
||||
|
||||
// 表单值
|
||||
const form = reactive<any>({
|
||||
relevance: 1
|
||||
});
|
||||
|
||||
// 新增、编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
dialog: {
|
||||
width: "800px"
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写名称"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "名称不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "label",
|
||||
label: "标识",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写标识"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "标识不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写备注",
|
||||
type: "textarea",
|
||||
rows: 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "功能权限",
|
||||
prop: "menuIdList",
|
||||
value: [],
|
||||
component: {
|
||||
name: "cl-role-perms"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "数据权限",
|
||||
prop: "departmentIdList",
|
||||
value: [],
|
||||
component: {
|
||||
name: "cl-dept-check"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
order: "descending"
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: "selection",
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "label",
|
||||
label: "标识",
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "createTime",
|
||||
label: "创建时间",
|
||||
sortable: "custom",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "updateTime",
|
||||
label: "更新时间",
|
||||
sortable: "custom",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
type: "op"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service(service.base.sys.role).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
upsert,
|
||||
table,
|
||||
onLoad
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,576 +0,0 @@
|
||||
<template>
|
||||
<div class="system-user">
|
||||
<div class="pane">
|
||||
<!-- 组织架构 -->
|
||||
<div class="dept" :class="[isExpand ? '_expand' : '_collapse']">
|
||||
<cl-dept-tree
|
||||
@row-click="onDeptRowClick"
|
||||
@user-add="onDeptUserAdd"
|
||||
@list-change="onDeptListChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 成员列表 -->
|
||||
<div class="user">
|
||||
<div class="header">
|
||||
<div class="icon" @click="deptExpand">
|
||||
<i class="el-icon-arrow-left" v-if="isExpand"></i>
|
||||
<i class="el-icon-arrow-right" v-else></i>
|
||||
</div>
|
||||
|
||||
<span>成员列表</span>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<cl-crud :ref="setRefs('crud')" :on-refresh="onRefresh" @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn />
|
||||
<cl-add-btn />
|
||||
<cl-multi-delete-btn />
|
||||
<el-button
|
||||
v-permission="service.base.sys.user.permission.move"
|
||||
size="mini"
|
||||
type="success"
|
||||
:disabled="selects.ids.length == 0"
|
||||
@click="toMove()"
|
||||
>转移</el-button
|
||||
>
|
||||
<cl-flex1 />
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table
|
||||
:ref="setRefs('table')"
|
||||
v-bind="table"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<template #column-headImg="{ scope }">
|
||||
<cl-avatar
|
||||
shape="square"
|
||||
size="medium"
|
||||
:src="scope.row.headImg"
|
||||
:style="{ margin: 'auto' }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 权限 -->
|
||||
<template #column-roleName="{ scope }">
|
||||
<el-tag
|
||||
v-for="(item, index) in scope.row.roleNameList"
|
||||
:key="index"
|
||||
disable-transitions
|
||||
size="small"
|
||||
effect="dark"
|
||||
style="margin: 2px"
|
||||
>{{ item }}</el-tag
|
||||
>
|
||||
</template>
|
||||
|
||||
<!-- 单个转移 -->
|
||||
<template #slot-move-btn="{ scope }">
|
||||
<el-button
|
||||
v-permission="service.base.sys.user.permission.move"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="toMove(scope.row)"
|
||||
>转移</el-button
|
||||
>
|
||||
</template>
|
||||
</cl-table>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
|
||||
<cl-upsert
|
||||
:ref="setRefs('upsert')"
|
||||
v-bind="upsert"
|
||||
:on-submit="onUpsertSubmit"
|
||||
/>
|
||||
</cl-crud>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 部门移动 -->
|
||||
<cl-dept-move :ref="setRefs('dept-move')" @success="refresh({ page: 1 })" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, watch } from "vue";
|
||||
import { useCool } from "/@/cool";
|
||||
import { Table, Upsert } from "@cool-vue/crud/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-user",
|
||||
|
||||
setup() {
|
||||
const { refs, setRefs, store, service } = useCool();
|
||||
|
||||
// 是否展开
|
||||
const isExpand = ref<boolean>(true);
|
||||
|
||||
// 选择项
|
||||
const selects = reactive<any>({
|
||||
dept: {},
|
||||
ids: []
|
||||
});
|
||||
|
||||
// 部门列表
|
||||
const dept = ref<any[]>([]);
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
order: "descending"
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: "selection",
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
prop: "headImg",
|
||||
label: "头像"
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "姓名",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "username",
|
||||
label: "用户名",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "nickName",
|
||||
label: "昵称",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "departmentName",
|
||||
label: "部门名称",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "roleName",
|
||||
label: "角色",
|
||||
headerAlign: "center",
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
prop: "phone",
|
||||
label: "手机号码",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
minWidth: 120,
|
||||
dict: [
|
||||
{
|
||||
label: "启用",
|
||||
value: 1,
|
||||
type: "success"
|
||||
},
|
||||
{
|
||||
label: "禁用",
|
||||
value: 0,
|
||||
type: "danger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
prop: "createTime",
|
||||
label: "创建时间",
|
||||
sortable: "custom",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
type: "op",
|
||||
buttons: ["slot-move-btn", "edit", "delete"],
|
||||
width: 160
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 新增、编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
dialog: {
|
||||
width: "800px"
|
||||
},
|
||||
|
||||
items: [
|
||||
{
|
||||
prop: "headImg",
|
||||
label: "头像",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "cl-upload",
|
||||
props: {
|
||||
text: "选择头像",
|
||||
icon: "el-icon-picture"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "姓名",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写姓名"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "姓名不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "nickName",
|
||||
label: "昵称",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写昵称"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "昵称不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "username",
|
||||
label: "用户名",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写用户名"
|
||||
}
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: "用户名不能为空"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
prop: "password",
|
||||
label: "密码",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写密码",
|
||||
type: "password"
|
||||
}
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
min: 6,
|
||||
max: 16,
|
||||
message: "密码长度在 6 到 16 个字符"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
prop: "roleIdList",
|
||||
label: "角色",
|
||||
span: 24,
|
||||
value: [],
|
||||
component: {
|
||||
name: "cl-role-select",
|
||||
props: {
|
||||
props: {
|
||||
"multiple-limit": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
required: true,
|
||||
message: "角色不能为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "phone",
|
||||
label: "手机号码",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写手机号码"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "email",
|
||||
label: "邮箱",
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写邮箱"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写备注",
|
||||
type: "textarea",
|
||||
rows: 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
value: 1,
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "开启",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "关闭",
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 浏览器信息
|
||||
const browser = computed(() => store.getters.browser);
|
||||
|
||||
// 监听屏幕大小变化
|
||||
watch(
|
||||
() => browser.value.isMini,
|
||||
(val: boolean) => {
|
||||
isExpand.value = !val;
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: any) {
|
||||
ctx.service(service.base.sys.user).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function refresh(params: any) {
|
||||
refs.value.crud.refresh(params);
|
||||
}
|
||||
|
||||
// 刷新监听
|
||||
async function onRefresh(params: any, { next, render }: any) {
|
||||
const { list } = await next(params);
|
||||
|
||||
render(
|
||||
list.map((e: any) => {
|
||||
if (e.roleName) {
|
||||
e.roleNameList = e.roleName.split(",");
|
||||
}
|
||||
|
||||
e.status = Boolean(e.status);
|
||||
|
||||
return e;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 提交钩子
|
||||
function onUpsertSubmit(_: boolean, data: any, { next }: any) {
|
||||
let departmentId = data.departmentId;
|
||||
|
||||
if (!departmentId) {
|
||||
departmentId = selects.dept.id;
|
||||
|
||||
if (!departmentId) {
|
||||
departmentId = dept.value[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
next({
|
||||
...data,
|
||||
departmentId
|
||||
});
|
||||
}
|
||||
|
||||
// 多选监听
|
||||
function onSelectionChange(selection: any[]) {
|
||||
selects.ids = selection.map((e) => e.id);
|
||||
}
|
||||
|
||||
// 部门选择监听
|
||||
function onDeptRowClick({ item, ids }: any) {
|
||||
selects.dept = item;
|
||||
|
||||
refresh({
|
||||
page: 1,
|
||||
departmentIds: ids
|
||||
});
|
||||
|
||||
// 收起
|
||||
if (browser.value.isMini) {
|
||||
isExpand.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 部门下新增成员
|
||||
function onDeptUserAdd(item: any) {
|
||||
refs.value.crud.rowAppend({
|
||||
departmentId: item.id
|
||||
});
|
||||
}
|
||||
|
||||
// 部门列表监听
|
||||
function onDeptListChange(list: any[]) {
|
||||
dept.value = list;
|
||||
}
|
||||
|
||||
// 是否显示部门
|
||||
function deptExpand() {
|
||||
isExpand.value = !isExpand.value;
|
||||
}
|
||||
|
||||
// 移动成员
|
||||
async function toMove(e?: any) {
|
||||
let ids = [];
|
||||
|
||||
if (!e) {
|
||||
ids = selects.ids;
|
||||
} else {
|
||||
ids = [e.id];
|
||||
}
|
||||
|
||||
refs.value["dept-move"].toMove(ids);
|
||||
}
|
||||
|
||||
return {
|
||||
service,
|
||||
refs,
|
||||
isExpand,
|
||||
selects,
|
||||
dept,
|
||||
table,
|
||||
upsert,
|
||||
browser,
|
||||
setRefs,
|
||||
onLoad,
|
||||
refresh,
|
||||
onRefresh,
|
||||
onUpsertSubmit,
|
||||
onSelectionChange,
|
||||
onDeptRowClick,
|
||||
onDeptUserAdd,
|
||||
onDeptListChange,
|
||||
deptExpand,
|
||||
toMove
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.system-user {
|
||||
.pane {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dept {
|
||||
height: 100%;
|
||||
width: 300px;
|
||||
max-width: calc(100% - 50px);
|
||||
background-color: #fff;
|
||||
transition: width 0.3s;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&._collapse {
|
||||
margin-right: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
width: calc(100% - 310px);
|
||||
flex: 1;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
height: 40px;
|
||||
width: 80px;
|
||||
line-height: 40px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dept,
|
||||
.user {
|
||||
overflow: hidden;
|
||||
.container {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.dept {
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user