mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2024-11-01 06:02:38 +08:00
添加vue3
This commit is contained in:
parent
1a63d788f5
commit
3fd0114971
@ -1,2 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
|
@ -1,21 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
yarn.lock
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
@ -1,6 +1,3 @@
|
||||
/public/
|
||||
/dist/
|
||||
/node_modules/
|
||||
/src/icons/svg/
|
||||
/mock/
|
||||
vue.config.js
|
||||
/src/crud
|
||||
/src/core
|
19
.eslintrc.js
19
.eslintrc.js
@ -3,12 +3,19 @@ module.exports = {
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
extends: ["plugin:vue/essential", "@vue/prettier"],
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
"comma-dangle": [2, "never"]
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint"
|
||||
],
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser"
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"@typescript-eslint/no-explicit-any": ["off"]
|
||||
}
|
||||
};
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
@ -10,10 +11,11 @@ node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
yarn.lock
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
|
14
Dockerfile
14
Dockerfile
@ -1,14 +0,0 @@
|
||||
FROM node:lts-alpine
|
||||
WORKDIR /build
|
||||
RUN npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass
|
||||
RUN npm set registry https://registry.npm.taobao.org
|
||||
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.
|
292
README.md
292
README.md
@ -1,290 +1,24 @@
|
||||
<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>
|
||||
# front-next-vue3
|
||||
|
||||
<p align="center">cool-admin 一个很酷的后台权限管理系统,开源免费,模块化、插件化、极速开发 CRUD,方便快速构建迭代后台管理系统, 到论坛 进一步了解</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>
|
||||
|
||||
## 演示
|
||||
|
||||
[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/)
|
||||
|
||||
## 使用条件
|
||||
|
||||
请确保您的操作系统上安装了 Node.js(> = 8.9.0)、@vue/cli (> 3.0.0)。
|
||||
|
||||
## 安装项目依赖
|
||||
|
||||
推荐使用 `yarn`:
|
||||
|
||||
```shell
|
||||
yarn
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
解决 `node-sass` 网络慢的方法:
|
||||
|
||||
```shell
|
||||
yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
|
||||
## 运行应用程序
|
||||
|
||||
安装过程完成后,运行以下命令启动服务。您可以在浏览器中预览网站 [http://localhost:9000](http://localhost:9000)
|
||||
|
||||
```shell
|
||||
yarn serve
|
||||
```
|
||||
|
||||
## 极速 CRUD
|
||||
|
||||
1. `vscode` 编辑器下输入关键字 `cl-crud`,会生成对应的模板代码:
|
||||
|
||||
```html
|
||||
<template>
|
||||
<cl-crud ref="crud" @load="onLoad">
|
||||
<el-row type="flex" align="middle">
|
||||
<!-- 刷新按钮 -->
|
||||
<cl-refresh-btn />
|
||||
<!-- 新增按钮 -->
|
||||
<cl-add-btn />
|
||||
<!-- 删除按钮 -->
|
||||
<cl-multi-delete-btn />
|
||||
<cl-flex1 />
|
||||
<!-- 关键字搜索 -->
|
||||
<cl-search-key />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<!-- 数据表格 -->
|
||||
<cl-table v-bind="table"></cl-table>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1 />
|
||||
<!-- 分页控件 -->
|
||||
<cl-pagination />
|
||||
</el-row>
|
||||
|
||||
<!-- 新增、编辑 -->
|
||||
<cl-upsert ref="upsert" v-bind="upsert"></cl-upsert>
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 新增、编辑配置
|
||||
upsert: {
|
||||
items: []
|
||||
},
|
||||
// 表格配置
|
||||
table: {
|
||||
columns: []
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
// crud 配置
|
||||
ctx.service().done();
|
||||
// 发送 page 接口请求
|
||||
app.refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
2. 编辑数据表格 `cl-table`:
|
||||
|
||||
```js
|
||||
{
|
||||
table: {
|
||||
// 参数与 el-table-column 一致,并支持许多骚操作
|
||||
columns: [
|
||||
// 多选列
|
||||
{
|
||||
type: "selection",
|
||||
width: 60
|
||||
},
|
||||
// 自定义列
|
||||
{
|
||||
label: "昵称",
|
||||
prop: "name"
|
||||
},
|
||||
{
|
||||
label: "账户",
|
||||
prop: "price",
|
||||
sortable: "custom" // 是否添加排序
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
// 字典匹配,使用 el-tag 展示
|
||||
dict: [
|
||||
{
|
||||
label: "启用",
|
||||
value: 1,
|
||||
type: "primary"
|
||||
},
|
||||
{
|
||||
label: "禁用",
|
||||
value: 0,
|
||||
type: "danger"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
prop: "createTime"
|
||||
},
|
||||
// 操作按钮列,默认包含:编辑、删除
|
||||
{
|
||||
type: "op"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
3. 编辑新增、编辑表单 `cl-upsert`:
|
||||
|
||||
```js
|
||||
{
|
||||
upsert: {
|
||||
items: [
|
||||
{
|
||||
label: "昵称",
|
||||
prop: "name",
|
||||
// 参数与 el-form-item 一致
|
||||
props: {},
|
||||
value: "神仙都没用", // 昵称默认值
|
||||
// 渲染参数,支持 slot, 组件实例,jsx
|
||||
component: {
|
||||
name: "el-input", // 可以是任意已注册的组件名
|
||||
props: {}, // 组件的参数
|
||||
on: {} // 组件的回调事件
|
||||
},
|
||||
// 验证规则,与 el-form 一致
|
||||
rules: {
|
||||
required: true,
|
||||
message: "昵称不呢为空"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "存款",
|
||||
prop: "price",
|
||||
component: {
|
||||
name: "el-input-number",
|
||||
props: {
|
||||
min: 0,
|
||||
max: 10000
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
value: 1,
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
{
|
||||
label: "启用",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "禁用",
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. 绑定 `service`。在 `src/service/` 下新建文件 `test.js`,并编辑:
|
||||
|
||||
```js
|
||||
// src/service/test.js
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
|
||||
// 请求接口的路径
|
||||
@Service("test")
|
||||
class Test extends BaseService {
|
||||
// 继承 BaseService 后,拥有 page, list, add, delete, update, info 6个基本接口
|
||||
|
||||
// 自定义其他接口
|
||||
@Permission("product") // 权限装饰器,可选
|
||||
product(id) {
|
||||
// this.request() 参数与 axios 一致
|
||||
return this.request({
|
||||
url: "/product",
|
||||
method: "POST",
|
||||
data: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Test;
|
||||
```
|
||||
|
||||
在 `src/service/` 下的文件,框架会自动根据 `目录结构` 和 `文件名称` 格式化成对应的 `$service` 对象。你现在可以这么使用它:
|
||||
|
||||
```js
|
||||
this.$service.test.page({ page: 1 });
|
||||
this.$service.test.product(1);
|
||||
```
|
||||
|
||||
`service` 编写好后,我们把它绑定在 `crud` 上:
|
||||
|
||||
```js
|
||||
export default {
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
// 绑定 service,这边指定到 test 即可
|
||||
ctx.service(this.$service.test).done();
|
||||
|
||||
// 发起 page 请求
|
||||
app.refresh({
|
||||
// 请求默认参数
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
5. 效果预览
|
||||
|
||||
![](https://cool-show.oss-cn-shanghai.aliyuncs.com/admin/crud.png)
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
|
@ -1,13 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/app"],
|
||||
plugins: [
|
||||
["jsx-v-model"],
|
||||
[
|
||||
"component",
|
||||
{
|
||||
libraryName: "element-ui",
|
||||
styleLibraryName: "theme-chalk"
|
||||
}
|
||||
]
|
||||
]
|
||||
presets: ["@vue/cli-plugin-babel/preset"]
|
||||
};
|
||||
|
84
nginx.conf
84
nginx.conf
@ -1,84 +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;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location /api/
|
||||
{
|
||||
proxy_pass http://midway:7001/;
|
||||
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 /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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
65
package.json
65
package.json
@ -1,65 +1,60 @@
|
||||
{
|
||||
"name": "cool-admin-vue",
|
||||
"version": "3.2.0",
|
||||
"name": "front-next-vue3",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"report": "vue-cli-service build --report",
|
||||
"lint": "vue-cli-service lint",
|
||||
"inspect": "vue inspect --mode=production > output.js"
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.5",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"axios": "^0.21.1",
|
||||
"cl-admin": "^1.5.3",
|
||||
"cl-admin-crud": "^1.6.8",
|
||||
"cl-admin-theme": "^0.0.5",
|
||||
"clipboard": "^2.0.7",
|
||||
"codemirror": "^5.59.4",
|
||||
"cl-admin": "^1.5.1",
|
||||
"clipboard": "^2.0.8",
|
||||
"clone-deep": "^4.0.1",
|
||||
"codemirror": "^5.60.0",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.10.4",
|
||||
"echarts": "^5.0.2",
|
||||
"element-ui": "^2.15.1",
|
||||
"element-plus": "1.0.2-beta.35",
|
||||
"js-beautify": "^1.13.5",
|
||||
"lodash": "^4.17.21",
|
||||
"merge": "^2.1.1",
|
||||
"mitt": "^2.1.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"qs": "^6.9.1",
|
||||
"quill": "^1.3.7",
|
||||
"socket.io-client": "2.3.1",
|
||||
"socket.io-client": "^4.0.0",
|
||||
"store": "^2.0.12",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-cron": "^1.0.9",
|
||||
"vue": "^3.0.9",
|
||||
"vue-echarts": "^6.0.0-rc.3",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.4.0"
|
||||
"vue-router": "^4.0.5",
|
||||
"vuedraggable": "^4.0.1",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^3.0.0",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
|
||||
"@vue/babel-preset-jsx": "^1.1.2",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-plugin-jsx-v-model": "^2.0.3",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"eslint-plugin-vue": "^7.0.0-0",
|
||||
"hard-source-webpack-plugin": "^0.13.1",
|
||||
"lint-staged": "^9.5.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"prettier": "^1.19.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"svg-sprite-loader": "^5.0.0",
|
||||
"typescript": "^3.9.3",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack-cli": "^3.3.12"
|
||||
}
|
||||
"svg-sprite-loader": "^6.0.2",
|
||||
"typescript": "~3.9.3"
|
||||
},
|
||||
"typings": "types/index.d.ts"
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 4.2 KiB |
@ -1,99 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="referer" content="never" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||
<title>COOL-ADMIN</title>
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
|
||||
<% } %>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.preload {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
letter-spacing: 1px;
|
||||
background-color: #2f3447;
|
||||
}
|
||||
|
||||
.preload .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.preload .name {
|
||||
font-size: 30px;
|
||||
color: #fff;
|
||||
letter-spacing: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.preload .title {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.preload .sub-title {
|
||||
color: #ababab;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.preload .footer {
|
||||
text-align: center;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
.preload .footer a {
|
||||
font-size: 12px;
|
||||
color: #ababab;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but cool-admin doesn't work properly without JavaScript enabled. Please
|
||||
enable it to continue.</strong
|
||||
>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<div class="preload">
|
||||
<div class="container">
|
||||
<p class="name">COOL-ADMIN</p>
|
||||
<p class="title">正在加载资源...</p>
|
||||
<p class="sub-title">初次加载资源可能需要较多时间 请耐心等待</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<a href="https://cool-js.com/" target="_blank"> https://cool-js.com </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||
<% } %>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,6 +4,6 @@ $--color-danger: $color-danger;
|
||||
$--color-warning: $color-warning;
|
||||
$--color-info: $color-info;
|
||||
|
||||
$--font-path: "~element-ui/lib/theme-chalk/fonts";
|
||||
$--font-path: "~element-plus/lib/theme-chalk/fonts";
|
||||
|
||||
@import "~element-ui/packages/theme-chalk/src/index";
|
||||
@import "~element-plus/packages/theme-chalk/src/index";
|
||||
|
@ -1,7 +1,6 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
@ -1,20 +1,18 @@
|
||||
import store from "store";
|
||||
import { getUrlParam } from "cl-admin/utils";
|
||||
import { getUrlParam } from "@/core/utils";
|
||||
import { MenuItem } from "@/cool/modules/base/types";
|
||||
|
||||
// 路由模式
|
||||
export const routerMode = "history";
|
||||
const routerMode = "history";
|
||||
|
||||
// 开发模式
|
||||
export const isDev = process.env.NODE_ENV == "development";
|
||||
const isDev: boolean = process.env.NODE_ENV == "development";
|
||||
|
||||
// Host
|
||||
export const host = "https://show.cool-admin.com";
|
||||
const host = "https://show.cool-admin.com";
|
||||
|
||||
// Socket
|
||||
export const socketUrl = (isDev ? `${host}` : "") + "/socket";
|
||||
|
||||
// 请求地址,本地会使用代理请求
|
||||
export const baseUrl = (function() {
|
||||
// 请求地址
|
||||
const baseUrl: string = (function() {
|
||||
let proxy = getUrlParam("proxy");
|
||||
|
||||
if (proxy) {
|
||||
@ -26,11 +24,14 @@ export const baseUrl = (function() {
|
||||
return isDev ? `/${proxy}/admin` : `/api/admin`;
|
||||
})();
|
||||
|
||||
// Socket
|
||||
const socketUrl: string = (isDev ? `${host}` : "") + "/socket";
|
||||
|
||||
// 阿里字体图标库 https://at.alicdn.com/t/**.css
|
||||
export const iconfontUrl = ``;
|
||||
const iconfontUrl = ``;
|
||||
|
||||
// 程序配置参数
|
||||
export const app = store.get("__app__") || {
|
||||
const app: any = store.get("__app__") || {
|
||||
name: "COOL-ADMIN",
|
||||
|
||||
conf: {
|
||||
@ -47,4 +48,6 @@ export const app = store.get("__app__") || {
|
||||
};
|
||||
|
||||
// 自定义菜单列表
|
||||
export const menuList = [];
|
||||
const menuList: MenuItem[] = [];
|
||||
|
||||
export { routerMode, baseUrl, socketUrl, iconfontUrl, app, isDev, menuList };
|
@ -1,46 +0,0 @@
|
||||
import Crud from "cl-admin-crud";
|
||||
import Theme from "cl-admin-theme";
|
||||
|
||||
export default {
|
||||
modules: [
|
||||
// 基础模块
|
||||
"base",
|
||||
// 文件上传
|
||||
{
|
||||
name: "upload",
|
||||
options: {
|
||||
icon: "el-icon-picture",
|
||||
text: "选择图片"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "crud",
|
||||
value: Crud,
|
||||
options: {
|
||||
crud: {
|
||||
dict: {
|
||||
sort: {
|
||||
prop: "order",
|
||||
order: "sort"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 客服聊天
|
||||
"chat",
|
||||
// 任务管理
|
||||
"task",
|
||||
// 复制指令
|
||||
"copy",
|
||||
// 省市区选择
|
||||
"distpicker",
|
||||
// 示例页
|
||||
"demo",
|
||||
// 主题切换
|
||||
{
|
||||
name: "theme",
|
||||
value: Theme
|
||||
}
|
||||
]
|
||||
};
|
3
src/cool/index.ts
Normal file
3
src/cool/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default {
|
||||
modules: ["base", "demo", "copy", "upload", "task", "theme", "chat"]
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import store from "@/store";
|
||||
|
||||
const lock = {
|
||||
const lock: any = {
|
||||
menuCollapse: null,
|
||||
showAMenu: null
|
||||
};
|
@ -32,7 +32,7 @@ function iconList() {
|
||||
return req
|
||||
.keys()
|
||||
.map(req)
|
||||
.map(e => e.default.id)
|
||||
.map((e: any) => e.default.id)
|
||||
.filter(e => e.includes("icon"))
|
||||
.sort();
|
||||
}
|
@ -1,17 +1,20 @@
|
||||
<template>
|
||||
<div class="cl-avatar" :class="[size, shape]" :style="[style]">
|
||||
<el-image :src="src" alt="">
|
||||
<div slot="error" class="image-slot">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isNumber } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { isNumber } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-avatar",
|
||||
|
||||
props: {
|
||||
@ -26,17 +29,21 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
const size = isNumber(this.size) ? this.size + "px" : this.size;
|
||||
setup(props) {
|
||||
const size = isNumber(props.size) ? props.size + "px" : props.size;
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
height: size,
|
||||
width: size
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
style
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -76,7 +83,7 @@ export default {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
/deep/.image-slot {
|
||||
:deep(.image-slot) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -1,19 +1,12 @@
|
||||
<template>
|
||||
<div class="cl-codemirror">
|
||||
<codemirror
|
||||
ref="code"
|
||||
v-model="value2"
|
||||
:options="options2"
|
||||
:style="{
|
||||
height,
|
||||
width
|
||||
}"
|
||||
/>
|
||||
<textarea class="cl-code" id="editor" :height="height" :width="width"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { codemirror } from "vue-codemirror";
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, watch } from "vue";
|
||||
import CodeMirror from "codemirror";
|
||||
import beautifyJs from "js-beautify";
|
||||
|
||||
import "codemirror/theme/cobalt.css";
|
||||
@ -22,72 +15,94 @@ import "codemirror/addon/hint/show-hint.css";
|
||||
import "codemirror/addon/hint/javascript-hint";
|
||||
import "codemirror/mode/javascript/javascript";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-codemirror",
|
||||
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
|
||||
props: {
|
||||
value: String,
|
||||
modelValue: null,
|
||||
height: String,
|
||||
width: String,
|
||||
options: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value2: ""
|
||||
};
|
||||
},
|
||||
emits: ["update:modelValue", "load"],
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.value2 = val || "";
|
||||
}
|
||||
},
|
||||
value2(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
let editor: any = null;
|
||||
|
||||
computed: {
|
||||
options2() {
|
||||
return {
|
||||
// 获取内容
|
||||
function getValue() {
|
||||
return editor ? editor.getValue() : "";
|
||||
}
|
||||
|
||||
// 设置内容
|
||||
function setValue(val?: string) {
|
||||
if (editor) {
|
||||
editor.setValue(beautifyJs(val || getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// 监听内容变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string) => {
|
||||
if (editor) {
|
||||
if (val != getValue().replace(/\s/g, "")) {
|
||||
setValue(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(function() {
|
||||
// 实例化
|
||||
editor = CodeMirror.fromTextArea(document.getElementById("editor"), {
|
||||
mode: "javascript",
|
||||
theme: "ambiance",
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
indentUnit: 4,
|
||||
...this.options
|
||||
};
|
||||
}
|
||||
},
|
||||
...props.options
|
||||
});
|
||||
|
||||
mounted() {
|
||||
this.$el.onkeydown = e => {
|
||||
let keyCode = e.keyCode || e.which || e.charCode;
|
||||
let altKey = e.altKey || e.metaKey;
|
||||
let shiftKey = e.shiftKey || e.metaKey;
|
||||
// 输入监听
|
||||
editor.on("change", (e: any) => {
|
||||
emit("update:modelValue", e.getValue().replace(/\s/g, ""));
|
||||
});
|
||||
|
||||
// 加载回调
|
||||
emit("load", editor);
|
||||
|
||||
// 设置编辑框样式
|
||||
const el = editor.display.wrapper;
|
||||
|
||||
if (el) {
|
||||
if (props.height) {
|
||||
el.style.height = props.height || "50px";
|
||||
}
|
||||
|
||||
if (props.width) {
|
||||
el.style.width = props.width;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置内容
|
||||
setValue(props.modelValue);
|
||||
|
||||
// shift + alt + f 格式化
|
||||
el.onkeydown = (e: any) => {
|
||||
const keyCode = e.keyCode || e.which || e.charCode;
|
||||
const altKey = e.altKey || e.metaKey;
|
||||
const shiftKey = e.shiftKey || e.metaKey;
|
||||
|
||||
if (altKey && shiftKey && keyCode == 70) {
|
||||
this.setValue();
|
||||
setValue();
|
||||
}
|
||||
};
|
||||
|
||||
this.setValue(this.value2);
|
||||
},
|
||||
|
||||
methods: {
|
||||
setValue(val) {
|
||||
this.value2 = beautifyJs(val || this.value2);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -98,10 +113,6 @@ export default {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cm-s-ambiance * {
|
||||
font-family: "Consolas";
|
||||
font-size: 13px;
|
||||
|
@ -15,15 +15,18 @@
|
||||
|
||||
<div class="cl-dept-check__tree" v-if="visible">
|
||||
<el-tree
|
||||
:data="list"
|
||||
:props="props"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
:check-strictly="!form.relevance"
|
||||
ref="treeRef"
|
||||
highlight-current
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
ref="tree"
|
||||
:data="list"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
:check-strictly="!form.relevance"
|
||||
@check-change="onCheckChange"
|
||||
>
|
||||
</el-tree>
|
||||
@ -31,83 +34,120 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { deepTree } from "@/core/utils";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, inject, nextTick, onMounted, ref, watch } from "vue";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-dept-check",
|
||||
|
||||
props: {
|
||||
value: Array,
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
title: String
|
||||
},
|
||||
|
||||
inject: ["form"],
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
checked: [],
|
||||
keyword: "",
|
||||
props: {
|
||||
label: "name",
|
||||
children: "children"
|
||||
},
|
||||
loading: false,
|
||||
visible: true
|
||||
};
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// 请求服务
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
watch: {
|
||||
keyword(val) {
|
||||
this.$refs["tree"].filter(val);
|
||||
},
|
||||
// 表单值
|
||||
const form = inject<any>("form");
|
||||
|
||||
value(val) {
|
||||
this.refreshTree(val);
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 已选列表
|
||||
const checked = ref<any>([]);
|
||||
|
||||
// 关键字搜素
|
||||
const keyword = ref<string>("");
|
||||
|
||||
// 加载中
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const treeRef = ref<any>({});
|
||||
|
||||
// 刷新已选列表
|
||||
function refreshTree(val: any[]) {
|
||||
checked.value = val || [];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
methods: {
|
||||
refreshTree(val) {
|
||||
this.checked = val || [];
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.$service.system.dept
|
||||
// 刷新树形列表
|
||||
function refresh() {
|
||||
$service.system.dept
|
||||
.list()
|
||||
.then(res => {
|
||||
this.list = deepTree(res);
|
||||
this.refreshTree(this.value);
|
||||
.then((res: any[]) => {
|
||||
list.value = deepTree(res);
|
||||
refreshTree(props.modelValue);
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
filterNode(val, data) {
|
||||
// 过滤节点
|
||||
function filterNode(val: string, data: any) {
|
||||
if (!val) return true;
|
||||
return data.name.includes(val);
|
||||
},
|
||||
}
|
||||
|
||||
onCheckStrictlyChange() {
|
||||
this.form.departmentIdList = [];
|
||||
this.visible = false;
|
||||
// 是否关联上下级
|
||||
function onCheckStrictlyChange() {
|
||||
visible.value = false;
|
||||
checked.value = [];
|
||||
emit("update:modelValue", []);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.visible = true;
|
||||
nextTick(() => {
|
||||
visible.value = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onCheckChange() {
|
||||
this.$emit("input", this.$refs["tree"].getCheckedKeys());
|
||||
// 监听选择
|
||||
function onCheckChange() {
|
||||
emit("update:modelValue", treeRef.value.getCheckedKeys());
|
||||
}
|
||||
|
||||
// 监听过滤
|
||||
watch(keyword, (val: string) => {
|
||||
treeRef.value.filter(val);
|
||||
});
|
||||
|
||||
// 刷新树
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: any[]) => {
|
||||
refreshTree(val);
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
list,
|
||||
checked,
|
||||
keyword,
|
||||
loading,
|
||||
visible,
|
||||
refresh,
|
||||
filterNode,
|
||||
onCheckStrictlyChange,
|
||||
onCheckChange,
|
||||
treeRef
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
120
src/cool/modules/base/components/dept/move.tsx
Normal file
120
src/cool/modules/base/components/dept/move.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import { useRefs } from "@/core";
|
||||
import { deepTree } from "@/core/utils";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { defineComponent, h, inject, ref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-dept-move",
|
||||
|
||||
emits: ["success", "error"],
|
||||
|
||||
setup(_: any, { emit }) {
|
||||
const $service = inject<any>("$service");
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 刷新列表
|
||||
async function refresh() {
|
||||
return await $service.system.dept.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.system.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,104 +0,0 @@
|
||||
<template>
|
||||
<div class="cl-dept-move"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
|
||||
export default {
|
||||
name: "cl-dept-move",
|
||||
|
||||
methods: {
|
||||
async toMove(ids) {
|
||||
this.$crud.openForm({
|
||||
title: "部门转移",
|
||||
width: "600px",
|
||||
props: {
|
||||
"label-width": "80px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "选择部门",
|
||||
prop: "dept",
|
||||
component: {
|
||||
name: "system-user__dept-move",
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.list = await this.$service.system.dept.list().then(deepTree);
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectRow(e) {
|
||||
this.$emit("input", e);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #eee",
|
||||
"border-radius": "3px",
|
||||
padding: "2px"
|
||||
}}>
|
||||
<el-tree
|
||||
data={this.list}
|
||||
{...{
|
||||
props: {
|
||||
props: {
|
||||
label: "name"
|
||||
}
|
||||
}
|
||||
}}
|
||||
node-key="id"
|
||||
highlight-current
|
||||
on-node-click={this.selectRow}></el-tree>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
on: {
|
||||
submit: (data, { done, close }) => {
|
||||
if (!data.dept) {
|
||||
this.$message.warning("请选择部门");
|
||||
return done();
|
||||
}
|
||||
|
||||
const { name, id } = data.dept;
|
||||
|
||||
this.$confirm(`是否将用户转移到部门 ${name} 下`, "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$service.system.user
|
||||
.move({
|
||||
departmentId: id,
|
||||
userIds: ids
|
||||
})
|
||||
.then(res => {
|
||||
this.$message.success("转移成功");
|
||||
this.$emit("success", res);
|
||||
close();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
this.$emit("error", err);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -10,7 +10,7 @@
|
||||
</el-tooltip>
|
||||
</li>
|
||||
|
||||
<li v-if="drag && !browser.isMini">
|
||||
<li v-if="drag && !isMini">
|
||||
<el-tooltip content="拖动排序">
|
||||
<i class="el-icon-s-operation" @click="isDrag = true"></i>
|
||||
</el-tooltip>
|
||||
@ -39,14 +39,14 @@
|
||||
v-loading="loading"
|
||||
@node-contextmenu="openCM"
|
||||
>
|
||||
<template slot-scope="{ node, data }">
|
||||
<template #default="{ node, data }">
|
||||
<div class="cl-dept-tree__node">
|
||||
<span class="cl-dept-tree__node-label" @click="rowClick(data)">{{
|
||||
node.label
|
||||
}}</span>
|
||||
<span
|
||||
class="cl-dept-tree__node-icon"
|
||||
v-if="browser.isMini"
|
||||
v-if="isMini"
|
||||
@click="openCM($event, data, node)"
|
||||
>
|
||||
<i class="el-icon-more"></i>
|
||||
@ -55,15 +55,19 @@
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
|
||||
<cl-form :ref="setRefs('form')"></cl-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree, isArray, revDeepTree } from "cl-admin/utils";
|
||||
import { ContextMenu, Form } from "cl-admin-crud";
|
||||
import { mapGetters } from "vuex";
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, onMounted, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { ContextMenu } from "@/crud";
|
||||
import { useRefs } from "@/core";
|
||||
import { deepTree, isArray, revDeepTree, isPc } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-dept-tree",
|
||||
|
||||
props: {
|
||||
@ -77,110 +81,64 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
isDrag: false
|
||||
};
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
computed: {
|
||||
...mapGetters(["browser"])
|
||||
},
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
created() {
|
||||
this.refresh();
|
||||
},
|
||||
// 加载中
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
methods: {
|
||||
openCM(e, d, n) {
|
||||
if (!d) {
|
||||
d = this.list[0] || {};
|
||||
}
|
||||
// 是否能拖动
|
||||
const isDrag = ref<boolean>(false);
|
||||
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "新增",
|
||||
"suffix-icon": "el-icon-plus",
|
||||
hidden: n && n.level >= this.level,
|
||||
callback: (_, done) => {
|
||||
this.rowEdit({
|
||||
name: "",
|
||||
parentName: d.name,
|
||||
parentId: d.id
|
||||
});
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "编辑",
|
||||
"suffix-icon": "el-icon-edit",
|
||||
callback: (_, done) => {
|
||||
this.rowEdit(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
"suffix-icon": "el-icon-delete",
|
||||
hidden: !Boolean(d.parentId),
|
||||
callback: (_, done) => {
|
||||
this.rowDel(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "新增成员",
|
||||
"suffix-icon": "el-icon-user",
|
||||
callback: (_, done) => {
|
||||
this.$emit("user-add", d);
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
// 请求服务
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
allowDrag({ data }) {
|
||||
// 允许托的规则
|
||||
function allowDrag({ data }: any) {
|
||||
return data.parentId;
|
||||
},
|
||||
}
|
||||
|
||||
allowDrop(_, dropNode) {
|
||||
// 允许放的规则
|
||||
function allowDrop(_: any, dropNode: any) {
|
||||
return dropNode.data.parentId;
|
||||
},
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.isDrag = false;
|
||||
this.loading = true;
|
||||
// 刷新
|
||||
function refresh() {
|
||||
isDrag.value = false;
|
||||
loading.value = true;
|
||||
|
||||
this.$service.system.dept
|
||||
$service.system.dept
|
||||
.list()
|
||||
.then(res => {
|
||||
this.list = deepTree(res);
|
||||
this.$emit("list-change", this.list);
|
||||
.then((res: any[]) => {
|
||||
list.value = deepTree(res);
|
||||
emit("list-change", list.value);
|
||||
})
|
||||
.done(() => {
|
||||
this.loading = false;
|
||||
loading.value = false;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
rowClick(e) {
|
||||
// 获取 ids
|
||||
function rowClick(e: any) {
|
||||
ContextMenu.close();
|
||||
let ids = e.children ? revDeepTree(e.children).map(e => e.id) : [];
|
||||
const ids = e.children ? revDeepTree(e.children).map(e => e.id) : [];
|
||||
ids.unshift(e.id);
|
||||
this.$emit("row-click", { item: e, ids });
|
||||
},
|
||||
emit("row-click", { item: e, ids });
|
||||
}
|
||||
|
||||
rowEdit(e) {
|
||||
// 编辑部门
|
||||
function rowEdit(e: any) {
|
||||
const method = e.id ? "update" : "add";
|
||||
|
||||
Form.open({
|
||||
refs.value.form.open({
|
||||
title: "编辑部门",
|
||||
width: "550px",
|
||||
props: {
|
||||
"label-width": "100px"
|
||||
labelWidth: "100px"
|
||||
},
|
||||
items: [
|
||||
{
|
||||
@ -189,7 +147,7 @@ export default {
|
||||
value: e.name,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写部门名称"
|
||||
}
|
||||
},
|
||||
@ -204,7 +162,7 @@ export default {
|
||||
value: e.parentName || "...",
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
@ -224,50 +182,51 @@ export default {
|
||||
}
|
||||
],
|
||||
on: {
|
||||
submit: (data, { done, close }) => {
|
||||
this.$service.system.dept[method]({
|
||||
submit: (data: any, { done, close }: any) => {
|
||||
$service.system.dept[method]({
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
name: data.name,
|
||||
orderNum: data.orderNum
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`新增部门${data.name}成功`);
|
||||
ElMessage.success(`新增部门${data.name}成功`);
|
||||
close();
|
||||
this.refresh();
|
||||
refresh();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
rowDel(e) {
|
||||
const del = f => {
|
||||
this.$service.system.dept
|
||||
// 删除部门
|
||||
function rowDel(e: any) {
|
||||
const del = (f: boolean) => {
|
||||
$service.system.dept
|
||||
.delete({
|
||||
ids: [e.id],
|
||||
deleteUser: f
|
||||
})
|
||||
.then(() => {
|
||||
if (f) {
|
||||
this.$message.success("删除成功");
|
||||
ElMessage.success("删除成功");
|
||||
} else {
|
||||
this.$confirm(
|
||||
ElMessageBox.confirm(
|
||||
`“${e.name}” 部门的用户已成功转移到 “${e.parentName}” 部门。`,
|
||||
"删除成功"
|
||||
);
|
||||
}
|
||||
})
|
||||
.done(() => {
|
||||
this.refresh();
|
||||
refresh();
|
||||
});
|
||||
};
|
||||
|
||||
this.$confirm(`该操作会删除 “${e.name}” 部门的所有用户,是否确认?`, "提示", {
|
||||
ElMessageBox.confirm(`该操作会删除 “${e.name}” 部门的所有用户,是否确认?`, "提示", {
|
||||
type: "warning",
|
||||
confirmButtonText: "直接删除",
|
||||
cancelButtonText: "保留用户",
|
||||
@ -276,20 +235,23 @@ export default {
|
||||
.then(() => {
|
||||
del(true);
|
||||
})
|
||||
.catch(action => {
|
||||
.catch((action: string) => {
|
||||
if (action == "cancel") {
|
||||
del(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
treeOrder(f) {
|
||||
// 部门排序
|
||||
function treeOrder(f: boolean) {
|
||||
if (f) {
|
||||
this.$confirm("部门架构已发生改变,是否保存?", "提示", {
|
||||
ElMessageBox.confirm("部门架构已发生改变,是否保存?", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
const deep = (list, pid) => {
|
||||
const ids: any[] = [];
|
||||
|
||||
const deep = (list: any[], pid: any) => {
|
||||
list.forEach(e => {
|
||||
e.parentId = pid;
|
||||
ids.push(e);
|
||||
@ -300,11 +262,9 @@ export default {
|
||||
});
|
||||
};
|
||||
|
||||
let ids = [];
|
||||
deep(list.value, null);
|
||||
|
||||
deep(this.list, null);
|
||||
|
||||
this.$service.system.dept
|
||||
$service.system.dept
|
||||
.order(
|
||||
ids.map((e, i) => {
|
||||
return {
|
||||
@ -315,23 +275,94 @@ export default {
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
this.$message.success("更新排序成功");
|
||||
ElMessage.success("更新排序成功");
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
})
|
||||
.done(() => {
|
||||
this.refresh();
|
||||
this.isDrag = false;
|
||||
refresh();
|
||||
isDrag.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => null);
|
||||
} else {
|
||||
this.refresh();
|
||||
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,
|
||||
callback: (_: any, done: Function) => {
|
||||
rowEdit({
|
||||
name: "",
|
||||
parentName: d.name,
|
||||
parentId: d.id
|
||||
});
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "编辑",
|
||||
"suffix-icon": "el-icon-edit",
|
||||
callback: (_: any, done: Function) => {
|
||||
rowEdit(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
"suffix-icon": "el-icon-delete",
|
||||
hidden: !d.parentId,
|
||||
callback: (_: any, done: Function) => {
|
||||
rowDel(d);
|
||||
done();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "新增成员",
|
||||
"suffix-icon": "el-icon-user",
|
||||
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" scoped>
|
||||
@ -360,7 +391,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.el-tree-node__content {
|
||||
:deep(.el-tree-node__content) {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
@ -387,7 +418,7 @@ export default {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
/deep/.el-tree-node__content {
|
||||
:deep(.el-tree-node__content) {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +1,102 @@
|
||||
<template>
|
||||
<div class="cl-editor-quill">
|
||||
<div class="editor" :style="style"></div>
|
||||
<div :ref="setRefs('editor')" class="editor" :style="style"></div>
|
||||
|
||||
<cl-upload-space
|
||||
ref="upload-space"
|
||||
:ref="setRefs('upload-space')"
|
||||
detail-data
|
||||
:show-button="false"
|
||||
@confirm="onFileConfirm"
|
||||
@confirm="onUploadSpaceConfirm"
|
||||
>
|
||||
</cl-upload-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, watch } from "vue";
|
||||
import Quill from "quill";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import { isNumber } from "cl-admin/utils";
|
||||
import { isNumber } from "@/core/utils";
|
||||
import { useRefs } from "@/core";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-editor-quill",
|
||||
|
||||
props: {
|
||||
value: null,
|
||||
options: Object,
|
||||
modelValue: null,
|
||||
height: [String, Number],
|
||||
width: [String, Number],
|
||||
options: Object
|
||||
width: [String, Number]
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
content: "",
|
||||
quill: null,
|
||||
cursorIndex: 0
|
||||
};
|
||||
},
|
||||
emits: ["update:modelValue", "load"],
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
const height = isNumber(this.height) ? this.height + "px" : this.height;
|
||||
const width = isNumber(this.width) ? this.width + "px" : this.width;
|
||||
setup(props, { emit }) {
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置内容
|
||||
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: {
|
||||
value(val) {
|
||||
// 监听绑定值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string) => {
|
||||
if (val) {
|
||||
if (val !== this.content) {
|
||||
this.setContent(val);
|
||||
if (val !== content.value) {
|
||||
setContent(val);
|
||||
}
|
||||
} else {
|
||||
this.setContent("");
|
||||
setContent("");
|
||||
}
|
||||
},
|
||||
|
||||
content(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
mounted() {
|
||||
onMounted(function() {
|
||||
// 实例化
|
||||
this.quill = new Quill(this.$el.querySelector(".editor"), {
|
||||
quill = new Quill(refs.value.editor, {
|
||||
theme: "snow",
|
||||
placeholder: "输入内容",
|
||||
modules: {
|
||||
@ -86,59 +117,37 @@ export default {
|
||||
["link", "image"]
|
||||
]
|
||||
},
|
||||
...this.options
|
||||
...props.options
|
||||
});
|
||||
|
||||
// 添加文件上传工具
|
||||
this.quill.getModule("toolbar").addHandler("image", this.uploadHandler);
|
||||
// 添加图片工具
|
||||
quill.getModule("toolbar").addHandler("image", uploadFileHandler);
|
||||
|
||||
// 监听内容变化
|
||||
this.quill.on("text-change", () => {
|
||||
this.content = this.quill.root.innerHTML;
|
||||
// 监听输入
|
||||
quill.on("text-change", () => {
|
||||
content.value = quill.root.innerHTML;
|
||||
emit("update:modelValue", content.value);
|
||||
});
|
||||
|
||||
// 设置默认内容
|
||||
this.setContent(this.value);
|
||||
// 设置内容
|
||||
setContent(props.modelValue);
|
||||
|
||||
// 加载完回调
|
||||
this.$emit("load", this.quill);
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadHandler() {
|
||||
const selection = this.quill.getSelection();
|
||||
|
||||
if (selection) {
|
||||
this.cursorIndex = selection.index;
|
||||
}
|
||||
|
||||
this.$refs["upload-space"].open();
|
||||
},
|
||||
|
||||
onFileConfirm(files) {
|
||||
if (files.length > 0) {
|
||||
// 批量插件图片
|
||||
files.forEach((file, i) => {
|
||||
let [type] = file.type.split("/");
|
||||
|
||||
this.quill.insertEmbed(
|
||||
this.cursorIndex + i,
|
||||
type,
|
||||
file.url,
|
||||
Quill.sources.USER
|
||||
);
|
||||
// 加载回调
|
||||
emit("load", quill);
|
||||
});
|
||||
|
||||
// 移动光标到图片后一位
|
||||
this.quill.setSelection(this.cursorIndex + files.length);
|
||||
return {
|
||||
refs,
|
||||
content,
|
||||
quill,
|
||||
cursorIndex,
|
||||
style,
|
||||
setRefs,
|
||||
setContent,
|
||||
onUploadSpaceConfirm
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
setContent(val) {
|
||||
this.quill.root.innerHTML = val || "";
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<svg :class="svgClass" :style="style2" aria-hidden="true">
|
||||
<svg :class="svgClass" :style="style" aria-hidden="true">
|
||||
<use :xlink:href="iconName"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isNumber } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import { isNumber } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "icon-svg",
|
||||
|
||||
props: {
|
||||
@ -22,30 +23,26 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
setup(props) {
|
||||
const style = ref<any>({
|
||||
fontSize: isNumber(props.size) ? props.size + "px" : props.size
|
||||
});
|
||||
|
||||
const iconName = computed<string>(() => `#${props.name}`);
|
||||
const svgClass = computed<Array<string>>(() => {
|
||||
return ["icon-svg", `icon-svg__${props.name}`, String(props.className || "")];
|
||||
});
|
||||
|
||||
return {
|
||||
style2: {}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#${this.name}`;
|
||||
},
|
||||
svgClass() {
|
||||
return ["icon-svg", `icon-svg__${this.name}`, this.className];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.style2 = {
|
||||
fontSize: isNumber(this.size) ? this.size + "px" : this.size
|
||||
style,
|
||||
iconName,
|
||||
svgClass
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.icon-svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
@ -1,39 +0,0 @@
|
||||
import Avatar from "./avatar";
|
||||
import Scrollbar from "./scrollbar";
|
||||
import RouteNav from "./route-nav";
|
||||
import Process from "./process";
|
||||
import IconSvg from "./icon-svg";
|
||||
import DeptCheck from "./dept/check";
|
||||
import DeptMove from "./dept/move";
|
||||
import DeptTree from "./dept/tree";
|
||||
import MenuSlider from "./menu/slider";
|
||||
import MenuTopbar from "./menu/topbar";
|
||||
import MenuFile from "./menu/file";
|
||||
import MenuIcons from "./menu/icons";
|
||||
import MenuPerms from "./menu/perms";
|
||||
import MenuTree from "./menu/tree";
|
||||
import RoleSelect from "./role/select";
|
||||
import RolePerms from "./role/perms";
|
||||
import EditorQuill from "./editor-quill";
|
||||
import Codemirror from "./codemirror";
|
||||
|
||||
export default {
|
||||
Avatar,
|
||||
Scrollbar,
|
||||
RouteNav,
|
||||
Process,
|
||||
IconSvg,
|
||||
DeptCheck,
|
||||
DeptMove,
|
||||
DeptTree,
|
||||
MenuSlider,
|
||||
MenuTopbar,
|
||||
MenuFile,
|
||||
MenuIcons,
|
||||
MenuPerms,
|
||||
MenuTree,
|
||||
RoleSelect,
|
||||
RolePerms,
|
||||
EditorQuill,
|
||||
Codemirror
|
||||
};
|
39
src/cool/modules/base/components/index.ts
Normal file
39
src/cool/modules/base/components/index.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import Avatar from "./avatar/index.vue";
|
||||
import Scrollbar from "./scrollbar/index.vue";
|
||||
import RouteNav from "./route-nav/index.vue";
|
||||
import Process from "./process/index.vue";
|
||||
import IconSvg from "./icon-svg/index.vue";
|
||||
import DeptCheck from "./dept/check.vue";
|
||||
import DeptMove from "./dept/move";
|
||||
import DeptTree from "./dept/tree.vue";
|
||||
import MenuSlider from "./menu/slider/index";
|
||||
import MenuTopbar from "./menu/topbar.vue";
|
||||
import MenuFile from "./menu/file.vue";
|
||||
import MenuIcons from "./menu/icons.vue";
|
||||
import MenuPerms from "./menu/perms.vue";
|
||||
import MenuTree from "./menu/tree.vue";
|
||||
import RoleSelect from "./role/select.vue";
|
||||
import RolePerms from "./role/perms.vue";
|
||||
import EditorQuill from "./editor-quill/index.vue";
|
||||
import Codemirror from "./codemirror/index.vue";
|
||||
|
||||
export default {
|
||||
Avatar,
|
||||
Scrollbar,
|
||||
RouteNav,
|
||||
Process,
|
||||
IconSvg,
|
||||
DeptCheck,
|
||||
DeptMove,
|
||||
DeptTree,
|
||||
MenuSlider,
|
||||
MenuTopbar,
|
||||
MenuFile,
|
||||
MenuIcons,
|
||||
MenuPerms,
|
||||
MenuTree,
|
||||
RoleSelect,
|
||||
RolePerms,
|
||||
EditorQuill,
|
||||
Codemirror
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="cl-menu-file">
|
||||
<el-select v-model="newValue" allow-create filterable clearable placeholder="请选择">
|
||||
<el-select v-model="path" allow-create filterable clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
@ -12,55 +12,62 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
|
||||
const files = require
|
||||
.context("@/", true, /views\/(?!(components)|(.*\/components)|(index\.js)).*.(js|vue)/)
|
||||
.keys();
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-menu-file",
|
||||
|
||||
props: {
|
||||
value: [String]
|
||||
},
|
||||
|
||||
inject: ["form"],
|
||||
|
||||
data() {
|
||||
return {
|
||||
newValue: "",
|
||||
list: []
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.newValue = val || "";
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
newValue(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
created() {
|
||||
this.list = files.map(e => {
|
||||
setup(props, { emit }) {
|
||||
// 路径
|
||||
const path = ref<string>(props.modelValue);
|
||||
|
||||
// 数据列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
val => {
|
||||
path.value = val || "";
|
||||
}
|
||||
);
|
||||
|
||||
watch(path, val => {
|
||||
emit("update:modelValue", val);
|
||||
});
|
||||
|
||||
list.value = files.map(e => {
|
||||
return {
|
||||
value: e.substr(2)
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
path,
|
||||
list
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-menu-file {
|
||||
width: 100%;
|
||||
|
||||
/deep/ .el-select {
|
||||
:deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -1,83 +1,105 @@
|
||||
<template>
|
||||
<div class="cl-menu-icons">
|
||||
<el-popover
|
||||
ref="iconPopover"
|
||||
:visible="visible"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
width="480px"
|
||||
popper-class="popper-menu-icon"
|
||||
>
|
||||
<el-row :gutter="10" class="list">
|
||||
<el-row :gutter="10" class="list scroller1">
|
||||
<el-col :span="3" :xs="4" v-for="(item, index) in list" :key="index">
|
||||
<el-button
|
||||
size="mini"
|
||||
:class="{ 'is-active': item === value }"
|
||||
@click="onUpdate(item)"
|
||||
:class="{ 'is-active': item === name }"
|
||||
@click="onChange(item)"
|
||||
>
|
||||
<icon-svg :name="item"></icon-svg>
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
|
||||
<template #reference>
|
||||
<el-input
|
||||
v-model="name"
|
||||
v-popover:iconPopover
|
||||
placeholder="请选择"
|
||||
@input="onUpdate"
|
||||
clearable
|
||||
@click="open"
|
||||
@input="onChange"
|
||||
></el-input>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { iconList } from "@/cool/modules/base";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-menu-icons",
|
||||
|
||||
props: {
|
||||
value: String
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
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 {
|
||||
list: [],
|
||||
name: ""
|
||||
name,
|
||||
list,
|
||||
visible,
|
||||
open,
|
||||
close,
|
||||
onChange
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.name = val;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.list = iconList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
onUpdate(icon) {
|
||||
this.$emit("input", icon);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
.
|
||||
<style lang="scss">
|
||||
.popper-menu-icon {
|
||||
width: 480px;
|
||||
max-width: 90%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.list {
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
|
@ -1,66 +1,78 @@
|
||||
<template>
|
||||
<div class="cl-menu-perms">
|
||||
<el-cascader
|
||||
:options="options"
|
||||
:props="{ multiple: true }"
|
||||
v-model="value"
|
||||
separator=":"
|
||||
clearable
|
||||
filterable
|
||||
v-model="newValue"
|
||||
:options="options"
|
||||
:props="{ multiple: true }"
|
||||
@change="onChange"
|
||||
></el-cascader>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, ref, watch } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-perms",
|
||||
|
||||
props: {
|
||||
value: [String, Number, Array]
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
newValue: []
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.parse();
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
let options = [];
|
||||
let list = [];
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
const flat = obj => {
|
||||
for (let i in obj) {
|
||||
let { permission } = obj[i];
|
||||
setup(props, { emit }) {
|
||||
const $service = inject("$service");
|
||||
|
||||
// 绑定值
|
||||
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) {
|
||||
list = [...list, Object.values(permission)].flat();
|
||||
perms = [...perms, Object.values(permission)].flat();
|
||||
} else {
|
||||
flat(obj[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flat(this.$service);
|
||||
flat($service);
|
||||
|
||||
list.filter(e => e.includes(":"))
|
||||
perms
|
||||
.filter(e => e.includes(":"))
|
||||
.map(e => e.split(":"))
|
||||
.forEach(arr => {
|
||||
const col = (i, d) => {
|
||||
let key = arr[i];
|
||||
const col = (i: number, d: any[]) => {
|
||||
const key = arr[i];
|
||||
|
||||
let index = d.findIndex(e => e.label == key);
|
||||
const index = d.findIndex((e: any) => e.label == key);
|
||||
|
||||
if (index >= 0) {
|
||||
col(i + 1, d[index].children);
|
||||
} else {
|
||||
let isLast = i == arr.length - 1;
|
||||
const isLast = i == arr.length - 1;
|
||||
|
||||
d.push({
|
||||
label: key,
|
||||
@ -74,24 +86,36 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
col(0, options);
|
||||
col(0, list);
|
||||
});
|
||||
|
||||
this.options = options;
|
||||
},
|
||||
options.value = list;
|
||||
})();
|
||||
|
||||
mounted() {
|
||||
this.parse();
|
||||
// 监听值
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: string) => {
|
||||
value.value = val ? val.split(",").map((e: string) => e.split(":")) : [];
|
||||
},
|
||||
|
||||
methods: {
|
||||
parse() {
|
||||
this.newValue = this.value ? this.value.split(",").map(e => e.split(":")) : [];
|
||||
},
|
||||
|
||||
onChange(row) {
|
||||
this.$emit("input", row.map(e => e.join(":")).join(","));
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
value,
|
||||
options,
|
||||
onChange
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-menu-perms {
|
||||
:deep(.el-cascader) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,92 +0,0 @@
|
||||
import { mapGetters } from "vuex";
|
||||
import "./index.scss";
|
||||
|
||||
export default {
|
||||
name: "cl-menu-slider",
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: true
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["menuList", "menuCollapse", "browser", "app"])
|
||||
},
|
||||
|
||||
watch: {
|
||||
menuList() {
|
||||
this.refresh();
|
||||
},
|
||||
"app.conf.showAMenu"() {
|
||||
this.$store.commit("SET_MENU_LIST");
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toView(url) {
|
||||
if (url != this.$route.path) {
|
||||
this.$router.push(url);
|
||||
}
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.visible = false;
|
||||
|
||||
setTimeout(() => {
|
||||
this.visible = true;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const fn = list => {
|
||||
return list
|
||||
.filter(e => e.isShow)
|
||||
.map(e => {
|
||||
let html = null;
|
||||
|
||||
if (e.type == 0) {
|
||||
html = (
|
||||
<el-submenu
|
||||
popper-class="cl-slider-menu__submenu"
|
||||
index={String(e.id)}
|
||||
key={e.id}>
|
||||
<template slot="title">
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
<span slot="title">{e.name}</span>
|
||||
</template>
|
||||
{fn(e.children)}
|
||||
</el-submenu>
|
||||
);
|
||||
} else {
|
||||
html = (
|
||||
<el-menu-item index={e.path} key={e.path}>
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
<span slot="title">{e.name}</span>
|
||||
</el-menu-item>
|
||||
);
|
||||
}
|
||||
|
||||
return html;
|
||||
});
|
||||
};
|
||||
|
||||
let el = fn(this.menuList);
|
||||
|
||||
return (
|
||||
this.visible && (
|
||||
<div class="cl-slider-menu">
|
||||
<el-menu
|
||||
default-active={this.$route.path}
|
||||
background-color="transparent"
|
||||
collapse-transition={false}
|
||||
collapse={this.browser.isMini ? false : this.menuCollapse}
|
||||
on-select={this.toView}>
|
||||
{el}
|
||||
</el-menu>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
@ -30,6 +30,8 @@
|
||||
.icon-svg {
|
||||
font-size: 16px;
|
||||
margin: 0 15px 0 5px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
span {
|
||||
|
122
src/cool/modules/base/components/menu/slider/index.tsx
Normal file
122
src/cool/modules/base/components/menu/slider/index.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { useStore } from "vuex";
|
||||
import { computed, defineComponent, h, ref, watch } from "vue";
|
||||
import "./index.scss";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-menu-slider",
|
||||
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
|
||||
// 是否可见
|
||||
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) {
|
||||
return list
|
||||
.filter((e: any) => e.isShow)
|
||||
.map((e: any) => {
|
||||
let html = null;
|
||||
|
||||
if (e.type == 0) {
|
||||
html = h(
|
||||
<el-submenu></el-submenu>,
|
||||
{
|
||||
index: String(e.id),
|
||||
key: e.id
|
||||
},
|
||||
{
|
||||
title: () => {
|
||||
return !ctx.menuCollapse ? (
|
||||
<span>
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
<span>{e.name}</span>
|
||||
</span>
|
||||
) : (
|
||||
<icon-svg name={e.icon}></icon-svg>
|
||||
);
|
||||
},
|
||||
default() {
|
||||
return deepMenu(e.children);
|
||||
}
|
||||
}
|
||||
);
|
||||
} 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);
|
||||
|
||||
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,5 +1,5 @@
|
||||
<template>
|
||||
<div class="cl-menu-topbar">
|
||||
<div class="app-topbar-menu">
|
||||
<el-menu
|
||||
:default-active="index"
|
||||
mode="horizontal"
|
||||
@ -14,70 +14,80 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from "vuex";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { firstMenu } from "../../utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-menu-topbar",
|
||||
|
||||
data() {
|
||||
return {
|
||||
index: "0"
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
// 缓存
|
||||
const store = useStore();
|
||||
|
||||
computed: {
|
||||
list() {
|
||||
return this.$store.getters.menuGroup.filter(e => e.isShow);
|
||||
// 路由控制
|
||||
const router = useRouter();
|
||||
|
||||
// 当页路由
|
||||
const route = useRoute();
|
||||
|
||||
// 选中标识
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const deep = (e, i) => {
|
||||
onMounted(function() {
|
||||
// 设置默认
|
||||
function deep(e: any, i: number) {
|
||||
switch (e.type) {
|
||||
case 0:
|
||||
e.children.forEach(e => {
|
||||
e.children.forEach((e: any) => {
|
||||
deep(e, i);
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
if (this.$route.path.includes(e.path)) {
|
||||
this.index = String(i);
|
||||
this.SET_MENU_LIST(i);
|
||||
if (route.path.includes(e.path)) {
|
||||
index.value = String(i);
|
||||
store.commit("SET_MENU_LIST", i);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.list.forEach((e, i) => {
|
||||
list.value.forEach((e: any, i: number) => {
|
||||
deep(e, i);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
methods: {
|
||||
...mapMutations(["SET_MENU_LIST"]),
|
||||
|
||||
onSelect(index) {
|
||||
this.SET_MENU_LIST(index);
|
||||
|
||||
// 获取第一个菜单地址
|
||||
const url = firstMenu(this.list[index].children);
|
||||
this.$router.push(url);
|
||||
return {
|
||||
index,
|
||||
list,
|
||||
onSelect
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-menu-topbar {
|
||||
.app-topbar-menu {
|
||||
margin-right: 10px;
|
||||
|
||||
/deep/.el-menu {
|
||||
:deep(.el-menu) {
|
||||
height: 50px;
|
||||
background: transparent;
|
||||
border-bottom: 0;
|
||||
@ -101,7 +111,7 @@ export default {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
/deep/.icon-svg {
|
||||
:deep(.icon-svg) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +1,129 @@
|
||||
<template>
|
||||
<div class="cl-menu-tree">
|
||||
<el-popover
|
||||
ref="popover"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
width="500px"
|
||||
popper-class="popper-menu-tree"
|
||||
>
|
||||
<el-input size="small" v-model="filterValue">
|
||||
<i slot="prefix" class="el-input__icon el-icon-search"></i>
|
||||
<el-input size="small" v-model="keyword">
|
||||
<template #prefix>
|
||||
<i class="el-input__icon el-icon-search"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-tree
|
||||
ref="tree"
|
||||
ref="treeRef"
|
||||
node-key="menuId"
|
||||
:data="treeList"
|
||||
:props="props"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:default-expanded-keys="expandedKeys"
|
||||
:filter-node-method="filterNode"
|
||||
@current-change="currentChange"
|
||||
@current-change="onCurrentChange"
|
||||
>
|
||||
</el-tree>
|
||||
|
||||
<template #reference>
|
||||
<el-input v-model="name" readonly placeholder="请选择"></el-input>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-input v-model="name" v-popover:popover readonly placeholder="请选择"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, onMounted, ref, watch } from "vue";
|
||||
import { deepTree } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-menu-tree",
|
||||
|
||||
props: {
|
||||
value: [Number, String]
|
||||
modelValue: [Number, String]
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
filterValue: "",
|
||||
list: [],
|
||||
props: {
|
||||
label: "name",
|
||||
children: "children"
|
||||
},
|
||||
expandedKeys: []
|
||||
};
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
watch: {
|
||||
filterValue(val) {
|
||||
this.$refs.tree.filter(val);
|
||||
setup(props, { emit }) {
|
||||
// 请求服务
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
// 关键字
|
||||
const keyword = ref<string>("");
|
||||
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 展开值
|
||||
const expandedKeys = ref<any[]>([]);
|
||||
|
||||
// el-tree 组件
|
||||
const treeRef = ref<any>({});
|
||||
|
||||
// 绑定值回调
|
||||
function onCurrentChange({ id }: any) {
|
||||
emit("update:modelValue", id);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
name() {
|
||||
const item = this.list.find(e => e.id == this.value);
|
||||
return item ? item.name : "一级菜单";
|
||||
},
|
||||
// 刷新列表
|
||||
function refresh() {
|
||||
$service.system.menu.list().then((res: any) => {
|
||||
const _list = res.filter((e: any) => e.type != 2);
|
||||
|
||||
treeList() {
|
||||
return deepTree(this.list);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.menuList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
currentChange({ id }) {
|
||||
this.$emit("input", id);
|
||||
},
|
||||
|
||||
menuList() {
|
||||
this.$service.system.menu.list().then(res => {
|
||||
let list = res.filter(e => e.type != 2);
|
||||
|
||||
list.unshift({
|
||||
_list.unshift({
|
||||
name: "一级菜单",
|
||||
id: null
|
||||
});
|
||||
|
||||
this.list = list;
|
||||
list.value = _list;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
filterNode(value, data) {
|
||||
// 过滤节点
|
||||
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 {
|
||||
keyword,
|
||||
list,
|
||||
expandedKeys,
|
||||
treeRef,
|
||||
name,
|
||||
treeList,
|
||||
refresh,
|
||||
filterNode,
|
||||
onCurrentChange
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popper-menu-tree {
|
||||
width: 480px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-input {
|
||||
|
@ -4,20 +4,19 @@
|
||||
<i class="el-icon-arrow-left"></i>
|
||||
</div>
|
||||
|
||||
<div class="app-process__scroller" ref="scroller">
|
||||
<div class="app-process__scroller" :ref="setRefs('scroller')">
|
||||
<div
|
||||
class="app-process__item"
|
||||
v-for="(item, index) in processList"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:ref="`item-${index}`"
|
||||
:ref="setRefs(`item-${index}`)"
|
||||
:class="{ active: item.active }"
|
||||
:data-index="index"
|
||||
@click="onTap(item, index)"
|
||||
@click="onTap(item)"
|
||||
@contextmenu.stop.prevent="openCM($event, item)"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
|
||||
<i class="el-icon-close" v-if="index > 0" @click.stop="onDel(index)"></i>
|
||||
<i class="el-icon-close" v-if="index > 0" @mousedown.stop="onDel(index)"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -27,100 +26,136 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import { ContextMenu } from "cl-admin-crud";
|
||||
import { last } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { last } from "@/core/utils";
|
||||
import { useRefs } from "@/core";
|
||||
import { ContextMenu } from "@/crud";
|
||||
|
||||
export default {
|
||||
name: "cl-process",
|
||||
|
||||
computed: {
|
||||
...mapGetters(["processList"])
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
watch: {
|
||||
"$route.path"(val) {
|
||||
this.adScroll(this.processList.findIndex(e => e.value === val) || 0);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations(["ADD_PROCESS", "DEL_PROCESS", "SET_PROCESS"]),
|
||||
|
||||
onTap(item, index) {
|
||||
this.adScroll(index);
|
||||
this.$router.push(item.value);
|
||||
},
|
||||
|
||||
onDel(index) {
|
||||
this.DEL_PROCESS(index);
|
||||
this.toPath();
|
||||
},
|
||||
|
||||
openCM(e, item) {
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "关闭当前",
|
||||
hidden: this.$route.path !== item.value,
|
||||
callback: (_, done) => {
|
||||
this.onDel(this.processList.findIndex(e => e.value == item.value));
|
||||
done();
|
||||
this.toPath();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "关闭其他",
|
||||
callback: (_, done) => {
|
||||
this.SET_PROCESS(
|
||||
this.processList.filter(
|
||||
e => e.value == item.value || e.value == "/"
|
||||
)
|
||||
);
|
||||
done();
|
||||
this.toPath();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "关闭所有",
|
||||
callback: (_, done) => {
|
||||
this.SET_PROCESS(this.processList.filter(e => e.value == "/"));
|
||||
done();
|
||||
this.toPath();
|
||||
}
|
||||
}
|
||||
]
|
||||
// 参数配置
|
||||
const menu = reactive<any>({
|
||||
current: {}
|
||||
});
|
||||
},
|
||||
|
||||
toPath() {
|
||||
const active = this.processList.find(e => e.active);
|
||||
// 数据列表
|
||||
const list = computed(() => store.getters.processList);
|
||||
|
||||
// 跳转
|
||||
function toPath() {
|
||||
const active = list.value.find((e: any) => e.active);
|
||||
|
||||
if (!active) {
|
||||
const next = last(this.processList);
|
||||
this.$router.push(next ? next.value : "/");
|
||||
const next = last(list.value);
|
||||
router.push(next ? next.value : "/");
|
||||
}
|
||||
},
|
||||
|
||||
adScroll(index) {
|
||||
const el = this.$refs[`item-${index}`][0];
|
||||
|
||||
if (el) {
|
||||
this.scrollTo(el.offsetLeft + el.clientWidth - this.$refs["scroller"].clientWidth);
|
||||
}
|
||||
},
|
||||
|
||||
toScroll(f) {
|
||||
this.scrollTo(this.$refs["scroller"].scrollLeft + (f ? -100 : 100));
|
||||
},
|
||||
|
||||
scrollTo(left) {
|
||||
this.$refs["scroller"].scrollTo({
|
||||
// 移动到
|
||||
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>
|
||||
|
@ -6,14 +6,17 @@
|
||||
|
||||
<div class="scroller">
|
||||
<el-tree
|
||||
:data="list"
|
||||
:props="props"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
ref="treeRef"
|
||||
highlight-current
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
ref="tree"
|
||||
:data="list"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}"
|
||||
:default-checked-keys="checked"
|
||||
:filter-node-method="filterNode"
|
||||
@check-change="save"
|
||||
>
|
||||
</el-tree>
|
||||
@ -21,54 +24,50 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, onMounted, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { deepTree } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-role-perms",
|
||||
|
||||
props: {
|
||||
value: Array,
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
title: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
checked: [],
|
||||
keyword: "",
|
||||
props: {
|
||||
label: "name",
|
||||
children: "children"
|
||||
},
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
watch: {
|
||||
keyword(val) {
|
||||
this.$refs["tree"].filter(val);
|
||||
},
|
||||
// 树形列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
value(val) {
|
||||
this.refreshTree(val);
|
||||
}
|
||||
},
|
||||
// 已选列表
|
||||
const checked = ref<any[]>([]);
|
||||
|
||||
mounted() {
|
||||
this.refresh();
|
||||
},
|
||||
// 搜索关键字
|
||||
const keyword = ref<string>("");
|
||||
|
||||
methods: {
|
||||
refreshTree(val) {
|
||||
// 加载中
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// el-tree 组件
|
||||
const treeRef = ref<any>({});
|
||||
|
||||
// 刷新树
|
||||
function refreshTree(val: any[]) {
|
||||
if (!val) {
|
||||
this.checked = [];
|
||||
checked.value = [];
|
||||
}
|
||||
|
||||
let ids = [];
|
||||
const ids: any[] = [];
|
||||
|
||||
// 处理半选状态
|
||||
let fn = list => {
|
||||
const fn = (list: any[]) => {
|
||||
list.forEach(e => {
|
||||
if (e.children) {
|
||||
fn(e.children);
|
||||
@ -78,41 +77,63 @@ export default {
|
||||
});
|
||||
};
|
||||
|
||||
fn(this.list);
|
||||
fn(list.value);
|
||||
|
||||
this.checked = ids.filter(id => (val || []).find(e => e == id));
|
||||
},
|
||||
checked.value = ids.filter(id => (val || []).includes(id));
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.$service.system.menu
|
||||
// 刷新列表
|
||||
function refresh() {
|
||||
$service.system.menu
|
||||
.list()
|
||||
.then(res => {
|
||||
this.list = deepTree(res);
|
||||
|
||||
this.refreshTree(this.value);
|
||||
.then((res: any[]) => {
|
||||
list.value = deepTree(res);
|
||||
refreshTree(props.modelValue);
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
filterNode(val, data) {
|
||||
// 过滤节点
|
||||
function filterNode(val: string, data: any) {
|
||||
if (!val) return true;
|
||||
return data.name.includes(val);
|
||||
},
|
||||
|
||||
save() {
|
||||
const tree = this.$refs["tree"];
|
||||
}
|
||||
|
||||
// 保存
|
||||
function save() {
|
||||
// 选中的节点
|
||||
const checked = tree.getCheckedKeys();
|
||||
const checked = treeRef.value.getCheckedKeys();
|
||||
// 半选中的节点
|
||||
const halfChecked = tree.getHalfCheckedKeys();
|
||||
const halfChecked = treeRef.value.getHalfCheckedKeys();
|
||||
|
||||
this.$emit("input", [...checked, ...halfChecked]);
|
||||
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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-select v-model="newValue" v-bind="props" multiple @change="onChange">
|
||||
<el-select v-model="value" v-bind="props" multiple @change="onChange">
|
||||
<el-option
|
||||
v-for="(item, index) in list"
|
||||
:value="item.id"
|
||||
@ -9,47 +9,55 @@
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, onMounted, ref, watch } from "vue";
|
||||
import { isArray } from "@/core/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-role-select",
|
||||
|
||||
props: {
|
||||
value: [String, Number, Array],
|
||||
modelValue: [String, Number, Array],
|
||||
props: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
// 请求服务
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
// 数据列表
|
||||
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.system.role.list();
|
||||
});
|
||||
|
||||
return {
|
||||
list: [],
|
||||
newValue: undefined
|
||||
list,
|
||||
value,
|
||||
onChange
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
let arr = [];
|
||||
|
||||
if (!(val instanceof Array)) {
|
||||
arr = [val];
|
||||
} else {
|
||||
arr = val;
|
||||
}
|
||||
|
||||
this.newValue = arr.filter(Boolean);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.list = await this.$service.system.role.list();
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange(val) {
|
||||
this.$emit("input", val);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -15,24 +15,28 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRoute } from "vue-router";
|
||||
import _ from "lodash";
|
||||
import { isEmpty } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-route-nav",
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
|
||||
watch: {
|
||||
$route: {
|
||||
immediate: true,
|
||||
handler(route) {
|
||||
const deep = item => {
|
||||
// 数据列表
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
// 监听路由变化
|
||||
watch(
|
||||
() => route,
|
||||
(val: any) => {
|
||||
const deep = (item: any) => {
|
||||
if (route.path === "/") {
|
||||
return false;
|
||||
}
|
||||
@ -54,34 +58,42 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
this.list = _(this.menuGroup)
|
||||
list.value = _(store.getters.menuGroup)
|
||||
.map(deep)
|
||||
.filter(Boolean)
|
||||
.flattenDeep()
|
||||
.value();
|
||||
|
||||
if (this.list.length === 0) {
|
||||
this.list.push(route);
|
||||
}
|
||||
}
|
||||
if (isEmpty(list.value)) {
|
||||
list.value.push(val);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["menuGroup", "browser"]),
|
||||
|
||||
lastName() {
|
||||
return _.last(this.list).name;
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// 最后一个节点名称
|
||||
const lastName = computed(() => _.last(list.value).name);
|
||||
|
||||
const browser = computed(() => store.getters.browser);
|
||||
|
||||
return {
|
||||
list,
|
||||
lastName,
|
||||
browser
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-route-nav {
|
||||
white-space: nowrap;
|
||||
|
||||
/deep/.el-breadcrumb {
|
||||
:deep(.el-breadcrumb) {
|
||||
margin: 0 10px;
|
||||
|
||||
&__inner {
|
||||
font-size: 13px;
|
||||
padding: 0 10px;
|
||||
@ -91,7 +103,7 @@ export default {
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
@ -19,12 +19,11 @@
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBrowser } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { getBrowser } from "@/core/utils";
|
||||
|
||||
const { plat } = getBrowser();
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-scrollbar",
|
||||
|
||||
props: {
|
||||
@ -44,10 +43,16 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
width() {
|
||||
setup() {
|
||||
const { plat } = getBrowser();
|
||||
|
||||
const width = computed(() => {
|
||||
return `calc(100% - ${plat == "iphone" ? "10px" : "0px"})`;
|
||||
});
|
||||
|
||||
return {
|
||||
width
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -1,29 +1,16 @@
|
||||
import store from "@/store";
|
||||
|
||||
function change(el, binding) {
|
||||
el.style.display = checkPerm(binding.value) ? el.getAttribute("_display") : "none";
|
||||
}
|
||||
|
||||
function parse(value) {
|
||||
function parse(value: any) {
|
||||
const permission = store.getters.permission;
|
||||
|
||||
if (typeof value == "string") {
|
||||
return value ? permission.some(e => e.includes(value.replace(/\s/g, ""))) : false;
|
||||
return value ? permission.some((e: any) => e.includes(value.replace(/\s/g, ""))) : false;
|
||||
} else {
|
||||
return Boolean(value);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
inserted(el, binding) {
|
||||
el.setAttribute("_display", el.style.display || "");
|
||||
|
||||
change(el, binding);
|
||||
},
|
||||
update: change
|
||||
};
|
||||
|
||||
export const checkPerm = value => {
|
||||
function checkPerm(value: any) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
@ -34,9 +21,24 @@ export const checkPerm = value => {
|
||||
}
|
||||
|
||||
if (value.and) {
|
||||
return value.and.some(e => !parse(e)) ? false : true;
|
||||
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 {
|
||||
inserted(el: any, binding: any) {
|
||||
el.setAttribute("_display", el.style.display || "");
|
||||
|
||||
change(el, binding);
|
||||
},
|
||||
update: change
|
||||
};
|
||||
|
||||
export { checkPerm };
|
@ -1,17 +0,0 @@
|
||||
export default {
|
||||
default_avatar(url) {
|
||||
if (!url) {
|
||||
return require("../static/images/default-avatar.png");
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
default_name(name) {
|
||||
if (!name) {
|
||||
return "未命名";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
import components from "./components";
|
||||
import filters from "./filters";
|
||||
import pages from "./pages";
|
||||
import views from "./views";
|
||||
import store from "./store";
|
||||
@ -9,4 +8,4 @@ import { iconList } from "./common";
|
||||
import "./static/css/index.scss";
|
||||
|
||||
export { iconList, checkPerm };
|
||||
export default { components, filters, pages, views, store, service, directives };
|
||||
export default { components, pages, views, store, service, directives };
|
@ -15,11 +15,11 @@
|
||||
<el-button round @click="navTo">跳转</el-button>
|
||||
</div>
|
||||
|
||||
<div class="link">
|
||||
<el-link class="to-home" @click="home">回到首页</el-link>
|
||||
<el-link class="to-back" @click="back">返回上一页</el-link>
|
||||
<el-link class="to-login" @click="reLogin">重新登录</el-link>
|
||||
</div>
|
||||
<ul class="link">
|
||||
<li @click="home">回到首页</li>
|
||||
<li @click="back">返回上一页</li>
|
||||
<li @click="reLogin">重新登录</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
@ -32,53 +32,65 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { href } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { useStore } from "vuex";
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { href } from "@/core/utils";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
props: {
|
||||
code: Number,
|
||||
desc: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
url: "",
|
||||
isLogout: false
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
computed: {
|
||||
...mapGetters(["routes", "token"])
|
||||
},
|
||||
const url = ref<string>("");
|
||||
const isLogout = ref<boolean>(false);
|
||||
|
||||
methods: {
|
||||
navTo() {
|
||||
this.$router.push(this.url);
|
||||
},
|
||||
const routes = computed(() => store.getters.routes);
|
||||
const token = computed(() => store.getters.token);
|
||||
|
||||
toLogin() {
|
||||
this.$router.push("/login");
|
||||
},
|
||||
function navTo() {
|
||||
router.push(url.value);
|
||||
}
|
||||
|
||||
reLogin() {
|
||||
this.isLogout = true;
|
||||
function toLogin() {
|
||||
router.push("/login");
|
||||
}
|
||||
|
||||
this.$store.dispatch("userLogout").done(() => {
|
||||
function reLogin() {
|
||||
isLogout.value = true;
|
||||
|
||||
store.dispatch("userLogout").done(() => {
|
||||
href("/login");
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
back() {
|
||||
function back() {
|
||||
history.back();
|
||||
},
|
||||
}
|
||||
|
||||
home() {
|
||||
this.$router.push("/");
|
||||
function home() {
|
||||
router.push("/");
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
isLogout,
|
||||
routes,
|
||||
token,
|
||||
navTo,
|
||||
toLogin,
|
||||
reLogin,
|
||||
back,
|
||||
home
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -129,16 +141,19 @@ export default {
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
|
||||
a {
|
||||
li {
|
||||
font-weight: 500;
|
||||
transition: all 0.5s;
|
||||
-webkit-transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 0 15px;
|
||||
padding-bottom: 2px;
|
||||
margin: 0 20px;
|
||||
list-style: none;
|
||||
|
||||
&:hover {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
path: "/403",
|
||||
component: () => import("./error-page/403")
|
||||
},
|
||||
{
|
||||
path: "/404",
|
||||
component: () => import("./error-page/404")
|
||||
},
|
||||
{
|
||||
path: "/500",
|
||||
component: () => import("./error-page/500")
|
||||
},
|
||||
{
|
||||
path: "/502",
|
||||
component: () => import("./error-page/502")
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: () => import("./login")
|
||||
}
|
||||
];
|
22
src/cool/modules/base/pages/index.ts
Normal file
22
src/cool/modules/base/pages/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export default [
|
||||
{
|
||||
path: "/403",
|
||||
component: () => import("./error-page/403.vue")
|
||||
},
|
||||
{
|
||||
path: "/404",
|
||||
component: () => import("./error-page/404.vue")
|
||||
},
|
||||
{
|
||||
path: "/500",
|
||||
component: () => import("./error-page/500.vue")
|
||||
},
|
||||
{
|
||||
path: "/502",
|
||||
component: () => import("./error-page/502.vue")
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: () => import("./login/index.vue")
|
||||
}
|
||||
];
|
@ -1,55 +1,60 @@
|
||||
<template>
|
||||
<div class="common-captcha" @click="refresh">
|
||||
<div class="login-captcha" @click="refresh">
|
||||
<div class="svg" v-html="svg" v-if="svg"></div>
|
||||
|
||||
<img class="base64" :src="base64" alt="" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
svg: "",
|
||||
base64: ""
|
||||
};
|
||||
},
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
mounted() {
|
||||
this.refresh();
|
||||
},
|
||||
export default defineComponent({
|
||||
emits: ["update:modelValue", "change"],
|
||||
|
||||
methods: {
|
||||
refresh() {
|
||||
this.$service.open
|
||||
setup(_, { emit }) {
|
||||
const base64 = ref("");
|
||||
const svg = ref("");
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
const refresh = () => {
|
||||
$service.open
|
||||
.captcha({
|
||||
height: 36,
|
||||
width: 110
|
||||
})
|
||||
.then(({ captchaId, data }) => {
|
||||
.then(({ captchaId, data }: any) => {
|
||||
if (data.includes(";base64,")) {
|
||||
this.base64 = data;
|
||||
base64.value = data;
|
||||
} else {
|
||||
this.svg = data;
|
||||
svg.value = data;
|
||||
}
|
||||
|
||||
this.$emit("input", captchaId);
|
||||
this.$emit("change", {
|
||||
base64: this.base64,
|
||||
svg: this.svg,
|
||||
emit("update:modelValue", captchaId);
|
||||
emit("change", {
|
||||
base64,
|
||||
svg,
|
||||
captchaId
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
refresh();
|
||||
|
||||
return {
|
||||
base64,
|
||||
svg,
|
||||
refresh
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.common-captcha {
|
||||
.login-captcha {
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<img class="logo" src="../../static/images/logo.png" alt="" />
|
||||
<p class="desc">COOL ADMIN是一款快速开发后台权限管理系统</p>
|
||||
|
||||
<el-form ref="form" class="form" size="medium" :disabled="saving">
|
||||
<el-form class="form" size="medium" :disabled="saving">
|
||||
<el-form-item label="用户名">
|
||||
<el-input
|
||||
placeholder="请输入用户名"
|
||||
@ -30,91 +30,105 @@
|
||||
maxlength="4"
|
||||
v-model="form.verifyCode"
|
||||
auto-complete="off"
|
||||
@keyup.enter.native="next"
|
||||
@keyup.enter="toLogin"
|
||||
></el-input>
|
||||
|
||||
<captcha
|
||||
ref="captcha"
|
||||
:ref="setRefs('captcha')"
|
||||
class="value"
|
||||
v-model="form.captchaId"
|
||||
@change="captchaChange"
|
||||
@change="
|
||||
() => {
|
||||
form.verifyCode = '';
|
||||
}
|
||||
"
|
||||
></captcha>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-button round size="mini" class="submit-btn" @click="next" :loading="saving"
|
||||
<el-button round size="mini" class="submit-btn" @click="toLogin" :loading="saving"
|
||||
>登录</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Captcha from "./components/captcha";
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
import Captcha from "./components/captcha.vue";
|
||||
import { useRefs } from "@/core";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Captcha
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
const saving = ref<boolean>(false);
|
||||
|
||||
// 登录表单数据
|
||||
const form = reactive({
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
captchaId: "",
|
||||
verifyCode: ""
|
||||
},
|
||||
saving: false
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
methods: {
|
||||
captchaChange() {
|
||||
this.form.verifyCode = "";
|
||||
},
|
||||
|
||||
async next() {
|
||||
const { username, password, verifyCode } = this.form;
|
||||
|
||||
if (!username) {
|
||||
return this.$message.warning("用户名不能为空");
|
||||
// 登录
|
||||
async function toLogin() {
|
||||
if (!form.username) {
|
||||
return ElMessage.warning("用户名不能为空");
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return this.$message.warning("密码不能为空");
|
||||
if (!form.password) {
|
||||
return ElMessage.warning("密码不能为空");
|
||||
}
|
||||
|
||||
if (!verifyCode) {
|
||||
return this.$message.warning("图片验证码不能为空");
|
||||
if (!form.verifyCode) {
|
||||
return ElMessage.warning("图片验证码不能为空");
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
// 登录
|
||||
await this.$store.dispatch("userLogin", this.form);
|
||||
await store.dispatch("userLogin", form);
|
||||
|
||||
// 用户信息
|
||||
await this.$store.dispatch("userInfo");
|
||||
await store.dispatch("userInfo");
|
||||
|
||||
// 权限菜单
|
||||
let [first] = await this.$store.dispatch("permMenu");
|
||||
const [first] = await store.dispatch("permMenu");
|
||||
|
||||
if (!first) {
|
||||
this.$message.error("该账号没有权限");
|
||||
ElMessage.error("该账号没有权限");
|
||||
} else {
|
||||
this.$router.push("/");
|
||||
router.push("/");
|
||||
}
|
||||
} catch (err) {
|
||||
this.$message.error(err);
|
||||
this.$refs.captcha.refresh();
|
||||
ElMessage.error(err);
|
||||
refs.value.captcha.refresh();
|
||||
}
|
||||
|
||||
this.saving = false;
|
||||
saving.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
form,
|
||||
saving,
|
||||
toLogin,
|
||||
setRefs
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -147,7 +161,7 @@ export default {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/deep/.el-form {
|
||||
:deep(.el-form) {
|
||||
width: 300px;
|
||||
border-radius: 3px;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
import { BaseService, Service } from "@/core";
|
||||
|
||||
@Service("base/comm")
|
||||
class Common extends BaseService {
|
||||
@ -17,7 +17,7 @@ class Common extends BaseService {
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
upload(params) {
|
||||
upload(params: any) {
|
||||
return this.request({
|
||||
url: "/upload",
|
||||
method: "POST",
|
||||
@ -54,7 +54,7 @@ class Common extends BaseService {
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
userUpdate(params) {
|
||||
userUpdate(params: any) {
|
||||
return this.request({
|
||||
url: "/personUpdate",
|
||||
method: "POST",
|
@ -4,6 +4,7 @@ import SysUser from "./system/user";
|
||||
import SysMenu from "./system/menu";
|
||||
import SysRole from "./system/role";
|
||||
import SysDept from "./system/dept";
|
||||
import SysTask from "./system/task";
|
||||
import SysParam from "./system/param";
|
||||
import SysLog from "./system/log";
|
||||
import PluginInfo from "./plugin/info";
|
||||
@ -16,6 +17,7 @@ export default {
|
||||
menu: new SysMenu(),
|
||||
role: new SysRole(),
|
||||
dept: new SysDept(),
|
||||
task: new SysTask(),
|
||||
param: new SysParam(),
|
||||
log: new SysLog()
|
||||
},
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
import { BaseService, Service } from "@/core";
|
||||
|
||||
@Service("base/open")
|
||||
class Open extends BaseService {
|
||||
@ -9,7 +9,7 @@ class Open extends BaseService {
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
userLogin({ username, password, captchaId, verifyCode }) {
|
||||
userLogin({ username, password, captchaId, verifyCode }: any) {
|
||||
return this.request({
|
||||
url: "/login",
|
||||
method: "POST",
|
||||
@ -29,7 +29,7 @@ class Open extends BaseService {
|
||||
* @returns
|
||||
* @memberof CommonService
|
||||
*/
|
||||
captcha({ height, width }) {
|
||||
captcha({ height, width }: any) {
|
||||
return this.request({
|
||||
url: "/captcha",
|
||||
params: {
|
||||
@ -43,7 +43,7 @@ class Open extends BaseService {
|
||||
* 刷新 token
|
||||
* @param {string} token
|
||||
*/
|
||||
refreshToken(token) {
|
||||
refreshToken(token: string) {
|
||||
return this.request({
|
||||
url: "/refreshToken",
|
||||
params: {
|
@ -1,9 +1,9 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service("base/plugin/info")
|
||||
class PluginInfo extends BaseService {
|
||||
@Permission("config")
|
||||
config(data) {
|
||||
config(data: any) {
|
||||
return this.request({
|
||||
url: "/config",
|
||||
method: "POST",
|
||||
@ -12,7 +12,7 @@ class PluginInfo extends BaseService {
|
||||
}
|
||||
|
||||
@Permission("getConfig")
|
||||
getConfig(params) {
|
||||
getConfig(params: any) {
|
||||
return this.request({
|
||||
url: "/getConfig",
|
||||
params
|
||||
@ -20,7 +20,7 @@ class PluginInfo extends BaseService {
|
||||
}
|
||||
|
||||
@Permission("enable")
|
||||
enable(data) {
|
||||
enable(data: any) {
|
||||
return this.request({
|
||||
url: "/enable",
|
||||
method: "POST",
|
@ -1,9 +1,9 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service("base/sys/department")
|
||||
class SysDepartment extends BaseService {
|
||||
@Permission("order")
|
||||
order(data) {
|
||||
order(data: any) {
|
||||
return this.request({
|
||||
url: "/order",
|
||||
method: "POST",
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service("base/sys/log")
|
||||
class SysLog extends BaseService {
|
||||
@ -18,7 +18,7 @@ class SysLog extends BaseService {
|
||||
}
|
||||
|
||||
@Permission("setKeep")
|
||||
setKeep(value) {
|
||||
setKeep(value: any) {
|
||||
return this.request({
|
||||
url: "/setKeep",
|
||||
method: "POST",
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
import { BaseService, Service } from "@/core";
|
||||
|
||||
@Service("base/sys/menu")
|
||||
class SysMenu extends BaseService {}
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
import { BaseService, Service } from "@/core";
|
||||
|
||||
@Service("base/sys/param")
|
||||
class SysParam extends BaseService {}
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service } from "cl-admin";
|
||||
import { BaseService, Service } from "@/core";
|
||||
|
||||
@Service("base/sys/role")
|
||||
class SysRole extends BaseService {}
|
41
src/cool/modules/base/service/system/task.ts
Normal file
41
src/cool/modules/base/service/system/task.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service("base/sys/task")
|
||||
class SysTask extends BaseService {
|
||||
@Permission("stop")
|
||||
stop(data: any) {
|
||||
return this.request({
|
||||
url: "/stop",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("start")
|
||||
start(data: any) {
|
||||
return this.request({
|
||||
url: "/start",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("once")
|
||||
once(data: any) {
|
||||
return this.request({
|
||||
url: "/once",
|
||||
method: "POST",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
@Permission("log")
|
||||
log(params: any) {
|
||||
return this.request({
|
||||
url: "/log",
|
||||
params
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SysTask;
|
@ -1,9 +1,9 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service("base/sys/user")
|
||||
class SysUser extends BaseService {
|
||||
@Permission("move")
|
||||
move(data) {
|
||||
move(data: any) {
|
||||
return this.request({
|
||||
url: "/move",
|
||||
method: "POST",
|
@ -1,50 +0,0 @@
|
||||
import { app } from "@/config/env";
|
||||
import { deepMerge, getBrowser } from "cl-admin/utils";
|
||||
import store from "store";
|
||||
|
||||
const browser = getBrowser();
|
||||
|
||||
export default {
|
||||
state: {
|
||||
info: {
|
||||
...app
|
||||
},
|
||||
browser,
|
||||
collapse: browser.isMini ? true : false
|
||||
},
|
||||
getters: {
|
||||
// 应用配置
|
||||
app: state => state.info,
|
||||
// 浏览器信息
|
||||
browser: state => state.browser,
|
||||
// 左侧菜单是否收起
|
||||
menuCollapse: state => state.collapse
|
||||
},
|
||||
actions: {
|
||||
appLoad({ getters, dispatch }) {
|
||||
if (getters.token) {
|
||||
// 读取菜单权限
|
||||
dispatch("permMenu");
|
||||
// 获取用户信息
|
||||
dispatch("userInfo");
|
||||
}
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
// 设置浏览器信息
|
||||
SET_BROWSER(state) {
|
||||
state.browser = getBrowser();
|
||||
},
|
||||
|
||||
// 收起左侧菜单
|
||||
COLLAPSE_MENU(state, val = false) {
|
||||
state.collapse = val;
|
||||
},
|
||||
|
||||
// 更新应用配置
|
||||
UPDATE_APP(state, val) {
|
||||
deepMerge(state.info, val);
|
||||
store.set("__app__", state.info);
|
||||
}
|
||||
}
|
||||
};
|
58
src/cool/modules/base/store/app.ts
Normal file
58
src/cool/modules/base/store/app.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import store from "store";
|
||||
import { deepMerge, getBrowser } from "@/core/utils";
|
||||
import { app } from "@/config/env";
|
||||
|
||||
const browser = getBrowser();
|
||||
|
||||
const state = {
|
||||
info: {
|
||||
...app
|
||||
},
|
||||
browser,
|
||||
collapse: browser.isMini ? true : false
|
||||
};
|
||||
|
||||
const getters = {
|
||||
// 应用配置
|
||||
app: (state: any) => state.info,
|
||||
// 浏览器信息
|
||||
browser: (state: any) => state.browser,
|
||||
// 左侧菜单是否收起
|
||||
menuCollapse: (state: any) => state.collapse
|
||||
};
|
||||
|
||||
const actions = {
|
||||
appLoad({ getters, dispatch }: any) {
|
||||
if (getters.token) {
|
||||
// 读取菜单权限
|
||||
dispatch("permMenu");
|
||||
// 获取用户信息
|
||||
dispatch("userInfo");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
// 设置浏览器信息
|
||||
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,143 +0,0 @@
|
||||
import { Message } from "element-ui";
|
||||
import { deepTree, revDeepTree, isArray, isEmpty } from "cl-admin/utils";
|
||||
import { revisePath } from "../utils";
|
||||
import router from "@/router";
|
||||
import { menuList } from "@/config/env";
|
||||
import store from "store";
|
||||
|
||||
export default {
|
||||
state: {
|
||||
// 视图路由,type=1
|
||||
routes: store.get("viewRoutes") || [],
|
||||
// 树形菜单
|
||||
group: store.get("menuGroup") || [],
|
||||
// showAMenu 模式下,顶级菜单的序号
|
||||
index: 0,
|
||||
// 左侧菜单
|
||||
menu: [],
|
||||
// 权限列表
|
||||
permission: []
|
||||
},
|
||||
getters: {
|
||||
// 树形菜单列表
|
||||
menuGroup: state => state.group,
|
||||
// 左侧菜单
|
||||
menuList: state => state.menu,
|
||||
// 视图路由
|
||||
routes: state => state.routes,
|
||||
// 权限列表
|
||||
permission: state => state.permission
|
||||
},
|
||||
actions: {
|
||||
// 设置菜单、权限
|
||||
permMenu({ commit, state, getters }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const next = res => {
|
||||
if (!isArray(res.menus)) {
|
||||
res.menus = [];
|
||||
}
|
||||
|
||||
if (!isArray(res.perms)) {
|
||||
res.perms = [];
|
||||
}
|
||||
|
||||
const routes = res.menus
|
||||
.filter(e => e.type != 2)
|
||||
.map(e => {
|
||||
return {
|
||||
moduleName: e.moduleName,
|
||||
id: e.id,
|
||||
parentId: e.parentId,
|
||||
path: revisePath(e.router || 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 => e.type == 1)
|
||||
);
|
||||
// 设置菜单
|
||||
commit("SET_MENU_LIST", state.index);
|
||||
|
||||
resolve(menuGroup);
|
||||
};
|
||||
|
||||
// 监测自定义菜单
|
||||
if (!getters.app.conf.customMenu) {
|
||||
this.$service.common
|
||||
.permMenu()
|
||||
.then(res => {
|
||||
next(res);
|
||||
})
|
||||
.catch(err => {
|
||||
Message.error("菜单加载异常");
|
||||
console.error(err);
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
next({
|
||||
menus: revDeepTree(menuList)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
// 设置树形菜单列表
|
||||
SET_MENU_GROUP(state, list) {
|
||||
state.group = list;
|
||||
store.set("menuGroup", list);
|
||||
},
|
||||
|
||||
// 设置视图路由
|
||||
SET_VIEW_ROUTES(state, list) {
|
||||
router.$plugin.addViews(list);
|
||||
|
||||
state.routes = list;
|
||||
store.set("viewRoutes", list);
|
||||
},
|
||||
|
||||
// 设置左侧菜单
|
||||
SET_MENU_LIST(state, index) {
|
||||
const { showAMenu } = this.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, list) {
|
||||
state.permission = list;
|
||||
store.set("permission", list);
|
||||
}
|
||||
}
|
||||
};
|
152
src/cool/modules/base/store/menu.ts
Normal file
152
src/cool/modules/base/store/menu.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
import storage from "store";
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
import { deepTree, revDeepTree, isArray, isEmpty } from "@/core/utils";
|
||||
import { menuList } from "@/config/env";
|
||||
import { revisePath } from "../utils";
|
||||
import { MenuItem } from "../types";
|
||||
|
||||
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.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);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
export default {
|
||||
state: {
|
||||
info: {},
|
||||
list: []
|
||||
},
|
||||
|
||||
getters: {
|
||||
// 模块信息
|
||||
modules: state => state.info,
|
||||
// 模块列表
|
||||
moduleList: state => state.list
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_MODULE(state, list) {
|
||||
let d = {};
|
||||
|
||||
list.forEach(e => {
|
||||
d[e.name] = e;
|
||||
});
|
||||
|
||||
state.list = list;
|
||||
state.info = d;
|
||||
}
|
||||
}
|
||||
};
|
30
src/cool/modules/base/store/module.ts
Normal file
30
src/cool/modules/base/store/module.ts
Normal file
@ -0,0 +1,30 @@
|
||||
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,57 +0,0 @@
|
||||
const fMenu = {
|
||||
label: "首页",
|
||||
value: "/",
|
||||
active: true
|
||||
};
|
||||
|
||||
export default {
|
||||
state: {
|
||||
list: [fMenu]
|
||||
},
|
||||
getters: {
|
||||
// 页面进程列表
|
||||
processList: state => state.list
|
||||
},
|
||||
mutations: {
|
||||
ADD_PROCESS(state, item) {
|
||||
const index = state.list.findIndex(
|
||||
e => e.value.split("?")[0] === item.value.split("?")[0]
|
||||
);
|
||||
|
||||
state.list.map(e => {
|
||||
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, index) {
|
||||
if (index != 0) {
|
||||
state.list.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
SET_PROCESS(state, list) {
|
||||
state.list = list;
|
||||
},
|
||||
|
||||
RESET_PROCESS(state) {
|
||||
state.list = [fMenu];
|
||||
}
|
||||
}
|
||||
};
|
66
src/cool/modules/base/store/process.ts
Normal file
66
src/cool/modules/base/store/process.ts
Normal file
@ -0,0 +1,66 @@
|
||||
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,102 +0,0 @@
|
||||
import { storage, href } from "cl-admin/utils";
|
||||
|
||||
// 用户信息
|
||||
let info = storage.get("userInfo") || {};
|
||||
// 授权标识
|
||||
let token = storage.get("token") || null;
|
||||
|
||||
export default {
|
||||
state: {
|
||||
token,
|
||||
info
|
||||
},
|
||||
getters: {
|
||||
userInfo: state => state.info,
|
||||
token: state => state.token
|
||||
},
|
||||
actions: {
|
||||
// 用户登录
|
||||
userLogin({ commit }, form) {
|
||||
return this.$service.open.userLogin(form).then(res => {
|
||||
commit("SET_TOKEN", res);
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
// 用户退出
|
||||
userLogout({ dispatch }) {
|
||||
return new Promise(resolve => {
|
||||
this.$service.common.userLogout().done(() => {
|
||||
dispatch("userRemove").then(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 用户信息
|
||||
userInfo({ commit }) {
|
||||
return this.$service.common.userInfo().then(res => {
|
||||
commit("SET_USERINFO", res);
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
// 用户移除
|
||||
userRemove({ commit }) {
|
||||
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 }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$service.open
|
||||
.refreshToken(storage.get("refreshToken"))
|
||||
.then(res => {
|
||||
commit("SET_TOKEN", res);
|
||||
resolve(res.token);
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch("userRemove");
|
||||
href("/login");
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
// 设置用户信息
|
||||
SET_USERINFO(state, val) {
|
||||
state.info = val;
|
||||
storage.set("userInfo", val);
|
||||
},
|
||||
|
||||
// 设置授权标识
|
||||
SET_TOKEN(state, { token, expire, refreshToken, refreshExpire }) {
|
||||
// 请求的唯一标识
|
||||
state.token = token;
|
||||
storage.set("token", token, expire);
|
||||
|
||||
// 刷新 token 的唯一标识
|
||||
storage.set("refreshToken", refreshToken, refreshExpire);
|
||||
},
|
||||
|
||||
// 移除授权标识
|
||||
CLEAR_TOKEN(state) {
|
||||
state.token = null;
|
||||
storage.remove("token");
|
||||
storage.remove("refreshToken");
|
||||
},
|
||||
|
||||
// 移除用户信息
|
||||
CLEAR_USER(state) {
|
||||
state.info = {};
|
||||
storage.remove("userInfo");
|
||||
}
|
||||
}
|
||||
};
|
109
src/cool/modules/base/store/user.ts
Normal file
109
src/cool/modules/base/store/user.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { storage, href } from "@/core/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.open.userLogin(form).then((res: Token) => {
|
||||
commit("SET_TOKEN", res);
|
||||
return res;
|
||||
});
|
||||
},
|
||||
|
||||
// 用户退出
|
||||
userLogout({ dispatch }: any): Promise<any> {
|
||||
return new Promise(resolve => {
|
||||
store.$service.common.userLogout().done(() => {
|
||||
dispatch("userRemove").then(() => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 用户信息
|
||||
userInfo({ commit }: any): Promise<any> {
|
||||
return store.$service.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.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
Normal file
31
src/cool/modules/base/types/index.d.ts
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
export interface Token {
|
||||
expire: number;
|
||||
refreshExpire: number;
|
||||
refreshToken: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export enum MenuType {
|
||||
"目录" = 0,
|
||||
"菜单" = 1,
|
||||
"权限" = 2
|
||||
}
|
||||
|
||||
export 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,4 +1,4 @@
|
||||
export const revisePath = path => {
|
||||
export const revisePath = (path: string) => {
|
||||
if (!path) {
|
||||
return "";
|
||||
}
|
||||
@ -10,11 +10,11 @@ export const revisePath = path => {
|
||||
}
|
||||
};
|
||||
|
||||
export function firstMenu(list) {
|
||||
export function firstMenu(list: Array<any>) {
|
||||
let path = "";
|
||||
|
||||
const fn = arr => {
|
||||
arr.forEach(e => {
|
||||
const fn = (arr: Array<any>) => {
|
||||
arr.forEach((e: any) => {
|
||||
if (e.type == 1) {
|
||||
if (!path) {
|
||||
path = e.path;
|
||||
@ -30,7 +30,7 @@ export function firstMenu(list) {
|
||||
return path || "/404";
|
||||
}
|
||||
|
||||
export function createLink(url, id) {
|
||||
export function createLink(url: string, id?: string) {
|
||||
const link = document.createElement("link");
|
||||
link.href = url;
|
||||
link.type = "text/css";
|
||||
@ -38,8 +38,9 @@ export function createLink(url, id) {
|
||||
if (id) {
|
||||
link.id = id;
|
||||
}
|
||||
|
||||
document
|
||||
.getElementsByTagName("head")
|
||||
.item(0)
|
||||
.appendChild(link);
|
||||
?.item(0)
|
||||
?.appendChild(link);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
path: "/my/info",
|
||||
component: () => import("./info"),
|
||||
meta: {
|
||||
label: "个人中心",
|
||||
keepAlive: true
|
||||
}
|
||||
}
|
||||
];
|
7
src/cool/modules/base/views/index.ts
Normal file
7
src/cool/modules/base/views/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default [
|
||||
{
|
||||
label: "个人中心",
|
||||
path: "/my/info",
|
||||
component: () => import("./info.vue")
|
||||
}
|
||||
];
|
@ -22,51 +22,56 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
<script lang="ts">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, inject, reactive, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
saving: false
|
||||
};
|
||||
},
|
||||
export default defineComponent({
|
||||
name: "sys-info",
|
||||
|
||||
computed: {
|
||||
...mapGetters(["userInfo"])
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
mounted() {
|
||||
this.form = this.userInfo;
|
||||
},
|
||||
// 表单数据
|
||||
const form = reactive<any>(store.getters.userInfo);
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.saving = true;
|
||||
// 保存状态
|
||||
const saving = ref<boolean>(false);
|
||||
|
||||
const { headImg, nickName, password } = this.form;
|
||||
// 保存
|
||||
function save() {
|
||||
const { headImg, nickName, password } = form;
|
||||
|
||||
this.$service.common
|
||||
saving.value = true;
|
||||
|
||||
$service.common
|
||||
.userUpdate({
|
||||
headImg,
|
||||
nickName,
|
||||
password
|
||||
})
|
||||
.then(() => {
|
||||
this.form.password = "";
|
||||
this.$message.success("修改成功");
|
||||
this.$store.dispatch("userInfo");
|
||||
form.password = "";
|
||||
ElMessage.success("修改成功");
|
||||
store.dispatch("userInfo");
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
})
|
||||
.done(() => {
|
||||
this.saving = false;
|
||||
saving.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
saving,
|
||||
save
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<cl-crud ref="crud" @load="onLoad">
|
||||
<cl-crud :ref="setRefs('crud')" @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn></cl-refresh-btn>
|
||||
|
||||
@ -38,125 +38,124 @@
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, reactive, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useRefs } from "@/core";
|
||||
import { CrudLoad, Table } from "@/crud/types";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
day: 1,
|
||||
table: {
|
||||
export default defineComponent({
|
||||
name: "sys-log",
|
||||
|
||||
setup() {
|
||||
const $service = inject<any>("$service");
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
// 天数
|
||||
const day = ref<number>(1);
|
||||
|
||||
// cl-table 配置
|
||||
const table = reactive<Table>({
|
||||
"context-menu": ["refresh"],
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
order: "descending"
|
||||
}
|
||||
},
|
||||
"context-menu": [
|
||||
"refresh",
|
||||
{
|
||||
label: "清空",
|
||||
callback: (_, done) => {
|
||||
this.clear();
|
||||
done();
|
||||
}
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
type: "index",
|
||||
label: "#",
|
||||
align: "center",
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
prop: "userId",
|
||||
label: "用户ID",
|
||||
align: "center"
|
||||
label: "用户ID"
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "昵称",
|
||||
align: "center",
|
||||
minWidth: "150"
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "action",
|
||||
label: "请求地址",
|
||||
align: "center",
|
||||
minWidth: "200",
|
||||
"show-overflow-tooltip": true
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: "params",
|
||||
label: "参数",
|
||||
align: "center",
|
||||
minWidth: "200",
|
||||
"show-overflow-tooltip": true
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: "ip",
|
||||
label: "ip",
|
||||
minWidth: "180",
|
||||
align: "center"
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
prop: "ipAddr",
|
||||
label: "ip地址",
|
||||
minWidth: "150",
|
||||
align: "center"
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "createTime",
|
||||
label: "创建时间",
|
||||
minWidth: "150",
|
||||
align: "center",
|
||||
minWidth: 150,
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["permission"])
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$service.system.log.getKeep().then(res => {
|
||||
this.day = res;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
ctx.service(this.$service.system.log).done();
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service($service.system.log).done();
|
||||
app.refresh();
|
||||
},
|
||||
}
|
||||
|
||||
saveDay() {
|
||||
this.$service.system.log.setKeep(this.day).then(() => {
|
||||
this.$message.success("保存成功");
|
||||
// 保存天数
|
||||
function saveDay() {
|
||||
$service.system.log.setKeep(day.value).then(() => {
|
||||
ElMessage.success("保存成功");
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.$confirm("是否要清空日志", "提示", {
|
||||
// 清空日志
|
||||
function clear() {
|
||||
ElMessageBox.confirm("是否要清空日志", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.$service.system.log
|
||||
$service.system.log
|
||||
.clear()
|
||||
.then(() => {
|
||||
this.$message.success("清空成功");
|
||||
this.$refs["crud"].refresh();
|
||||
ElMessage.success("清空成功");
|
||||
refs.value.crud.refresh();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
// 获取天数
|
||||
$service.system.log.getKeep().then((res: number) => {
|
||||
day.value = Number(res);
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
day,
|
||||
table,
|
||||
setRefs,
|
||||
onLoad,
|
||||
saveDay,
|
||||
clear
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<cl-crud ref="crud" @load="onLoad" :on-refresh="onRefresh">
|
||||
<cl-crud :ref="setRefs('crud')" :on-refresh="onRefresh" @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn />
|
||||
<cl-add-btn />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table ref="table" v-bind="table" @row-click="onRowClick">
|
||||
<cl-table :ref="setRefs('table')" v-bind="table" @row-click="onRowClick">
|
||||
<!-- 名称 -->
|
||||
<template #column-name="{ scope }">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
@ -66,40 +66,102 @@
|
||||
</cl-table>
|
||||
</el-row>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-flex1></cl-flex1>
|
||||
<cl-pagination :props="{ layout: 'total' }"></cl-pagination>
|
||||
</el-row>
|
||||
|
||||
<!-- 编辑 -->
|
||||
<cl-upsert ref="upsert" v-bind="upsert"></cl-upsert>
|
||||
<cl-upsert v-bind="upsert"></cl-upsert>
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deepTree } from "cl-admin/utils";
|
||||
<script lang="ts">
|
||||
import { useRefs } from "@/core";
|
||||
import { deepTree } from "@/core/utils";
|
||||
import { useRouter } from "vue-router";
|
||||
import { defineComponent, inject, reactive } from "vue";
|
||||
import { CrudLoad, Table, Upsert, RefreshOp } from "@/crud/types";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
table: {
|
||||
export default defineComponent({
|
||||
name: "sys-menu",
|
||||
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const { refs, setRefs } = useRefs();
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service($service.system.menu).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
// 刷新监听
|
||||
function onRefresh(_: any, { render }: RefreshOp) {
|
||||
$service.system.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);
|
||||
}
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"row-key": "id"
|
||||
},
|
||||
"context-menu": [
|
||||
row => {
|
||||
(row: any) => {
|
||||
return {
|
||||
label: "新增",
|
||||
hidden: row.type == 2,
|
||||
callback: (_, done) => {
|
||||
this.upsertAppend(row);
|
||||
callback: (_: any, done: Function) => {
|
||||
upsertAppend(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
},
|
||||
"update",
|
||||
"delete",
|
||||
row => {
|
||||
(row: any) => {
|
||||
return {
|
||||
label: "权限",
|
||||
hidden: row.type != 1,
|
||||
callback: (_, done) => {
|
||||
this.setPermission(row);
|
||||
callback: (_: any, done: Function) => {
|
||||
setPermission(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
@ -115,13 +177,11 @@ export default {
|
||||
{
|
||||
prop: "icon",
|
||||
label: "图标",
|
||||
align: "center",
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: "type",
|
||||
label: "类型",
|
||||
align: "center",
|
||||
width: 100,
|
||||
dict: [
|
||||
{
|
||||
@ -141,54 +201,47 @@ export default {
|
||||
{
|
||||
prop: "router",
|
||||
label: "节点路由",
|
||||
align: "center",
|
||||
"min-width": 160
|
||||
minWidth: 160
|
||||
},
|
||||
{
|
||||
prop: "keepAlive",
|
||||
label: "路由缓存",
|
||||
align: "center",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: "viewPath",
|
||||
label: "文件路径",
|
||||
align: "center",
|
||||
"min-width": 200,
|
||||
"show-overflow-tooltip": true
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: "perms",
|
||||
label: "权限",
|
||||
"header-align": "center",
|
||||
"min-width": 300
|
||||
headerAlign: "center",
|
||||
minWidth: 300
|
||||
},
|
||||
{
|
||||
prop: "orderNum",
|
||||
label: "排序号",
|
||||
align: "center",
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
prop: "updateTime",
|
||||
label: "更新时间",
|
||||
align: "center",
|
||||
sortable: "custom",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
align: "center",
|
||||
type: "op",
|
||||
buttons: ["slot-add", "edit", "delete"]
|
||||
}
|
||||
]
|
||||
},
|
||||
});
|
||||
|
||||
upsert: {
|
||||
props: {
|
||||
width: "800px"
|
||||
},
|
||||
// 新增、编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
width: "800px",
|
||||
items: [
|
||||
{
|
||||
prop: "type",
|
||||
@ -219,11 +272,10 @@ export default {
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请输入节点名称"
|
||||
}
|
||||
},
|
||||
|
||||
rules: {
|
||||
required: true,
|
||||
message: "名称不能为空"
|
||||
@ -241,10 +293,10 @@ export default {
|
||||
prop: "router",
|
||||
label: "节点路由",
|
||||
span: 24,
|
||||
hidden: ({ scope }) => scope.type != 1,
|
||||
hidden: ({ scope }: any) => scope.type != 1,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请输入节点路由"
|
||||
}
|
||||
}
|
||||
@ -254,7 +306,7 @@ export default {
|
||||
value: true,
|
||||
label: "路由缓存",
|
||||
span: 24,
|
||||
hidden: ({ scope }) => scope.type != 1,
|
||||
hidden: ({ scope }: any) => scope.type != 1,
|
||||
component: {
|
||||
name: "el-radio-group",
|
||||
options: [
|
||||
@ -274,7 +326,7 @@ export default {
|
||||
label: "是否显示",
|
||||
span: 24,
|
||||
value: true,
|
||||
hidden: ({ scope }) => scope.type == 2,
|
||||
hidden: ({ scope }: any) => scope.type == 2,
|
||||
flex: false,
|
||||
component: {
|
||||
name: "el-switch"
|
||||
@ -284,7 +336,7 @@ export default {
|
||||
prop: "viewPath",
|
||||
label: "文件路径",
|
||||
span: 24,
|
||||
hidden: ({ scope }) => scope.type != 1,
|
||||
hidden: ({ scope }: any) => scope.type != 1,
|
||||
component: {
|
||||
name: "cl-menu-file"
|
||||
}
|
||||
@ -293,12 +345,11 @@ export default {
|
||||
prop: "icon",
|
||||
label: "节点图标",
|
||||
span: 24,
|
||||
hidden: ({ scope }) => scope.type == 2,
|
||||
hidden: ({ scope }: any) => scope.type == 2,
|
||||
component: {
|
||||
name: "cl-menu-icons"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
prop: "orderNum",
|
||||
label: "排序号",
|
||||
@ -317,56 +368,26 @@ export default {
|
||||
prop: "perms",
|
||||
label: "权限",
|
||||
span: 24,
|
||||
hidden: ({ scope }) => scope.type != 2,
|
||||
hidden: ({ scope }: any) => scope.type != 2,
|
||||
component: {
|
||||
name: "cl-menu-perms"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
table,
|
||||
upsert,
|
||||
setRefs,
|
||||
onLoad,
|
||||
onRefresh,
|
||||
onRowClick,
|
||||
upsertAppend,
|
||||
setPermission,
|
||||
toUrl
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
ctx.service(this.$service.system.menu).done();
|
||||
|
||||
app.refresh();
|
||||
},
|
||||
|
||||
onRefresh(_, { render }) {
|
||||
this.$service.system.menu.list().then(list => {
|
||||
list.map(e => {
|
||||
e.permList = e.perms ? e.perms.split(",") : [];
|
||||
});
|
||||
|
||||
render(deepTree(list));
|
||||
});
|
||||
},
|
||||
|
||||
onRowClick(row, column) {
|
||||
if (column.property && row.children) {
|
||||
this.$refs["table"].toggleRowExpansion(row);
|
||||
}
|
||||
},
|
||||
|
||||
upsertAppend({ type, id }) {
|
||||
this.$refs["crud"].rowAppend({
|
||||
parentId: id,
|
||||
type: type + 1
|
||||
});
|
||||
},
|
||||
|
||||
setPermission({ id }) {
|
||||
this.$refs["crud"].rowAppend({
|
||||
parentId: id,
|
||||
type: 2
|
||||
});
|
||||
},
|
||||
|
||||
toUrl(url) {
|
||||
this.$router.push(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<cl-crud @load="onLoad">
|
||||
<template #slot-content="{ scope }">
|
||||
<div class="editor" v-for="(item, index) in tab.list" :key="index">
|
||||
<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" height="300px" v-model="scope.data"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn></cl-refresh-btn>
|
||||
<cl-add-btn></cl-add-btn>
|
||||
@ -28,13 +17,14 @@
|
||||
<cl-pagination></cl-pagination>
|
||||
</el-row>
|
||||
|
||||
<cl-upsert ref="upsert" v-bind="upsert" @open="onUpsertOpen">
|
||||
<cl-upsert :ref="setRefs('upsert')" v-bind="upsert" @open="onUpsertOpen">
|
||||
<template #slot-content="{ scope }">
|
||||
<div class="editor" v-for="(item, index) in tab.list" :key="index">
|
||||
<template v-if="tab.index === index">
|
||||
<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"
|
||||
height="300px"
|
||||
@ -47,11 +37,21 @@
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tab: {
|
||||
<script lang="ts">
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { defineComponent, inject, nextTick, reactive } from "vue";
|
||||
import { useRefs } from "@/core";
|
||||
import { CrudLoad, Table, Upsert } from "@/crud/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-param",
|
||||
|
||||
setup() {
|
||||
const $service = inject<any>("$service");
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
// 选项卡
|
||||
const tab = reactive<any>({
|
||||
index: null,
|
||||
|
||||
list: [
|
||||
@ -66,51 +66,47 @@ export default {
|
||||
component: "cl-editor-quill"
|
||||
}
|
||||
]
|
||||
},
|
||||
table: {
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
columns: [
|
||||
{
|
||||
type: "selection",
|
||||
align: "center",
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
label: "名称",
|
||||
prop: "name",
|
||||
align: "center",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "keyName",
|
||||
prop: "keyName",
|
||||
align: "center",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "数据",
|
||||
prop: "data",
|
||||
align: "center",
|
||||
"min-width": 150,
|
||||
"show-overflow-tooltip": true
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
label: "备注",
|
||||
prop: "remark",
|
||||
align: "center",
|
||||
"min-width": 200,
|
||||
"show-overflow-tooltip": true
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
align: "center",
|
||||
type: "op"
|
||||
}
|
||||
]
|
||||
},
|
||||
upsert: {
|
||||
props: {
|
||||
width: "1000px"
|
||||
},
|
||||
});
|
||||
|
||||
// 新增编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
width: "1000px",
|
||||
|
||||
items: [
|
||||
{
|
||||
@ -119,7 +115,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请输入名称"
|
||||
}
|
||||
},
|
||||
@ -134,7 +130,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请输入Key"
|
||||
}
|
||||
},
|
||||
@ -156,49 +152,58 @@ export default {
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
type: "textarea"
|
||||
},
|
||||
attrs: {
|
||||
placeholder: "请输入备注",
|
||||
rows: 3
|
||||
rows: 3,
|
||||
type: "textarea"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
ctx.service(this.$service.system.param).done();
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service($service.system.param).done();
|
||||
app.refresh();
|
||||
},
|
||||
}
|
||||
|
||||
changeTab(i) {
|
||||
this.$confirm("切换编辑器会清空输入内容,是否继续?", "提示", {
|
||||
// 切换编辑器
|
||||
function changeTab(i: number) {
|
||||
ElMessageBox.confirm("切换编辑器会清空输入内容,是否继续?", "提示", {
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.tab.index = i;
|
||||
this.$refs["upsert"].setForm("data", "");
|
||||
tab.index = i;
|
||||
refs.value.upsert.setForm("data", "");
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
onUpsertOpen(isEdit, data) {
|
||||
this.tab.index = null;
|
||||
// 监听打开
|
||||
function onUpsertOpen(isEdit: boolean, data: any) {
|
||||
tab.index = null;
|
||||
|
||||
this.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
if (isEdit) {
|
||||
this.tab.index = /<*>/g.test(data.data) ? 1 : 0;
|
||||
tab.index = /<*>/g.test(data.data) ? 1 : 0;
|
||||
} else {
|
||||
this.tab.index = 1;
|
||||
tab.index = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
tab,
|
||||
table,
|
||||
upsert,
|
||||
setRefs,
|
||||
onLoad,
|
||||
changeTab,
|
||||
onUpsertOpen
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -14,14 +14,23 @@
|
||||
<cl-table v-bind="table">
|
||||
<template #column-enable="{ scope }">
|
||||
<el-switch
|
||||
v-model="scope.row.enable"
|
||||
v-model="scope.row._enable"
|
||||
size="mini"
|
||||
:inactive-value="0"
|
||||
:active-value="1"
|
||||
:disabled="!perms.enable"
|
||||
@change="onEnableChange($event, scope.row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
|
||||
<!-- 配置按钮 -->
|
||||
<template #slot-conf="{ scope }">
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
v-if="scope.row.view && perms.edit"
|
||||
@click="openConf(scope.row)"
|
||||
>配置</el-button
|
||||
>
|
||||
</template>
|
||||
</cl-table>
|
||||
</el-row>
|
||||
|
||||
@ -32,30 +41,116 @@
|
||||
</el-row>
|
||||
</cl-crud>
|
||||
|
||||
<cl-form ref="form"></cl-form>
|
||||
<!-- 表单 -->
|
||||
<cl-form :ref="setRefs('form')"></cl-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { defineComponent, inject, reactive } from "vue";
|
||||
import { checkPerm } from "@/cool/modules/base";
|
||||
import { useRefs } from "@/core";
|
||||
import { CrudLoad, RefreshOp, Table } from "@/crud/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-plugin",
|
||||
|
||||
setup() {
|
||||
const $service = inject<any>("$service");
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
export default {
|
||||
data() {
|
||||
// 编辑权限
|
||||
const { config, getConfig, enable } = this.$service.plugin.info.permission;
|
||||
const { config, getConfig, enable } = $service.plugin.info.permission;
|
||||
|
||||
const perms = {
|
||||
const perms = reactive<any>({
|
||||
edit: checkPerm({
|
||||
and: [config, getConfig]
|
||||
}),
|
||||
enable: checkPerm(enable)
|
||||
};
|
||||
});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service($service.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.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.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.plugin.info
|
||||
.config({
|
||||
namespace,
|
||||
config: data
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success("保存成功");
|
||||
close();
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// 权限配置
|
||||
perms,
|
||||
// 表格配置
|
||||
table: {
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
@ -64,12 +159,12 @@ export default {
|
||||
},
|
||||
"context-menu": [
|
||||
"refresh",
|
||||
scope => {
|
||||
(scope: any) => {
|
||||
return {
|
||||
label: "配置",
|
||||
hidden: !perms.edit || !scope.view,
|
||||
callback: (_, done) => {
|
||||
this.openConf(scope);
|
||||
callback: (_: any, done: Function) => {
|
||||
openConf(scope);
|
||||
done();
|
||||
}
|
||||
};
|
||||
@ -79,39 +174,39 @@ export default {
|
||||
{
|
||||
label: "名称",
|
||||
prop: "name",
|
||||
"min-width": 140
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
label: "作者",
|
||||
prop: "author",
|
||||
"min-width": 120
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
label: "联系方式",
|
||||
prop: "contact",
|
||||
"show-overflow-tooltip": true,
|
||||
"min-width": 180
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: "功能描述",
|
||||
prop: "description",
|
||||
"show-overflow-tooltip": true,
|
||||
"min-width": 150
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "版本号",
|
||||
prop: "version",
|
||||
"min-width": 110
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
label: "是否启用",
|
||||
prop: "enable",
|
||||
"min-width": 110
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
label: "命名空间",
|
||||
prop: "namespace",
|
||||
"min-width": 110
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
@ -149,101 +244,21 @@ export default {
|
||||
{
|
||||
type: "op",
|
||||
width: 120,
|
||||
buttons: [
|
||||
({ scope }) => {
|
||||
return (
|
||||
scope.row.view &&
|
||||
perms.edit && (
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
onclick={() => {
|
||||
this.openConf(scope.row);
|
||||
}}>
|
||||
配置
|
||||
</el-button>
|
||||
)
|
||||
);
|
||||
buttons: ["slot-conf"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
perms,
|
||||
table,
|
||||
setRefs,
|
||||
onLoad,
|
||||
onRefresh,
|
||||
onEnableChange,
|
||||
openConf
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
ctx.service(this.$service.plugin.info)
|
||||
.set("dict", {
|
||||
api: {
|
||||
page: "list"
|
||||
}
|
||||
})
|
||||
.done();
|
||||
app.refresh();
|
||||
},
|
||||
|
||||
// 刷新钩子
|
||||
onRefresh(params, { next, render }) {
|
||||
next(params).then(res => {
|
||||
render(res, {
|
||||
total: res.length
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 开启、关闭
|
||||
onEnableChange(val, item) {
|
||||
this.$service.plugin.info
|
||||
.enable({
|
||||
namespace: item.namespace,
|
||||
enable: val
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(val ? "开启成功" : "关闭成功");
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
// 打开配置
|
||||
async openConf({ name, namespace, view }) {
|
||||
const form = await this.$service.plugin.info.getConfig({
|
||||
namespace
|
||||
});
|
||||
|
||||
let items = [];
|
||||
|
||||
try {
|
||||
items = JSON.parse(view);
|
||||
} catch (e) {
|
||||
items = [];
|
||||
}
|
||||
|
||||
this.$refs.form.open({
|
||||
title: `${name}配置`,
|
||||
items,
|
||||
form,
|
||||
on: {
|
||||
submit: (data, { close, done }) => {
|
||||
this.$service.plugin.info
|
||||
.config({
|
||||
namespace,
|
||||
config: data
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success("保存成功");
|
||||
close();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -21,17 +21,25 @@
|
||||
</cl-crud>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
<script lang="ts">
|
||||
import { CrudLoad, Table, Upsert } from "@/crud/types";
|
||||
import { defineComponent, inject, reactive } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "sys-role",
|
||||
|
||||
setup() {
|
||||
const $service = inject<any>("$service");
|
||||
|
||||
// 表单值
|
||||
const form = reactive<any>({
|
||||
relevance: 1
|
||||
},
|
||||
upsert: {
|
||||
props: {
|
||||
width: "800px"
|
||||
},
|
||||
});
|
||||
|
||||
// 新增、编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
width: "800px",
|
||||
|
||||
items: [
|
||||
{
|
||||
prop: "name",
|
||||
@ -39,7 +47,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写名称"
|
||||
}
|
||||
},
|
||||
@ -54,7 +62,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写标识"
|
||||
}
|
||||
},
|
||||
@ -70,11 +78,9 @@ export default {
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写备注",
|
||||
type: "textarea",
|
||||
rows: 4
|
||||
},
|
||||
attrs: {
|
||||
placeholder: "请填写备注"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -95,8 +101,10 @@ export default {
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
table: {
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
@ -106,58 +114,55 @@ export default {
|
||||
columns: [
|
||||
{
|
||||
type: "selection",
|
||||
align: "center",
|
||||
width: "60"
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
align: "center",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "label",
|
||||
label: "标识",
|
||||
align: "center",
|
||||
"min-width": 120
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
align: "center",
|
||||
"show-overflow-tooltips": true,
|
||||
"min-width": 150
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "createTime",
|
||||
label: "创建时间",
|
||||
align: "center",
|
||||
sortable: "custom",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "updateTime",
|
||||
label: "更新时间",
|
||||
align: "center",
|
||||
sortable: "custom",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
align: "center",
|
||||
type: "op"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLoad({ ctx, app }) {
|
||||
ctx.service(this.$service.system.role).done();
|
||||
});
|
||||
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: CrudLoad) {
|
||||
ctx.service($service.system.role).done();
|
||||
app.refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
upsert,
|
||||
table,
|
||||
onLoad
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<cl-crud ref="crud" :on-refresh="onRefresh" @load="onLoad">
|
||||
<cl-crud :ref="setRefs('crud')" :on-refresh="onRefresh" @load="onLoad">
|
||||
<el-row type="flex">
|
||||
<cl-refresh-btn></cl-refresh-btn>
|
||||
<cl-add-btn></cl-add-btn>
|
||||
@ -35,13 +35,12 @@
|
||||
@click="toMove()"
|
||||
>转移</el-button
|
||||
>
|
||||
<cl-flex1></cl-flex1>
|
||||
<cl-search-key></cl-search-key>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<cl-table
|
||||
ref="table"
|
||||
:ref="setRefs('table')"
|
||||
v-bind="table"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
@ -50,7 +49,7 @@
|
||||
<cl-avatar
|
||||
shape="square"
|
||||
size="medium"
|
||||
:src="scope.row.headImg | default_avatar"
|
||||
:src="scope.row.headImg"
|
||||
:style="{ margin: 'auto' }"
|
||||
>
|
||||
</cl-avatar>
|
||||
@ -88,32 +87,55 @@
|
||||
</el-row>
|
||||
|
||||
<cl-upsert
|
||||
ref="upsert"
|
||||
:ref="setRefs('upsert')"
|
||||
:items="upsert.items"
|
||||
:on-submit="onUpsertSubmit"
|
||||
></cl-upsert>
|
||||
>
|
||||
<template #slot-tips>
|
||||
<div>
|
||||
<i class="el-icon-warning"></i>
|
||||
<span style="margin-left: 6px">新增用户默认密码为:123456</span>
|
||||
</div>
|
||||
</template>
|
||||
</cl-upsert>
|
||||
</cl-crud>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 部门移动 -->
|
||||
<cl-dept-move ref="dept-move" @success="refresh({ page: 1 })"></cl-dept-move>
|
||||
<cl-dept-move :ref="setRefs('dept-move')" @success="refresh({ page: 1 })"></cl-dept-move>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
<script lang="ts">
|
||||
import { computed, inject, reactive, ref, watch } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRefs } from "@/core";
|
||||
import { Table, Upsert } from "@/crud/types";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isExpand: true,
|
||||
selects: {
|
||||
name: "sys-user",
|
||||
|
||||
setup() {
|
||||
const $service = inject<any>("$service");
|
||||
const store = useStore();
|
||||
const { refs, setRefs } = useRefs();
|
||||
|
||||
// 是否展开
|
||||
const isExpand = ref<boolean>(true);
|
||||
|
||||
// 选择项
|
||||
const selects = reactive<any>({
|
||||
dept: {},
|
||||
ids: []
|
||||
},
|
||||
dept: [],
|
||||
table: {
|
||||
});
|
||||
|
||||
// 部门列表
|
||||
const dept = ref<any[]>([]);
|
||||
|
||||
// 表格配置
|
||||
const table = reactive<Table>({
|
||||
props: {
|
||||
"default-sort": {
|
||||
prop: "createTime",
|
||||
@ -132,43 +154,43 @@ export default {
|
||||
{
|
||||
prop: "name",
|
||||
label: "姓名",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "username",
|
||||
label: "用户名",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "nickName",
|
||||
label: "昵称",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "departmentName",
|
||||
label: "部门名称",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "roleName",
|
||||
label: "角色",
|
||||
"header-align": "center",
|
||||
"min-width": 200
|
||||
headerAlign: "center",
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
prop: "phone",
|
||||
label: "手机号码",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "remark",
|
||||
label: "备注",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
"min-width": 120,
|
||||
minWidth: 120,
|
||||
dict: [
|
||||
{
|
||||
label: "启用",
|
||||
@ -186,7 +208,7 @@ export default {
|
||||
prop: "createTime",
|
||||
label: "创建时间",
|
||||
sortable: "custom",
|
||||
"min-width": 150
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
type: "op",
|
||||
@ -194,8 +216,10 @@ export default {
|
||||
width: 160
|
||||
}
|
||||
]
|
||||
},
|
||||
upsert: {
|
||||
});
|
||||
|
||||
// 新增、编辑配置
|
||||
const upsert = reactive<Upsert>({
|
||||
items: [
|
||||
{
|
||||
prop: "headImg",
|
||||
@ -215,7 +239,7 @@ export default {
|
||||
span: 24,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写姓名"
|
||||
}
|
||||
},
|
||||
@ -230,7 +254,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写昵称"
|
||||
}
|
||||
},
|
||||
@ -245,7 +269,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写用户名"
|
||||
}
|
||||
},
|
||||
@ -263,7 +287,7 @@ export default {
|
||||
hidden: ":isAdd",
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写密码",
|
||||
type: "password"
|
||||
}
|
||||
@ -300,7 +324,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写手机号码"
|
||||
}
|
||||
}
|
||||
@ -311,7 +335,7 @@ export default {
|
||||
span: 12,
|
||||
component: {
|
||||
name: "el-input",
|
||||
attrs: {
|
||||
props: {
|
||||
placeholder: "请填写邮箱"
|
||||
}
|
||||
}
|
||||
@ -323,11 +347,9 @@ export default {
|
||||
component: {
|
||||
name: "el-input",
|
||||
props: {
|
||||
placeholder: "请填写备注",
|
||||
type: "textarea",
|
||||
rows: 4
|
||||
},
|
||||
attrs: {
|
||||
placeholder: "请填写备注"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -352,63 +374,62 @@ export default {
|
||||
{
|
||||
prop: "tips",
|
||||
hidden: ":isEdit",
|
||||
component: (
|
||||
<div>
|
||||
<i class="el-icon-warning"></i>
|
||||
<span style="margin-left: 6px">新增用户默认密码为:123456</span>
|
||||
</div>
|
||||
)
|
||||
component: {
|
||||
name: "slot-tips"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 浏览器信息
|
||||
const browser = computed(() => store.getters.browser);
|
||||
|
||||
// 监听屏幕大小变化
|
||||
watch(
|
||||
() => browser.value.isMini,
|
||||
(val: boolean) => {
|
||||
isExpand.value = !val;
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
computed: {
|
||||
...mapGetters(["browser"])
|
||||
},
|
||||
|
||||
watch: {
|
||||
"browser.isMini": {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.isExpand = !val;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
refresh(params) {
|
||||
this.$refs["crud"].refresh(params);
|
||||
},
|
||||
|
||||
onLoad({ ctx, app }) {
|
||||
ctx.service(this.$service.system.user).done();
|
||||
// crud 加载
|
||||
function onLoad({ ctx, app }: any) {
|
||||
ctx.service($service.system.user).done();
|
||||
app.refresh();
|
||||
},
|
||||
}
|
||||
|
||||
async onRefresh(params, { next, render }) {
|
||||
let { list } = await next(params);
|
||||
// 刷新列表
|
||||
function refresh(params: any) {
|
||||
refs.value.crud.refresh(params);
|
||||
}
|
||||
|
||||
list.map(e => {
|
||||
// 刷新监听
|
||||
async function onRefresh(params: any, { next, render }: any) {
|
||||
const { list } = await next(params);
|
||||
|
||||
list.map((e: any) => {
|
||||
if (e.roleName) {
|
||||
this.$set(e, "roleNameList", e.roleName.split(","));
|
||||
e.roleNameList = e.roleName.split(",");
|
||||
}
|
||||
|
||||
e.status = Boolean(e.status);
|
||||
});
|
||||
|
||||
render(list);
|
||||
},
|
||||
}
|
||||
|
||||
onUpsertSubmit(_, data, { next }) {
|
||||
// 提交钩子
|
||||
function onUpsertSubmit(_: boolean, data: any, { next }: any) {
|
||||
let departmentId = data.departmentId;
|
||||
|
||||
if (!departmentId) {
|
||||
departmentId = this.selects.dept.id;
|
||||
departmentId = selects.dept.id;
|
||||
|
||||
if (!departmentId) {
|
||||
departmentId = this.dept[0].id;
|
||||
departmentId = dept.value[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,51 +437,78 @@ export default {
|
||||
...data,
|
||||
departmentId
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onSelectionChange(selection) {
|
||||
this.selects.ids = selection.map(e => e.id);
|
||||
},
|
||||
// 多选监听
|
||||
function onSelectionChange(selection: any[]) {
|
||||
selects.ids = selection.map(e => e.id);
|
||||
}
|
||||
|
||||
onDeptRowClick({ item, ids }) {
|
||||
this.selects.dept = item;
|
||||
// 部门选择监听
|
||||
function onDeptRowClick({ item, ids }: any) {
|
||||
selects.dept = item;
|
||||
|
||||
this.refresh({
|
||||
refresh({
|
||||
page: 1,
|
||||
departmentIds: ids
|
||||
});
|
||||
|
||||
// 收起
|
||||
if (this.browser.isMini) {
|
||||
this.isExpand = false;
|
||||
if (browser.value.isMini) {
|
||||
isExpand.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDeptUserAdd(item) {
|
||||
this.$refs["crud"].rowAppend({
|
||||
// 部门下新增成员
|
||||
function onDeptUserAdd(item: any) {
|
||||
refs.value.crud.rowAppend({
|
||||
departmentId: item.id
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onDeptListChange(list) {
|
||||
this.dept = list;
|
||||
},
|
||||
// 部门列表监听
|
||||
function onDeptListChange(list: any[]) {
|
||||
dept.value = list;
|
||||
}
|
||||
|
||||
deptExpand() {
|
||||
this.isExpand = !this.isExpand;
|
||||
},
|
||||
// 是否显示部门
|
||||
function deptExpand() {
|
||||
isExpand.value = !isExpand.value;
|
||||
}
|
||||
|
||||
async toMove(e) {
|
||||
// 移动成员
|
||||
async function toMove(e: any) {
|
||||
let ids = [];
|
||||
|
||||
if (!e) {
|
||||
ids = this.selects.ids;
|
||||
ids = selects.ids;
|
||||
} else {
|
||||
ids = [e.id];
|
||||
}
|
||||
|
||||
this.$refs["dept-move"].toMove(ids);
|
||||
refs.value["dept-move"].toMove(ids);
|
||||
}
|
||||
|
||||
return {
|
||||
refs,
|
||||
isExpand,
|
||||
selects,
|
||||
dept,
|
||||
table,
|
||||
upsert,
|
||||
browser,
|
||||
setRefs,
|
||||
onLoad,
|
||||
refresh,
|
||||
onRefresh,
|
||||
onUpsertSubmit,
|
||||
onSelectionChange,
|
||||
onDeptRowClick,
|
||||
onDeptUserAdd,
|
||||
onDeptListChange,
|
||||
deptExpand,
|
||||
toMove
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="cl-chat__wrap">
|
||||
<!-- 聊天窗口 -->
|
||||
<cl-dialog
|
||||
:visible.sync="visible"
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
:height="height"
|
||||
:width="width"
|
||||
@ -12,7 +12,7 @@
|
||||
'append-to-body': true,
|
||||
'close-on-click-modal': false
|
||||
}"
|
||||
:controls="['slot-expand', 'cl-flex1', 'fullscreen', 'close']"
|
||||
:controls="['slot-session', 'cl-flex1', 'fullscreen', 'close']"
|
||||
>
|
||||
<div class="cl-chat">
|
||||
<!-- 会话列表 -->
|
||||
@ -29,38 +29,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展开按钮 -->
|
||||
<template #slot-expand>
|
||||
<template #slot-session>
|
||||
<button v-if="session">
|
||||
<i
|
||||
class="el-icon-notebook-2"
|
||||
v-if="sessionVisible"
|
||||
@click="CLOSE_SESSION()"
|
||||
></i>
|
||||
<i class="el-icon-arrow-left" v-else @click="OPEN_SESSION()"></i>
|
||||
<i class="el-icon-notebook-2" v-if="sessionVisible" @click="closeSession()"></i>
|
||||
<i class="el-icon-arrow-left" v-else @click="openSession()"></i>
|
||||
</button>
|
||||
</template>
|
||||
</cl-dialog>
|
||||
|
||||
<!-- MP3 -->
|
||||
<div class="mp3">
|
||||
<audio style="display: none" ref="sound" src="../static/notify.mp3" controls></audio>
|
||||
<audio
|
||||
style="display: none"
|
||||
:ref="setRefs('sound')"
|
||||
src="../static/notify.mp3"
|
||||
controls
|
||||
></audio>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, h, inject, onUnmounted, provide, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { ElNotification } from "element-plus";
|
||||
import dayjs from "dayjs";
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import io from "socket.io-client";
|
||||
import { socketUrl } from "@/config/env";
|
||||
import Session from "./session";
|
||||
import Message from "./message";
|
||||
import Input from "./input";
|
||||
import eventBus from "../utils/event-bus";
|
||||
// import io from "socket.io-client";
|
||||
// import { socketUrl } from "@/config/env";
|
||||
import Session from "./session.vue";
|
||||
import Message from "./message.vue";
|
||||
import Input from "./input.vue";
|
||||
import { parseContent } from "../utils";
|
||||
import { useRefs } from "@/core";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "cl-chat",
|
||||
|
||||
components: {
|
||||
@ -80,98 +82,126 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
modes: ["text", "image", "emoji", "voice", "video"], // 消息类型
|
||||
visible: false,
|
||||
socket: null
|
||||
};
|
||||
},
|
||||
setup(_, { emit }) {
|
||||
const store = useStore();
|
||||
const { refs, setRefs } = useRefs();
|
||||
const $service = inject<any>("$service");
|
||||
const mitt = inject<any>("mitt");
|
||||
|
||||
provide() {
|
||||
return {
|
||||
chat: this
|
||||
};
|
||||
},
|
||||
// 当前会话
|
||||
const session = computed(() => store.getters.session);
|
||||
|
||||
computed: {
|
||||
...mapGetters(["token", "session", "sessionList", "sessionVisible"]),
|
||||
// 会话列表是否可见
|
||||
const sessionVisible = computed(() => store.getters.sessionVisible);
|
||||
|
||||
title() {
|
||||
return this.session ? `与 ${this.session.nickname} 聊天中` : "聊天对话框";
|
||||
// 消息类型
|
||||
const modes = ["text", "image", "emoji", "voice", "video"];
|
||||
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
// socket 实例
|
||||
const socket: any = null;
|
||||
|
||||
// 对话框标题
|
||||
const title = computed(() => {
|
||||
return session.value ? `与 ${session.value.nickname} 聊天中` : "聊天对话框";
|
||||
});
|
||||
|
||||
// 打开
|
||||
function open() {
|
||||
visible.value = true;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
// this.socket = io(`${socketUrl}?isAdmin=true&token=${token}`);
|
||||
// this.socket.on("connect", () => {
|
||||
// console.log("socket connect");
|
||||
// });
|
||||
// this.socket.on("admin", msg => {
|
||||
// this.onMessage(msg);
|
||||
// });
|
||||
// this.socket.on("error", err => {
|
||||
// console.log(err);
|
||||
// });
|
||||
// this.socket.on("disconnect", () => {
|
||||
// console.log("disconnect connect");
|
||||
// });
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
// 关闭
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations(["OPEN_SESSION", "CLOSE_SESSION", "UPDATE_SESSION"]),
|
||||
// 打开会话列表
|
||||
function openSession() {
|
||||
store.commit("OPEN_SESSION");
|
||||
}
|
||||
|
||||
open() {
|
||||
this.visible = true;
|
||||
},
|
||||
|
||||
close() {
|
||||
this.visible = false;
|
||||
},
|
||||
|
||||
// 监听消息
|
||||
onMessage(msg) {
|
||||
// 回调
|
||||
this.$emit("message", msg);
|
||||
// 关闭会话列表
|
||||
function closeSession() {
|
||||
store.commit("CLOSE_SESSION");
|
||||
}
|
||||
|
||||
// 消息通知
|
||||
this.notification(msg);
|
||||
function notification(msg: string) {
|
||||
const { _text } = parseContent(JSON.parse(msg));
|
||||
|
||||
// 播放音乐
|
||||
if (refs.value.sound) {
|
||||
refs.value.sound.play();
|
||||
}
|
||||
|
||||
if (!visible.value) {
|
||||
// 页面消息提示
|
||||
ElNotification({
|
||||
title: "提示",
|
||||
message: h("span", _text)
|
||||
});
|
||||
|
||||
// 浏览器消息通知
|
||||
const NotificationInstance = Notification || window.Notification;
|
||||
if (NotificationInstance) {
|
||||
if (NotificationInstance.permission !== "denied") {
|
||||
NotificationInstance.requestPermission(() => {
|
||||
const n = new Notification("COOL-MALL", {
|
||||
body: _text,
|
||||
icon: "/favicon.ico"
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
n.close();
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听消息
|
||||
function onMessage(msg: string) {
|
||||
// 回调
|
||||
emit("message", msg);
|
||||
|
||||
// 消息通知
|
||||
notification(msg);
|
||||
|
||||
try {
|
||||
const { contentType, fromId, content, msgId } = JSON.parse(msg);
|
||||
|
||||
// 是否当前
|
||||
const same = this.session && this.session.userId == fromId;
|
||||
const same = session.value && session.value.userId == fromId;
|
||||
|
||||
if (same) {
|
||||
// 更新消息
|
||||
this.UPDATE_SESSION({
|
||||
store.commit("UPDATE_SESSION", {
|
||||
contentType,
|
||||
content
|
||||
});
|
||||
|
||||
// 追加消息
|
||||
this.$store.commit("APPEND_MESSAGE_LIST", {
|
||||
store.commit("APPEND_MESSAGE_LIST", {
|
||||
contentType,
|
||||
content: JSON.parse(content),
|
||||
type: 1
|
||||
});
|
||||
|
||||
mitt.emit("message.scrollToBottom");
|
||||
|
||||
// 阅读消息
|
||||
this.$service.im.message.read({
|
||||
$service.im.message.read({
|
||||
ids: [msgId],
|
||||
session: this.session.id
|
||||
session: session.value.id
|
||||
});
|
||||
}
|
||||
|
||||
// 查找会话
|
||||
const item = this.sessionList.find(e => e.userId == fromId);
|
||||
const item = store.getters.sessionList.find((e: any) => e.userId == fromId);
|
||||
|
||||
if (item) {
|
||||
if (!same) {
|
||||
@ -185,58 +215,65 @@ export default {
|
||||
});
|
||||
} else {
|
||||
// 刷新会话列表
|
||||
eventBus.$emit("session.refresh");
|
||||
mitt.emit("session.refresh");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("消息格式异常", e);
|
||||
}
|
||||
},
|
||||
|
||||
// 消息通知
|
||||
notification(msg) {
|
||||
const { _text } = parseContent(JSON.parse(msg));
|
||||
|
||||
// 播放音乐
|
||||
if (this.$refs.sound) {
|
||||
this.$refs.sound.play();
|
||||
}
|
||||
|
||||
if (!this.visible) {
|
||||
// 页面消息提示
|
||||
this.$notify({
|
||||
title: "提示",
|
||||
message: this.$createElement("span", _text)
|
||||
// 加载 socket
|
||||
(function() {
|
||||
// socket = io(`${socketUrl}?isAdmin=true&token=${store.getters.token}`);
|
||||
// socket.on("connect", () => {
|
||||
// console.log("socket connect");
|
||||
// });
|
||||
// socket.on("admin", msg => {
|
||||
// onMessage(msg);
|
||||
// });
|
||||
// socket.on("error", err => {
|
||||
// console.log(err);
|
||||
// });
|
||||
// socket.on("disconnect", () => {
|
||||
// console.log("disconnect connect");
|
||||
// });
|
||||
})();
|
||||
|
||||
// 共享参数
|
||||
provide("chat", {
|
||||
modes,
|
||||
socket
|
||||
});
|
||||
|
||||
// 浏览器消息通知
|
||||
const NotificationInstance = Notification || window.Notification;
|
||||
if (!!NotificationInstance) {
|
||||
if (NotificationInstance.permission !== "denied") {
|
||||
NotificationInstance.requestPermission(status => {
|
||||
let n = new Notification("COOL-MALL", {
|
||||
body: _text,
|
||||
icon: "/favicon.ico"
|
||||
// 销毁
|
||||
onUnmounted(function() {
|
||||
if (socket) {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
n.close();
|
||||
}, 2000);
|
||||
});
|
||||
return {
|
||||
refs,
|
||||
session,
|
||||
sessionVisible,
|
||||
visible,
|
||||
title,
|
||||
setRefs,
|
||||
open,
|
||||
close,
|
||||
openSession,
|
||||
closeSession,
|
||||
onMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cl-chat__dialog {
|
||||
.el-dialog {
|
||||
&__body {
|
||||
.el-dialog__body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-chat {
|
||||
|
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-popover
|
||||
v-model="visible"
|
||||
placement="top"
|
||||
:visible="visible"
|
||||
:width="popoverWidth"
|
||||
placement="top"
|
||||
trigger="click"
|
||||
popper-class="popover-emoji"
|
||||
popper-class="popper-emoji"
|
||||
>
|
||||
<div class="tool-emoji">
|
||||
<div class="tool-emoji__scroller scroller1">
|
||||
@ -19,12 +20,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img slot="reference" src="../static/images/emoji.png" alt="" />
|
||||
<template #reference>
|
||||
<img src="../static/images/emoji.png" alt="" @click="open" />
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
// 表情列表
|
||||
const emoji = {
|
||||
url: "https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/",
|
||||
@ -120,34 +126,50 @@ const emoji = {
|
||||
]
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
export default defineComponent({
|
||||
setup(_, { emit }) {
|
||||
const store = useStore();
|
||||
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
// 表情列表
|
||||
const list = ref<any[]>(emoji.list.map(e => emoji.url + e));
|
||||
|
||||
// 弹窗宽度
|
||||
const popoverWidth = computed(() => {
|
||||
const { width } = store.getters.browser;
|
||||
return (width > 500 ? 500 : width) - 24;
|
||||
});
|
||||
|
||||
function open() {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function select(e: any) {
|
||||
emit("select", e);
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
visible: false,
|
||||
list: emoji.list.map(e => emoji.url + e)
|
||||
visible,
|
||||
list,
|
||||
popoverWidth,
|
||||
open,
|
||||
close,
|
||||
select
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(["browser"]),
|
||||
|
||||
popoverWidth() {
|
||||
return (this.browser.width > 500 ? 500 : this.browser.width) - 24;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
select(e) {
|
||||
this.$emit("select", e);
|
||||
this.visible = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popover-emoji {
|
||||
padding: 5px;
|
||||
.popper-emoji {
|
||||
padding: 5px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
import Notice from "./notice";
|
||||
import Chat from "./chat";
|
||||
|
||||
export default { Notice, Chat };
|
4
src/cool/modules/chat/components/index.ts
Normal file
4
src/cool/modules/chat/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import Notice from "./notice.vue";
|
||||
import Chat from "./chat.vue";
|
||||
|
||||
export default { Notice, Chat };
|
@ -58,7 +58,7 @@
|
||||
type="textarea"
|
||||
resize="none"
|
||||
:rows="5"
|
||||
@keyup.enter.native="onTextSend"
|
||||
@keyup.enter="onTextSend"
|
||||
></el-input>
|
||||
|
||||
<el-button type="primary" size="mini" :disabled="!text" @click="onTextSend"
|
||||
@ -68,33 +68,63 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from "vuex";
|
||||
import Emoji from "./emoji";
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, nextTick, reactive, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import Emoji from "./emoji.vue";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Emoji
|
||||
},
|
||||
|
||||
inject: ["chat"],
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const chat = inject<any>("chat");
|
||||
const mitt = inject<any>("mitt");
|
||||
|
||||
data() {
|
||||
return {
|
||||
text: "",
|
||||
emoji: {
|
||||
// 输入值
|
||||
const text = ref<string>("");
|
||||
|
||||
// 表情
|
||||
const emoji = reactive<any>({
|
||||
visible: false
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
methods: {
|
||||
...mapMutations(["UPDATE_SESSION", "UPDATE_MESSAGE", "APPEND_MESSAGE_LIST"]),
|
||||
// 追加消息
|
||||
function append(data: any) {
|
||||
store.commit("APPEND_MESSAGE_LIST", data);
|
||||
mitt.emit("message.scrollToBottom");
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
function send(data: any, isAppend?: boolean) {
|
||||
const { id, userId } = store.getters.session;
|
||||
|
||||
// 格式化内容
|
||||
data.content = JSON.stringify(data.content);
|
||||
|
||||
// 更新消息
|
||||
store.commit("UPDATE_SESSION", data);
|
||||
|
||||
if (chat.socket) {
|
||||
chat.socket.emit(`user@${userId}`, {
|
||||
contentType: data.contentType,
|
||||
type: 0,
|
||||
content: data.content,
|
||||
sessionId: id
|
||||
});
|
||||
}
|
||||
|
||||
if (isAppend) {
|
||||
append(data);
|
||||
}
|
||||
}
|
||||
|
||||
// 上传前,获取图片预览地址
|
||||
onBeforeUpload(file, key) {
|
||||
function onBeforeUpload(file: any, key: string) {
|
||||
// 先添加到列表中,等待上传
|
||||
const next = (options = {}) => {
|
||||
function next(options = {}) {
|
||||
const data = {
|
||||
content: {
|
||||
[`${key}Url`]: ""
|
||||
@ -103,18 +133,18 @@ export default {
|
||||
uid: file.uid,
|
||||
loading: true,
|
||||
progress: "0%",
|
||||
contentType: this.chat.modes.indexOf(key),
|
||||
contentType: chat.modes.indexOf(key),
|
||||
...options
|
||||
};
|
||||
|
||||
this.append(data);
|
||||
};
|
||||
append(data);
|
||||
}
|
||||
|
||||
// 图片预览
|
||||
if (key == "image") {
|
||||
const fileReader = new FileReader();
|
||||
|
||||
fileReader.onload = e => {
|
||||
fileReader.onload = (e: any) => {
|
||||
const imageUrl = e.target.result;
|
||||
const image = new Image();
|
||||
|
||||
@ -145,21 +175,21 @@ export default {
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 上传中
|
||||
onUploadProgress(e, file) {
|
||||
this.UPDATE_MESSAGE({
|
||||
function onUploadProgress(e: any, file: any) {
|
||||
store.commit("UPDATE_MESSAGE", {
|
||||
file,
|
||||
data: {
|
||||
progress: e.percent + "%"
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// 上传成功
|
||||
onUploadSuccess(res, file, key) {
|
||||
this.UPDATE_MESSAGE({
|
||||
function onUploadSuccess(res: any, file: any, key: string) {
|
||||
store.commit("UPDATE_MESSAGE", {
|
||||
file,
|
||||
data: {
|
||||
loading: false,
|
||||
@ -167,34 +197,34 @@ export default {
|
||||
[`${key}Url`]: res.data
|
||||
}
|
||||
},
|
||||
callback: this.send
|
||||
callback: send
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// 发送文本内容
|
||||
onTextSend() {
|
||||
if (this.text) {
|
||||
if (this.text.replace(/\n/g, "") !== "") {
|
||||
function onTextSend() {
|
||||
if (text.value) {
|
||||
if (text.value.replace(/\n/g, "") !== "") {
|
||||
const data = {
|
||||
type: 0,
|
||||
contentType: 0,
|
||||
content: {
|
||||
text: this.text
|
||||
text: text.value
|
||||
}
|
||||
};
|
||||
|
||||
this.send(data, true);
|
||||
send(data, true);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.text = "";
|
||||
nextTick(() => {
|
||||
text.value = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 图片选择
|
||||
onImageSelect(res) {
|
||||
this.send(
|
||||
function onImageSelect(res: any) {
|
||||
send(
|
||||
{
|
||||
content: {
|
||||
imageUrl: res.data
|
||||
@ -204,12 +234,12 @@ export default {
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
// 表情选择
|
||||
onEmojiSelect(url) {
|
||||
this.emoji.visible = false;
|
||||
this.send(
|
||||
function onEmojiSelect(url: string) {
|
||||
emoji.visible = false;
|
||||
send(
|
||||
{
|
||||
content: {
|
||||
imageUrl: url
|
||||
@ -219,11 +249,11 @@ export default {
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
// 视频选择
|
||||
onVideoSelect(url) {
|
||||
this.send(
|
||||
function onVideoSelect(url: string) {
|
||||
send(
|
||||
{
|
||||
content: {
|
||||
videoUrl: url
|
||||
@ -233,37 +263,22 @@ export default {
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
send(data, isAppend) {
|
||||
const { id, userId } = this.$store.getters.session;
|
||||
|
||||
// 更新会话消息
|
||||
this.UPDATE_SESSION(data);
|
||||
|
||||
// 发送消息
|
||||
if (this.chat.socket) {
|
||||
this.chat.socket.emit(`user@${userId}`, {
|
||||
contentType: data.contentType,
|
||||
type: 0,
|
||||
content: JSON.stringify(data.content),
|
||||
sessionId: id
|
||||
});
|
||||
}
|
||||
|
||||
// 是否添加到列表中
|
||||
if (isAppend) {
|
||||
this.append(data);
|
||||
return {
|
||||
text,
|
||||
emoji,
|
||||
send,
|
||||
onBeforeUpload,
|
||||
onUploadProgress,
|
||||
onUploadSuccess,
|
||||
onTextSend,
|
||||
onImageSelect,
|
||||
onEmojiSelect,
|
||||
onVideoSelect
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 追加消息
|
||||
append(data) {
|
||||
this.APPEND_MESSAGE_LIST(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -287,7 +302,7 @@ export default {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/deep/ img {
|
||||
:deep(img) {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="cl-chat-message" v-loading="!visible && loading" element-loading-text="消息加载中">
|
||||
<div
|
||||
class="cl-chat-message__scroller scroller1"
|
||||
ref="scroller"
|
||||
:ref="setRefs('scroller')"
|
||||
:style="{
|
||||
opacity: visible ? 1 : 0
|
||||
}"
|
||||
@ -76,16 +76,14 @@
|
||||
<!-- 语音 -->
|
||||
<template v-else-if="item.mode === 'voice'">
|
||||
<icon-voice :play="item.isPlay"></icon-voice>
|
||||
<span class="duration"
|
||||
>{{ item.content.duration | duration }}"</span
|
||||
>
|
||||
<span class="duration">{{ item.content.duration }}"</span>
|
||||
</template>
|
||||
|
||||
<!-- 视频 -->
|
||||
<template v-else-if="item.mode === 'video'">
|
||||
<div class="item">
|
||||
<video
|
||||
:poster="item.content.videoUrl | video_poster"
|
||||
:poster="item.content.videoUrl"
|
||||
:src="item.content.videoUrl"
|
||||
controls
|
||||
></video>
|
||||
@ -111,52 +109,61 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, nextTick, onUnmounted, reactive, ref } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { mapGetters } from "vuex";
|
||||
import { isString } from "cl-admin/utils";
|
||||
import eventBus from "../utils/event-bus";
|
||||
import IconVoice from "./icon-voice";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useStore } from "vuex";
|
||||
import { isString } from "@/core/utils";
|
||||
import IconVoice from "./icon-voice.vue";
|
||||
import { useRefs } from "@/core";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconVoice
|
||||
},
|
||||
|
||||
inject: ["chat"],
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const { refs, setRefs } = useRefs();
|
||||
const $service = inject<any>("$service");
|
||||
const chat = inject<any>("chat");
|
||||
const mitt = inject<any>("mitt");
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
visible: false,
|
||||
pagination: {
|
||||
// 当前会话信息
|
||||
const session = computed(() => store.getters.session);
|
||||
|
||||
// 加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// 是否可见
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive<any>({
|
||||
page: 1,
|
||||
size: 20,
|
||||
total: 0
|
||||
},
|
||||
voice: {
|
||||
});
|
||||
|
||||
// 语音
|
||||
const voice = reactive<any>({
|
||||
url: "",
|
||||
timer: null
|
||||
},
|
||||
refreshRd: null
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
filters: {
|
||||
duration(val) {
|
||||
return Math.ceil((val || 1) / 1000);
|
||||
}
|
||||
},
|
||||
// 请求随机值
|
||||
const refreshRd = ref<any>(null);
|
||||
|
||||
computed: {
|
||||
...mapGetters(["userInfo", "session", "messageList"]),
|
||||
// 消息列表
|
||||
const list = computed(() => {
|
||||
const { userInfo, messageList } = store.getters;
|
||||
|
||||
list() {
|
||||
let date = "";
|
||||
|
||||
return this.messageList.map(e => {
|
||||
return messageList.map((e: any) => {
|
||||
// 时间间隔
|
||||
e._date = date
|
||||
const _date = date
|
||||
? dayjs(e.createTime).isBefore(dayjs(date).add(1, "minute"))
|
||||
? ""
|
||||
: e.createTime
|
||||
@ -170,126 +177,115 @@ export default {
|
||||
e = JSON.parse(e);
|
||||
}
|
||||
|
||||
if (isString(e.content)) {
|
||||
e.content = JSON.parse(e.content);
|
||||
}
|
||||
// 内容
|
||||
const content = isString(e.content) ? JSON.parse(e.content) : e.content;
|
||||
|
||||
// 解析昵称
|
||||
const nickName = e.type == 0 ? this.userInfo.nickName : this.session.nickname;
|
||||
// 昵称
|
||||
const nickName = e.type == 0 ? userInfo.nickName : session.value.nickname;
|
||||
|
||||
// 解析头像
|
||||
// 头像
|
||||
const avatarUrl =
|
||||
e.type == 0
|
||||
? this.userInfo.avatarUrl || require("../static/images/custom-avatar.png")
|
||||
: this.session.headimgurl;
|
||||
? userInfo.avatarUrl || require("../static/images/custom-avatar.png")
|
||||
: session.value.headimgurl;
|
||||
|
||||
return {
|
||||
...e,
|
||||
_date,
|
||||
content,
|
||||
avatarUrl,
|
||||
nickName,
|
||||
mode: this.chat.modes[e.contentType]
|
||||
mode: chat.modes[e.contentType]
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
// 销毁事件
|
||||
eventBus.$off("message.refresh");
|
||||
eventBus.$off("message.scrollToBottom");
|
||||
},
|
||||
|
||||
created() {
|
||||
// 监听列表刷新
|
||||
eventBus.$on("message.refresh", this.refresh);
|
||||
|
||||
// 滚动到底部
|
||||
eventBus.$on("message.scrollToBottom", this.scrollToBottom);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
// 清除播放
|
||||
clearTimeout(this.voice.timer);
|
||||
|
||||
this.messageList.map(e => {
|
||||
e.isPlay = false;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 点击
|
||||
onTap(item) {
|
||||
function onTap(item: any) {
|
||||
// 播放语音
|
||||
if (item.mode == "voice") {
|
||||
this.messageList.map(e => {
|
||||
this.$set(e, "isPlay", e.id == item.id ? e.isPlay : false);
|
||||
store.getters.messageList.map((e: any) => {
|
||||
e.isPlay = e.id == item.id ? e.isPlay : false;
|
||||
});
|
||||
|
||||
item.isPlay = !item.isPlay;
|
||||
|
||||
if (item.isPlay) {
|
||||
this.voice.url = item.content.voiceUrl;
|
||||
voice.url = item.content.voiceUrl;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs["voice"].play();
|
||||
nextTick(() => {
|
||||
refs.value.voice.play();
|
||||
});
|
||||
} else {
|
||||
this.$refs["voice"].pause();
|
||||
refs.value.voice.pause();
|
||||
item.isPlay = false;
|
||||
}
|
||||
|
||||
clearTimeout(this.voice.timer);
|
||||
clearTimeout(voice.timer);
|
||||
|
||||
this.voice.timer = setTimeout(() => {
|
||||
voice.timer = setTimeout(() => {
|
||||
item.isPlay = false;
|
||||
}, item.content.duration);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
if (refs.value.scroller) {
|
||||
refs.value.scroller.scrollTo({
|
||||
top: 99999,
|
||||
behavior: visible.value ? "smooth" : "auto"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
refresh(params) {
|
||||
function refresh(params?: any) {
|
||||
// 请求随机值
|
||||
const rd = (this.refreshRd = Math.random());
|
||||
const rd = (refreshRd.value = Math.random());
|
||||
|
||||
// 请求参数
|
||||
const data = {
|
||||
...this.pagination,
|
||||
...pagination,
|
||||
...params,
|
||||
sessionId: this.session.id,
|
||||
sessionId: session.value.id,
|
||||
order: "createTime",
|
||||
sort: "desc"
|
||||
};
|
||||
|
||||
// 加载动画
|
||||
this.loading = true;
|
||||
loading.value = true;
|
||||
|
||||
// 首页处理
|
||||
if (data.page === 1) {
|
||||
this.visible = false;
|
||||
this.$store.commit("CLEAR_MESSAGE_LIST");
|
||||
visible.value = false;
|
||||
store.commit("CLEAR_MESSAGE_LIST");
|
||||
}
|
||||
|
||||
// 完成
|
||||
const done = () => {
|
||||
this.loading = false;
|
||||
this.visible = true;
|
||||
loading.value = false;
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
this.$service.im.message
|
||||
$service.im.message
|
||||
.page(data)
|
||||
.then(res => {
|
||||
.then((res: any) => {
|
||||
// 防止脏数据
|
||||
if (rd != this.refreshRd) {
|
||||
if (rd != refreshRd.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 分页信息
|
||||
this.pagination = res.pagination;
|
||||
Object.assign(pagination, res.pagination);
|
||||
|
||||
// 追加数据
|
||||
this.$store.commit("PREPEND_MESSAGE_LIST", res.list);
|
||||
store.commit("PREPEND_MESSAGE_LIST", res.list);
|
||||
|
||||
if (data.page === 1) {
|
||||
this.scrollToBottom();
|
||||
scrollToBottom();
|
||||
|
||||
// 首次滚动隐藏
|
||||
setTimeout(done, 0);
|
||||
@ -297,30 +293,53 @@ export default {
|
||||
done();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(err);
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
done();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
onLoadmore() {
|
||||
this.refresh({ page: this.pagination.page + 1 });
|
||||
},
|
||||
function onLoadmore() {
|
||||
refresh({ page: pagination.page + 1 });
|
||||
}
|
||||
|
||||
// 监听列表刷新
|
||||
mitt.on("message.refresh", refresh);
|
||||
// 滚动到底部
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs["scroller"]) {
|
||||
this.$refs["scroller"].scrollTo({
|
||||
top: 99999,
|
||||
behavior: this.visible ? "smooth" : "auto"
|
||||
mitt.on("message.scrollToBottom", scrollToBottom);
|
||||
|
||||
// 销毁
|
||||
onUnmounted(function() {
|
||||
// 移除语音播放
|
||||
clearTimeout(voice.timer);
|
||||
|
||||
list.value.map((e: any) => {
|
||||
e.isPlay = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 移除事件
|
||||
mitt.off("message.refresh", refresh);
|
||||
mitt.off("message.scrollToBottom", scrollToBottom);
|
||||
});
|
||||
|
||||
return {
|
||||
refs,
|
||||
chat,
|
||||
session,
|
||||
loading,
|
||||
visible,
|
||||
pagination,
|
||||
voice,
|
||||
list,
|
||||
setRefs,
|
||||
onTap,
|
||||
refresh,
|
||||
onLoadmore,
|
||||
scrollToBottom
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -494,7 +513,7 @@ export default {
|
||||
.content {
|
||||
background-color: #fff;
|
||||
|
||||
/deep/.el-image {
|
||||
:deep(.el-image) {
|
||||
display: block;
|
||||
border-radius: 6px;
|
||||
max-width: 200px;
|
||||
|
@ -51,7 +51,7 @@ export default {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/deep/.el-badge {
|
||||
:deep(.el-badge) {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
size="small"
|
||||
clearable
|
||||
@clear="onSearch"
|
||||
@keyup.enter.native="onSearch"
|
||||
@keyup.enter="onSearch"
|
||||
></el-input>
|
||||
</div>
|
||||
|
||||
@ -52,79 +52,128 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import { isEmpty } from "cl-admin/utils";
|
||||
import { ContextMenu } from "cl-admin-crud";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, onUnmounted, reactive, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { isEmpty } from "@/core/utils";
|
||||
import { ContextMenu } from "@/crud";
|
||||
import { parseContent } from "../utils";
|
||||
import eventBus from "../utils/event-bus";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
pagination: {
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const $service = inject<any>("$service");
|
||||
const mitt = inject<any>("mitt");
|
||||
|
||||
// 当前会话信息
|
||||
const session = computed(() => store.getters.session);
|
||||
// 是否显示会话列表
|
||||
const sessionVisible = computed(() => store.getters.sessionVisible);
|
||||
// 浏览器信息
|
||||
const browser = computed(() => store.getters.browser);
|
||||
|
||||
// 加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive<any>({
|
||||
page: 1,
|
||||
size: 100,
|
||||
total: 0
|
||||
},
|
||||
keyWord: ""
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
computed: {
|
||||
...mapGetters(["sessionList", "session", "browser", "sessionVisible"]),
|
||||
// 关键字筛选
|
||||
const keyWord = ref<string>("");
|
||||
|
||||
// 列表数据
|
||||
list() {
|
||||
return this.sessionList
|
||||
.map(e => {
|
||||
const { _text } = parseContent(e);
|
||||
e.lastMessage = _text;
|
||||
return e;
|
||||
// 刷新列表
|
||||
function refresh(params?: any) {
|
||||
loading.value = true;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
$service.im.session
|
||||
.page({
|
||||
...pagination,
|
||||
keyWord: keyWord.value,
|
||||
params,
|
||||
order: "updateTime",
|
||||
sort: "desc"
|
||||
})
|
||||
.sort((a, b) => {
|
||||
.then((res: any) => {
|
||||
store.commit("SET_SESSION_LIST", res.list);
|
||||
Object.assign(pagination, res.pagination);
|
||||
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err: string) => {
|
||||
ElMessage.error(err);
|
||||
reject(err);
|
||||
})
|
||||
.done(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索关键字
|
||||
function onSearch() {
|
||||
refresh({ page: 1 });
|
||||
}
|
||||
|
||||
// 设置会话
|
||||
function setSession(item: any) {
|
||||
if (item) {
|
||||
store.commit("SET_SESSION", item);
|
||||
mitt.emit("message.refresh", { page: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
// 会话详情
|
||||
function toDetail(item?: any) {
|
||||
if (item) {
|
||||
// 点击关闭弹窗
|
||||
if (browser.value.isMini) store.commit("CLOSE_SESSION");
|
||||
|
||||
// 设置为当前会话
|
||||
if (!session.value || session.value.id != item.id) {
|
||||
setSession(item);
|
||||
}
|
||||
} else {
|
||||
store.commit("CLEAR_SESSION");
|
||||
}
|
||||
}
|
||||
|
||||
// 数据列表
|
||||
const list = computed(() => {
|
||||
return store.getters.sessionList
|
||||
.map((e: any) => {
|
||||
const { _text } = parseContent(e);
|
||||
return {
|
||||
...e,
|
||||
lastMessage: _text
|
||||
};
|
||||
})
|
||||
.sort((a: any, b: any) => {
|
||||
return a.updateTime < b.updateTime ? 1 : -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
// 销毁事件
|
||||
eventBus.$off("session.refresh");
|
||||
},
|
||||
|
||||
created() {
|
||||
// 监听列表刷新
|
||||
eventBus.$on("session.refresh", this.refresh);
|
||||
|
||||
// PC 端下首次请求读取第一个消息
|
||||
this.refresh().then(res => {
|
||||
if (!isEmpty(res.list) && !this.browser.isMini) {
|
||||
this.SET_SESSION(res.list[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations(["SET_SESSION_LIST", "SET_SESSION", "CLEAR_SESSION", "CLOSE_SESSION"]),
|
||||
|
||||
// 右键菜单
|
||||
openCM(e, id, index) {
|
||||
function openCM(e: any, id: any, index: number) {
|
||||
ContextMenu.open(e, {
|
||||
list: [
|
||||
{
|
||||
label: "删除",
|
||||
icon: "el-icon-delete",
|
||||
callback: (_, done) => {
|
||||
this.$service.im.session.delete({
|
||||
callback: (_: any, done: Function) => {
|
||||
$service.im.session.delete({
|
||||
ids: id
|
||||
});
|
||||
|
||||
this.list.splice(index, 1);
|
||||
list.value.splice(index, 1);
|
||||
|
||||
if (id == this.session.id) {
|
||||
this.toDetail();
|
||||
if (id == session.value.id) {
|
||||
toDetail();
|
||||
}
|
||||
|
||||
done();
|
||||
@ -132,58 +181,38 @@ export default {
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
refresh(params) {
|
||||
this.loading = true;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$service.im.session
|
||||
.page({
|
||||
...this.pagination,
|
||||
keyWord: this.keyWord,
|
||||
params,
|
||||
order: "updateTime",
|
||||
sort: "desc"
|
||||
})
|
||||
.then(res => {
|
||||
this.SET_SESSION_LIST(res.list);
|
||||
this.pagination = res.pagination;
|
||||
|
||||
resolve(res);
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err);
|
||||
reject(err);
|
||||
})
|
||||
.done(() => {
|
||||
this.loading = false;
|
||||
// PC 端下首次请求读取第一个消息
|
||||
refresh().then((res: any) => {
|
||||
if (!isEmpty(res.list) && !browser.value.isMini) {
|
||||
setSession(res.list[0]);
|
||||
}
|
||||
});
|
||||
|
||||
// 事件监听
|
||||
mitt.on("session.refresh", refresh);
|
||||
|
||||
// 销毁
|
||||
onUnmounted(function() {
|
||||
mitt.off("session.refresh", refresh);
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索关键字
|
||||
onSearch() {
|
||||
this.refresh({ page: 1 });
|
||||
},
|
||||
|
||||
// 会话详情
|
||||
toDetail(item) {
|
||||
if (item) {
|
||||
// 点击关闭弹窗
|
||||
if (this.browser.isMini) this.CLOSE_SESSION();
|
||||
|
||||
// 设置为当前会话
|
||||
if (!this.session || this.session.id != item.id) {
|
||||
this.SET_SESSION(item);
|
||||
return {
|
||||
session,
|
||||
sessionVisible,
|
||||
browser,
|
||||
list,
|
||||
loading,
|
||||
pagination,
|
||||
keyWord,
|
||||
openCM,
|
||||
refresh,
|
||||
onSearch,
|
||||
toDetail
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.CLEAR_SESSION();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service({
|
||||
namespace: "im/message",
|
||||
@ -6,7 +6,7 @@ import { BaseService, Service, Permission } from "cl-admin";
|
||||
})
|
||||
class ImMessage extends BaseService {
|
||||
@Permission("read")
|
||||
read(data) {
|
||||
read(data: any) {
|
||||
return this.request({
|
||||
url: "/read",
|
||||
method: "POST",
|
@ -1,4 +1,4 @@
|
||||
import { BaseService, Service, Permission } from "cl-admin";
|
||||
import { BaseService, Service, Permission } from "@/core";
|
||||
|
||||
@Service({
|
||||
namespace: "im/session",
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user