update prettier 替换为 oxfmt 格式化整个项目的代码结构

This commit is contained in:
疯狂的狮子Li
2026-04-01 17:03:20 +08:00
parent 199771997d
commit 16d9e1e7fe
131 changed files with 3750 additions and 1571 deletions
+1 -1
View File
@@ -25,4 +25,4 @@ description: 前端 API 与类型定义专家。用于当前项目中的 src/api
- API 路径是否与后端一致 - API 路径是否与后端一致
- 类型是否覆盖接口真实结构 - 类型是否覆盖接口真实结构
- 是否不必要地把类型写宽了 - 是否不必要地把类型写宽了
+1 -1
View File
@@ -15,4 +15,4 @@ description: 前端总入口。用于当前前端项目中的标准 CRUD 页面
- 先读当前目录下最近似页面和 API,再动代码。 - 先读当前目录下最近似页面和 API,再动代码。
- 冲突时优先相信当前项目真实页面,其次是公共组件和工具,再其次才是关联后端工程的 generator 模板。 - 冲突时优先相信当前项目真实页面,其次是公共组件和工具,再其次才是关联后端工程的 generator 模板。
- 默认直接产出可落地代码,而不是只给抽象建议。 - 默认直接产出可落地代码,而不是只给抽象建议。
+1 -1
View File
@@ -34,4 +34,4 @@ description: 前端标准 CRUD 页面专家。用于当前项目中的新建列
- API 路径是否与后端一致 - API 路径是否与后端一致
- `index.ts``types.ts` 是否同步补齐 - `index.ts``types.ts` 是否同步补齐
- 页面是否只是模板裸输出,如果是要继续补强到当前项目风格 - 页面是否只是模板裸输出,如果是要继续补强到当前项目风格
+1 -1
View File
@@ -24,4 +24,4 @@ description: 复杂前端页面增强专家。用于修改当前项目中已经
- 是否破坏了原页面结构和样式 - 是否破坏了原页面结构和样式
- 是否误删了已有权限控制或交互能力 - 是否误删了已有权限控制或交互能力
- 是否应该拆成子组件而不是继续堆主页面 - 是否应该拆成子组件而不是继续堆主页面
+1 -1
View File
@@ -133,4 +133,4 @@ description: 在当前前端项目中按现有 Vue 3 + TypeScript + Element Plus
例如: 例如:
- 使用 `$frontend-crud-coding``/system/client` 补一套标准 CRUD 页面,参考 `system/user` 和 generator 模板。 - 使用 `$frontend-crud-coding``/system/client` 补一套标准 CRUD 页面,参考 `system/user` 和 generator 模板。
- 使用 `$frontend-crud-coding` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前项目风格。 - 使用 `$frontend-crud-coding` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前项目风格。
@@ -109,4 +109,4 @@
- 业务名 - 业务名
- 后端接口前缀 - 后端接口前缀
- 是新增还是修改 - 是新增还是修改
- 想参考哪个现有页面 - 想参考哪个现有页面
@@ -238,4 +238,4 @@
- 类型是否与接口返回结构一致。 - 类型是否与接口返回结构一致。
- 是否保留了原页面已有的权限和交互能力。 - 是否保留了原页面已有的权限和交互能力。
- 是否沿用了当前模块已有的组件和样式壳。 - 是否沿用了当前模块已有的组件和样式壳。
- 是否只是“生成器裸页”,如果是,需要继续补齐到当前项目风格。 - 是否只是“生成器裸页”,如果是,需要继续补齐到当前项目风格。
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
"src/types/components.d.ts",
"src/types/auto-imports.d.ts",
".ai_state",
".claude",
".codex",
"doc"
],
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"experimentalSortPackageJson": {
"sortScripts": true
}
}
+6 -3
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"plugins": ["vue", "typescript"], "plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "vue"],
"rules": { "rules": {
"typescript/no-empty-function": "off", "typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off", "typescript/no-explicit-any": "off",
@@ -8,8 +8,11 @@
"typescript/no-this-alias": "off", "typescript/no-this-alias": "off",
"typescript/no-empty-object-type": "off", "typescript/no-empty-object-type": "off",
"typescript/no-unused-expressions": "off", "typescript/no-unused-expressions": "off",
"vue/multi-word-component-names": "off",
"prefer-rest-params": "off" "prefer-rest-params": "off"
}, },
"ignore": ["dist/**", "dist-ssr/**", "coverage/**"] "categories": {
"correctness": "error",
"suspicious": "error"
},
"ignorePatterns": [".ai_state", ".claude", ".codex", "doc", "dist/**", "dist-ssr/**", "coverage/**"]
} }
-9
View File
@@ -1,9 +0,0 @@
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
-20
View File
@@ -1,20 +0,0 @@
{
"printWidth": 150,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "preserve",
"jsxSingleQuote": false,
"bracketSameLine": false,
"trailingComma": "none",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "auto"
}
+4 -4
View File
@@ -6,10 +6,10 @@
## 配套后端代码仓库地址 ## 配套后端代码仓库地址
| 介绍 | 项目名 | 项目地址 | | 介绍 | 项目名 | 项目地址 |
|------------|:-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ----------------- | :--------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) | | 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) |
| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) | | 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
## 分支说明 ## 分支说明
@@ -78,4 +78,4 @@ pnpm build:prod
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png '屏幕截图') | | ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png '屏幕截图') | | ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png '屏幕截图') | | ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png '屏幕截图') | | ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png '屏幕截图') |
+25 -9
View File
@@ -144,7 +144,8 @@
font-size: 14px; font-size: 14px;
line-height: 24px; line-height: 24px;
color: #454545; color: #454545;
font-family: 'Microsoft YaHei UI', 'Microsoft YaHei', DengXian, SimSun, 'Segoe UI', Tahoma, Helvetica, sans-serif; font-family:
'Microsoft YaHei UI', 'Microsoft YaHei', DengXian, SimSun, 'Segoe UI', Tahoma, Helvetica, sans-serif;
overflow-y: scroll; overflow-y: scroll;
} }
h1 { h1 {
@@ -208,15 +209,18 @@
</head> </head>
<body style="margin-top: 50px"> <body style="margin-top: 50px">
<h1>请升级您的浏览器,以便我们更好的为您提供服务!</h1> <h1>请升级您的浏览器,以便我们更好的为您提供服务!</h1>
<p>您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。</p> <p>
您正在使用 Internet Explorer
的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。
</p>
<hr /> <hr />
<h2>请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束</h2> <h2>请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束</h2>
<p> <p>
自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11
以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅
<a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support" <a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support">
>微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明</a 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明
> </a>
</p> </p>
<hr /> <hr />
@@ -224,16 +228,28 @@
<p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p> <p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p>
<ul class="browser"> <ul class="browser">
<li class="browser-chrome"> <li class="browser-chrome">
<a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1"> 谷歌浏览器<span>Google Chrome</span></a> <a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1">
谷歌浏览器
<span>Google Chrome</span>
</a>
</li> </li>
<li class="browser-firefox"> <li class="browser-firefox">
<a href="https://www.mozilla.org/zh-CN/firefox/new/"> 火狐浏览器<span>Mozilla Firefox</span></a> <a href="https://www.mozilla.org/zh-CN/firefox/new/">
火狐浏览器
<span>Mozilla Firefox</span>
</a>
</li> </li>
<li class="browser-ie"> <li class="browser-ie">
<a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie"> IE 11 浏览器<span>Internet Explorer</span></a> <a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie">
IE 11 浏览器
<span>Internet Explorer</span>
</a>
</li> </li>
<li class="browser-360"> <li class="browser-360">
<a href="http://se.360.cn/"> 360安全浏览器<span>360 Chrome</span></a> <a href="http://se.360.cn/">
360安全浏览器
<span>360 Chrome</span>
</a>
</li> </li>
<div class="clean"></div> <div class="clean"></div>
</ul> </ul>
+4 -4
View File
@@ -7,11 +7,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<title>%VITE_APP_TITLE%</title> <title>%VITE_APP_TITLE%</title>
<!--[if lt IE 11 <!--[if lt IE 11]>
]><script> <script>
window.location.href = '/html/ie.html'; window.location.href = '/html/ie.html';
</script><! </script>
[endif]--> <![endif]-->
<style> <style>
html, html,
body, body,
+24 -20
View File
@@ -3,22 +3,22 @@
"name": "ruoyi-vue-plus", "name": "ruoyi-vue-plus",
"version": "5.5.3-2.5.3", "version": "5.5.3-2.5.3",
"description": "RuoYi-Vue-Plus后台管理系统", "description": "RuoYi-Vue-Plus后台管理系统",
"author": "LionLi",
"license": "MIT", "license": "MIT",
"type": "module", "author": "LionLi",
"scripts": {
"dev": "vite serve --mode development",
"build:prod": "vite build --mode production",
"build:dev": "vite build --mode development",
"preview": "vite preview",
"lint": "oxlint src",
"lint:fix": "oxlint --fix src",
"prettier": "prettier --write ."
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitee.com/JavaLionLi/plus-ui.git" "url": "https://gitee.com/JavaLionLi/plus-ui.git"
}, },
"type": "module",
"scripts": {
"build:dev": "vite build --mode development",
"build:prod": "vite build --mode production",
"dev": "vite serve --mode development",
"fmt": "oxfmt .",
"lint": "oxlint src",
"lint:fix": "oxlint --fix src",
"preview": "vite preview"
},
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.2", "@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2", "@highlightjs/vue-plugin": "2.1.2",
@@ -54,8 +54,8 @@
"@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue": "^6.0.5",
"@vue/compiler-sfc": "3.5.30", "@vue/compiler-sfc": "3.5.30",
"autoprefixer": "10.4.27", "autoprefixer": "10.4.27",
"oxfmt": "^0.43.0",
"oxlint": "^1.58.0", "oxlint": "^1.58.0",
"prettier": "3.8.1",
"sass": "1.98.0", "sass": "1.98.0",
"typescript": "^6.0.2", "typescript": "^6.0.2",
"unocss": "66.6.6", "unocss": "66.6.6",
@@ -67,17 +67,21 @@
"vitest": "4.0.18", "vitest": "4.0.18",
"vue-tsc": "^3.2.5" "vue-tsc": "^3.2.5"
}, },
"pnpm": {
"onlyBuiltDependencies": ["@parcel/watcher", "es5-ext", "esbuild"]
},
"engines": {
"node": ">=20.19.0",
"pnpm": ">=10.0.0"
},
"browserslist": [ "browserslist": [
"Chrome >= 87", "Chrome >= 87",
"Edge >= 88", "Edge >= 88",
"Safari >= 14", "Safari >= 14",
"Firefox >= 78" "Firefox >= 78"
] ],
"engines": {
"node": ">=20.19.0",
"pnpm": ">=10.0.0"
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"es5-ext",
"esbuild"
]
}
} }
+4 -1
View File
@@ -53,7 +53,10 @@ export function register(data: any) {
*/ */
export function logout() { export function logout() {
closePush(); closePush();
if (import.meta.env.VITE_APP_MESSAGE_ENABLED === 'true' && import.meta.env.VITE_APP_MESSAGE_TRANSPORT.toLowerCase() === 'sse') { if (
import.meta.env.VITE_APP_MESSAGE_ENABLED === 'true' &&
import.meta.env.VITE_APP_MESSAGE_TRANSPORT.toLowerCase() === 'sse'
) {
request({ request({
url: import.meta.env.VITE_APP_MESSAGE_PATH + '/close', url: import.meta.env.VITE_APP_MESSAGE_PATH + '/close',
method: 'get' method: 'get'
+1 -1
View File
@@ -118,7 +118,7 @@ export const changeUserStatus = (userId: number | string, status: string) => {
export const unlockUser = (userId: number | string) => { export const unlockUser = (userId: number | string) => {
return request({ return request({
url: '/system/user/unlock/' + userId, url: '/system/user/unlock/' + userId,
method: 'get', method: 'get'
}); });
}; };
+6 -1
View File
@@ -1,5 +1,10 @@
import request from '@/utils/request'; import request from '@/utils/request';
import { FlowDefinitionQuery, definitionXmlVO, FlowDefinitionForm, FlowDefinitionVo } from '@/api/workflow/definition/types'; import {
FlowDefinitionQuery,
definitionXmlVO,
FlowDefinitionForm,
FlowDefinitionVo
} from '@/api/workflow/definition/types';
import { AxiosPromise } from 'axios'; import { AxiosPromise } from 'axios';
import { PageResult } from '@/api/types'; import { PageResult } from '@/api/types';
+4 -10
View File
@@ -33,7 +33,6 @@ export interface SpelVO {
* 备注 * 备注
*/ */
remark?: string; remark?: string;
} }
export interface SpelForm extends BaseEntity { export interface SpelForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface SpelForm extends BaseEntity {
* 备注 * 备注
*/ */
remark?: string; remark?: string;
} }
export interface SpelQuery extends PageQuery { export interface SpelQuery extends PageQuery {
/** /**
* 组件名称 * 组件名称
*/ */
@@ -101,11 +98,8 @@ export interface SpelQuery extends PageQuery {
*/ */
status?: string; status?: string;
/** /**
* 日期范围参数 * 日期范围参数
*/ */
params?: any; params?: any;
} }
+2 -8
View File
@@ -9,14 +9,8 @@ body {
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
background: var(--app-shell-bg); background: var(--app-shell-bg);
font-family: font-family:
'MiSans', 'MiSans', 'HarmonyOS Sans SC', 'PingFang SC', 'Source Han Sans SC', 'Noto Sans SC', 'Hiragino Sans GB',
'HarmonyOS Sans SC', 'Microsoft YaHei', sans-serif;
'PingFang SC',
'Source Han Sans SC',
'Noto Sans SC',
'Hiragino Sans GB',
'Microsoft YaHei',
sans-serif;
} }
label { label {
@@ -34,7 +34,6 @@
margin-right: 8px; margin-right: 8px;
} }
// --- el-card 本业务页外观(与 EP vendors 叠加) --- // --- el-card 本业务页外观(与 EP vendors 叠加) ---
.el-card__header { .el-card__header {
padding: 14px 16px 10px !important; padding: 14px 16px 10px !important;
@@ -52,7 +51,9 @@
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
border-color: var(--app-surface-border); border-color: var(--app-surface-border);
overflow: hidden; overflow: hidden;
transition: box-shadow 0.2s ease, border-color 0.2s ease; transition:
box-shadow 0.2s ease,
border-color 0.2s ease;
background: var(--app-surface-bg); background: var(--app-surface-bg);
} }
@@ -1,6 +1,5 @@
/* 工具类 */ /* 工具类 */
.pt5 { .pt5 {
padding-top: 5px; padding-top: 5px;
} }
@@ -9,7 +8,6 @@
padding-right: 5px; padding-right: 5px;
} }
.pl5 { .pl5 {
padding-left: 5px; padding-left: 5px;
} }
@@ -70,14 +68,9 @@
margin-left: 20px; margin-left: 20px;
} }
.el-dialog.scrollbar .el-dialog__body { .el-dialog.scrollbar .el-dialog__body {
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
max-height: 70vh; max-height: 70vh;
padding: 14px 22px 4px; padding: 14px 22px 4px;
} }
-4
View File
@@ -1,6 +1,3 @@
.link-type, .link-type,
.link-type:focus { .link-type:focus {
color: #337ab7; color: #337ab7;
@@ -10,4 +7,3 @@
color: rgb(32, 160, 255); color: rgb(32, 160, 255);
} }
} }
@@ -63,7 +63,9 @@
.tree-panel-col, .tree-panel-col,
.tree-content-col { .tree-content-col {
min-width: 0; min-width: 0;
transition: max-width 0.24s ease, flex-basis 0.24s ease; transition:
max-width 0.24s ease,
flex-basis 0.24s ease;
} }
.tree-panel-col.is-collapsed { .tree-panel-col.is-collapsed {
@@ -184,7 +186,12 @@
@include toolbar-responsive($mobile-breakpoint); @include toolbar-responsive($mobile-breakpoint);
} }
@mixin tree-table-crud-page($mobile-breakpoint: 900px, $gap: 12px, $tree-padding-top: 6px, $background: rgba(53, 109, 255, 0.08)) { @mixin tree-table-crud-page(
$mobile-breakpoint: 900px,
$gap: 12px,
$tree-padding-top: 6px,
$background: rgba(53, 109, 255, 0.08)
) {
@include content-stack($gap); @include content-stack($gap);
@include dept-tree-panel($tree-padding-top); @include dept-tree-panel($tree-padding-top);
@include collapsible-tree-layout($mobile-breakpoint); @include collapsible-tree-layout($mobile-breakpoint);
@@ -47,7 +47,3 @@
.text-muted { .text-muted {
color: #888888; color: #888888;
} }
+3 -2
View File
@@ -2,7 +2,6 @@
// --- 整块内容容器(常见于路由视图外包一层) --- // --- 整块内容容器(常见于路由视图外包一层) ---
.app-container { .app-container {
} }
// --- 可复用面板条(hover 抬升阴影,非 el-card --- // --- 可复用面板条(hover 抬升阴影,非 el-card ---
@@ -14,7 +13,9 @@
background-color: var(--app-surface-bg); background-color: var(--app-surface-bg);
padding: 0.75rem; padding: 0.75rem;
box-shadow: 0 0 0 rgba(0, 0, 0, 0); box-shadow: 0 0 0 rgba(0, 0, 0, 0);
transition: box-shadow 0.2s ease, border-color 0.2s ease; transition:
box-shadow 0.2s ease,
border-color 0.2s ease;
&:hover { &:hover {
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
-1
View File
@@ -1,4 +1,3 @@
// 清除浮动(嵌套用) // 清除浮动(嵌套用)
@mixin clearfix { @mixin clearfix {
&:after { &:after {
+1 -1
View File
@@ -10,4 +10,4 @@
.el-card .el-card__body { .el-card .el-card__body {
border-bottom-left-radius: var(--app-radius-lg); border-bottom-left-radius: var(--app-radius-lg);
border-bottom-right-radius: var(--app-radius-lg); border-bottom-right-radius: var(--app-radius-lg);
} }
-2
View File
@@ -98,5 +98,3 @@
justify-content: flex-end; justify-content: flex-end;
gap: 12px; gap: 12px;
} }
@@ -1,6 +1,3 @@
:root { :root {
--el-font-size-base: var(--app-font-size-base);
--el-font-size-base: var(--app-font-size-base) }
}
+8 -2
View File
@@ -29,7 +29,10 @@
.el-input-number__decrease, .el-input-number__decrease,
.el-input-number__increase { .el-input-number__increase {
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
transition: box-shadow 0.2s ease, border-color 0.2s ease, background-color 0.2s ease; transition:
box-shadow 0.2s ease,
border-color 0.2s ease,
background-color 0.2s ease;
} }
.el-input__wrapper.is-focus, .el-input__wrapper.is-focus,
@@ -42,7 +45,10 @@
.el-button { .el-button {
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
transition: box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; transition:
box-shadow 0.2s ease,
background-color 0.2s ease,
border-color 0.2s ease;
} }
.el-button:not(.is-text):not(.is-link):hover { .el-button:not(.is-text):not(.is-link):hover {
+1 -1
View File
@@ -9,4 +9,4 @@
@use './shell'; @use './shell';
@use './navigation'; @use './navigation';
@use './table'; @use './table';
@use './card'; @use './card';
+4 -1
View File
@@ -17,7 +17,10 @@
.el-pagination .btn-next, .el-pagination .btn-next,
.el-pagination .el-pager li { .el-pagination .el-pager li {
border-radius: var(--app-radius-sm); border-radius: var(--app-radius-sm);
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; transition:
background-color 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease;
} }
.el-pagination { .el-pagination {
-4
View File
@@ -46,8 +46,6 @@
border-right: 0; border-right: 0;
} }
// --- 表格内操作列:固定宽按钮、下拉链接色 --- // --- 表格内操作列:固定宽按钮、下拉链接色 ---
.el-table .fixed-width .el-button--small { .el-table .fixed-width .el-button--small {
padding-left: 0; padding-left: 0;
@@ -61,7 +59,6 @@
margin-left: 10px; margin-left: 10px;
} }
.el-table { .el-table {
.el-table__header-wrapper, .el-table__header-wrapper,
.el-table__fixed-header-wrapper { .el-table__fixed-header-wrapper {
@@ -80,4 +77,3 @@
} }
} }
} }
+7 -5
View File
@@ -2,7 +2,9 @@
<el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path"> <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta?.title }}</span> <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">
{{ item.meta?.title }}
</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a> <a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
@@ -31,20 +33,20 @@ const getBreadcrumb = () => {
}); });
getMatched(pathList, permissionStore.defaultRoutes, matched); getMatched(pathList, permissionStore.defaultRoutes, matched);
} else { } else {
matched = route.matched.filter((item) => item.meta && item.meta.title); matched = route.matched.filter(item => item.meta && item.meta.title);
} }
// 判断是否为首页 // 判断是否为首页
if (!isDashboard(matched[0])) { if (!isDashboard(matched[0])) {
matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched); matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched);
} }
levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false); levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false);
}; };
const findPathNum = (str, char = '/') => { const findPathNum = (str, char = '/') => {
if (typeof str !== 'string' || str.length === 0) return 0; if (typeof str !== 'string' || str.length === 0) return 0;
return str.split(char).length - 1; return str.split(char).length - 1;
}; };
const getMatched = (pathList, routeList, matched) => { const getMatched = (pathList, routeList, matched) => {
const data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]); const data = routeList.find(item => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
if (data) { if (data) {
matched.push(data); matched.push(data);
if (data.children && pathList.length) { if (data.children && pathList.length) {
@@ -60,7 +62,7 @@ const isDashboard = (route: RouteLocationMatched) => {
} }
return name.trim() === 'Index'; return name.trim() === 'Index';
}; };
const handleLink = (item) => { const handleLink = item => {
const { redirect, path } = item; const { redirect, path } = item;
redirect ? router.push(redirect) : router.push(path); redirect ? router.push(redirect) : router.push(path);
}; };
+14 -10
View File
@@ -3,7 +3,10 @@
<template v-for="(item, index) in options"> <template v-for="(item, index) in options">
<template v-if="isValueMatch(item.value)"> <template v-if="isValueMatch(item.value)">
<span <span
v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)" v-if="
(item.elTagType === 'default' || item.elTagType === '') &&
(item.elTagClass === '' || item.elTagClass == null)
"
:key="item.value" :key="item.value"
:index="index" :index="index"
:class="item.elTagClass" :class="item.elTagClass"
@@ -50,16 +53,17 @@ const props = withDefaults(defineProps<Props>(), {
const values = computed(() => { const values = computed(() => {
if (props.value === '' || props.value === null || typeof props.value === 'undefined') return []; if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value] if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value];
return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator); return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
}); });
const unmatch = computed(() => { const unmatch = computed(() => {
if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === 'undefined') return false; if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === 'undefined')
return false;
// 传入值为非数组 // 传入值为非数组
let unmatch = false; // 添加一个标志来判断是否有未匹配项 let unmatch = false; // 添加一个标志来判断是否有未匹配项
values.value.forEach((item) => { values.value.forEach(item => {
if (!props.options.some((v) => v.value == item)) { if (!props.options.some(v => v.value == item)) {
unmatch = true; // 如果有未匹配项,将标志设置为true unmatch = true; // 如果有未匹配项,将标志设置为true
} }
}); });
@@ -70,8 +74,8 @@ const unmatchArray = computed(() => {
// 记录未匹配的项 // 记录未匹配的项
const itemUnmatchArray: Array<string | number> = []; const itemUnmatchArray: Array<string | number> = [];
if (props.value !== '' && props.value !== null && typeof props.value !== 'undefined') { if (props.value !== '' && props.value !== null && typeof props.value !== 'undefined') {
values.value.forEach((item) => { values.value.forEach(item => {
if (!props.options.some((v) => v.value === item)) { if (!props.options.some(v => v.value === item)) {
itemUnmatchArray.push(item); itemUnmatchArray.push(item);
} }
}); });
@@ -88,8 +92,8 @@ const handleArray = (array: Array<string | number>) => {
}; };
const isValueMatch = (itemValue: any) => { const isValueMatch = (itemValue: any) => {
return values.value.some(val => val == itemValue) return values.value.some(val => val == itemValue);
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+3 -8
View File
@@ -1,11 +1,6 @@
<template> <template>
<div class="editor-shell"> <div class="editor-shell">
<EditorToolbar <EditorToolbar :editor="editorRef" :default-config="toolbarConfig" mode="default" class="editor-toolbar" />
:editor="editorRef"
:default-config="toolbarConfig"
mode="default"
class="editor-toolbar"
/>
<div class="editor-body" :style="styles"> <div class="editor-body" :style="styles">
<WangEditor <WangEditor
v-model="content" v-model="content"
@@ -139,7 +134,7 @@ const syncReadOnly = () => {
watch( watch(
() => props.modelValue, () => props.modelValue,
(value) => { value => {
const nextValue = value || ''; const nextValue = value || '';
if (nextValue !== content.value) { if (nextValue !== content.value) {
content.value = nextValue; content.value = nextValue;
@@ -148,7 +143,7 @@ watch(
{ immediate: true } { immediate: true }
); );
watch(content, (value) => { watch(content, value => {
if (value !== props.modelValue) { if (value !== props.modelValue) {
emit('update:modelValue', value); emit('update:modelValue', value);
} }
+11 -9
View File
@@ -23,10 +23,12 @@
<div v-if="showTip && !disabled" class="el-upload__tip"> <div v-if="showTip && !disabled" class="el-upload__tip">
请上传 请上传
<template v-if="fileSize"> <template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> 大小不超过
<b style="color: #f56c6c">{{ fileSize }}MB</b>
</template> </template>
<template v-if="fileType"> <template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 格式为
<b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</template> </template>
的文件 的文件
</div> </div>
@@ -34,7 +36,7 @@
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content"> <li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content">
<el-link :href="`${file.url}`" :underline="false" target="_blank"> <el-link :href="`${file.url}`" :underline="false" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span> <span class="el-icon-document">{{ getFileName(file.name) }}</span>
</el-link> </el-link>
<div class="ele-upload-list__item-content-action"> <div class="ele-upload-list__item-content-action">
<el-button type="danger" v-if="!disabled" link @click="handleDelete(index)">删除</el-button> <el-button type="danger" v-if="!disabled" link @click="handleDelete(index)">删除</el-button>
@@ -81,11 +83,11 @@ const showTip = computed(() => props.isShowTip && (props.fileType || props.fileS
const fileUploadRef = ref<ElUploadInstance>(); const fileUploadRef = ref<ElUploadInstance>();
// 监听 fileType 变化,更新 fileAccept // 监听 fileType 变化,更新 fileAccept
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(',')); const fileAccept = computed(() => props.fileType.map(type => `.${type}`).join(','));
watch( watch(
() => props.modelValue, () => props.modelValue,
async (val) => { async val => {
if (val) { if (val) {
let temp = 1; let temp = 1;
// 首先将值转为数组 // 首先将值转为数组
@@ -94,7 +96,7 @@ watch(
list = val; list = val;
} else { } else {
const res = await listByIds(val); const res = await listByIds(val);
list = res.data.map((oss) => { list = res.data.map(oss => {
return { return {
name: oss.originalName, name: oss.originalName,
url: oss.url, url: oss.url,
@@ -103,7 +105,7 @@ watch(
}); });
} }
// 然后将数组转为对象数组 // 然后将数组转为对象数组
fileList.value = list.map((item) => { fileList.value = list.map(item => {
item = { name: item.name, url: item.url, ossId: item.ossId }; item = { name: item.name, url: item.url, ossId: item.ossId };
item.uid = item.uid || new Date().getTime() + temp++; item.uid = item.uid || new Date().getTime() + temp++;
return item; return item;
@@ -185,7 +187,7 @@ const handleDelete = (index: number) => {
// 上传结束处理 // 上传结束处理
const uploadedSuccessfully = () => { const uploadedSuccessfully = () => {
if (number.value > 0 && uploadList.value.length === number.value) { if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value); fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = []; uploadList.value = [];
number.value = 0; number.value = 0;
emit('update:modelValue', listToString(fileList.value)); emit('update:modelValue', listToString(fileList.value));
@@ -207,7 +209,7 @@ const getFileName = (name: string) => {
const listToString = (list: any[], separator?: string) => { const listToString = (list: any[], separator?: string) => {
let strs = ''; let strs = '';
separator = separator || ','; separator = separator || ',';
list.forEach((item) => { list.forEach(item => {
if (item.ossId) { if (item.ossId) {
strs += item.ossId + separator; strs += item.ossId + separator;
} }
+8 -1
View File
@@ -1,6 +1,13 @@
<template> <template>
<div style="padding: 0 15px" @click="toggleClick"> <div style="padding: 0 15px" @click="toggleClick">
<svg :class="{ 'is-active': isActive }" class="hamburger" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"> <svg
:class="{ 'is-active': isActive }"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path <path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/> />
+14 -5
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="relative" :style="{ 'width': width }"> <div class="relative" :style="{ width: width }">
<el-input v-model="modelValue" readonly placeholder="点击选择图标" @click="visible = !visible"> <el-input v-model="modelValue" readonly placeholder="点击选择图标" @click="visible = !visible">
<template #prepend> <template #prepend>
<svg-icon :icon-class="modelValue" /> <svg-icon :icon-class="modelValue" />
@@ -8,7 +8,10 @@
<el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" :width="450"> <el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" :width="450">
<template #reference> <template #reference>
<div class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]" @click="visible = !visible"> <div
class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]"
@click="visible = !visible"
>
<CaretTop v-show="visible" /> <CaretTop v-show="visible" />
<CaretBottom v-show="!visible" /> <CaretBottom v-show="!visible" />
</div> </div>
@@ -31,7 +34,13 @@
<el-scrollbar height="w-[200px]"> <el-scrollbar height="w-[200px]">
<ul class="icon-list"> <ul class="icon-list">
<el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light"> <el-tooltip
v-for="(iconName, index) in iconNames"
:key="index"
:content="iconName"
placement="bottom"
effect="light"
>
<li :class="['icon-item', { active: modelValue == iconName }]" @click="selectedIcon(iconName)"> <li :class="['icon-item', { active: modelValue == iconName }]" @click="selectedIcon(iconName)">
<svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" /> <svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" />
</li> </li>
@@ -64,7 +73,7 @@ const customIcon = ref('');
*/ */
const filterIcons = () => { const filterIcons = () => {
if (filterValue.value) { if (filterValue.value) {
iconNames.value = icons.filter((iconName) => iconName.includes(filterValue.value)); iconNames.value = icons.filter(iconName => iconName.includes(filterValue.value));
} else { } else {
iconNames.value = icons; iconNames.value = icons;
} }
@@ -87,7 +96,7 @@ const applyCustomIcon = () => {
watch( watch(
() => props.modelValue, () => props.modelValue,
(value) => { value => {
customIcon.value = value?.includes(':') ? value : ''; customIcon.value = value?.includes(':') ? value : '';
}, },
{ immediate: true } { immediate: true }
+7 -1
View File
@@ -1,5 +1,11 @@
<template> <template>
<el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported> <el-image
:src="`${realSrc}`"
fit="cover"
:style="`width:${realWidth};height:${realHeight};`"
:preview-src-list="realSrcList"
preview-teleported
>
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
<el-icon><picture-filled /></el-icon> <el-icon><picture-filled /></el-icon>
+8 -6
View File
@@ -26,10 +26,12 @@
<div v-if="showTip" class="el-upload__tip"> <div v-if="showTip" class="el-upload__tip">
请上传 请上传
<template v-if="fileSize"> <template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> 大小不超过
<b style="color: #f56c6c">{{ fileSize }}MB</b>
</template> </template>
<template v-if="fileType"> <template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 格式为
<b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</template> </template>
的文件 的文件
</div> </div>
@@ -89,7 +91,7 @@ const showTip = computed(() => props.isShowTip && (props.fileType || props.fileS
const imageUploadRef = ref<ElUploadInstance>(); const imageUploadRef = ref<ElUploadInstance>();
// 监听 fileType 变化,更新 fileAccept // 监听 fileType 变化,更新 fileAccept
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(',')); const fileAccept = computed(() => props.fileType.map(type => `.${type}`).join(','));
watch( watch(
() => props.modelValue, () => props.modelValue,
@@ -104,7 +106,7 @@ watch(
list = res.data; list = res.data;
} }
// 然后将数组转为对象数组 // 然后将数组转为对象数组
fileList.value = list.map((item) => { fileList.value = list.map(item => {
// 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来 // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
let itemData; let itemData;
if (typeof item === 'string') { if (typeof item === 'string') {
@@ -187,7 +189,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
// 删除图片 // 删除图片
const handleDelete = (file: UploadFile): boolean => { const handleDelete = (file: UploadFile): boolean => {
const findex = fileList.value.map((f) => f.name).indexOf(file.name); const findex = fileList.value.map(f => f.name).indexOf(file.name);
if (findex > -1 && uploadList.value.length === number.value) { if (findex > -1 && uploadList.value.length === number.value) {
const ossId = fileList.value[findex].ossId; const ossId = fileList.value[findex].ossId;
delOss(ossId); delOss(ossId);
@@ -201,7 +203,7 @@ const handleDelete = (file: UploadFile): boolean => {
// 上传结束处理 // 上传结束处理
const uploadedSuccessfully = () => { const uploadedSuccessfully = () => {
if (number.value > 0 && uploadList.value.length === number.value) { if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value); fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = []; uploadList.value = [];
number.value = 0; number.value = 0;
emit('update:modelValue', listToString(fileList.value)); emit('update:modelValue', listToString(fileList.value));
+2 -2
View File
@@ -5,8 +5,8 @@
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item :disabled="appStore.language === 'zh_CN'" command="zh_CN"> 中文 </el-dropdown-item> <el-dropdown-item :disabled="appStore.language === 'zh_CN'" command="zh_CN">中文</el-dropdown-item>
<el-dropdown-item :disabled="appStore.language === 'en_US'" command="en_US"> English </el-dropdown-item> <el-dropdown-item :disabled="appStore.language === 'en_US'" command="en_US">English</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
+9 -1
View File
@@ -1,5 +1,13 @@
<template> <template>
<el-dialog v-model="visible" :title="props.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false"> <el-dialog
v-model="visible"
:title="props.title"
width="50%"
draggable
:before-close="cancel"
center
:close-on-click-modal="false"
>
<el-form v-loading="loading" ref="ruleFormRef" :model="form" :rules="rules" label-width="120px"> <el-form v-loading="loading" ref="ruleFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="消息提醒" prop="messageType"> <el-form-item label="消息提醒" prop="messageType">
<el-checkbox-group v-model="form.messageType"> <el-checkbox-group v-model="form.messageType">
+20 -5
View File
@@ -1,10 +1,23 @@
<template> <template>
<div style="display: flex; justify-content: space-between"> <div style="display: flex; justify-content: space-between">
<div> <div>
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft', mode)">暂存</el-button> <el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft', mode)">
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="primary" @click="submitForm('submit', mode)"> </el-button> 暂存
<el-button v-if="approvalButtonShow" :loading="props.buttonLoading" type="primary" @click="approvalVerifyOpen">审批</el-button> </el-button>
<el-button v-if="props.id && props.status !== 'draft'" type="primary" @click="handleApprovalRecord">流程进度</el-button> <el-button
v-if="submitButtonShow"
:loading="props.buttonLoading"
type="primary"
@click="submitForm('submit', mode)"
>
</el-button>
<el-button v-if="approvalButtonShow" :loading="props.buttonLoading" type="primary" @click="approvalVerifyOpen">
审批
</el-button>
<el-button v-if="props.id && props.status !== 'draft'" type="primary" @click="handleApprovalRecord">
流程进度
</el-button>
<slot /> <slot />
</div> </div>
<div> <div>
@@ -40,7 +53,9 @@ const handleApprovalRecord = () => {
const submitButtonShow = computed(() => { const submitButtonShow = computed(() => {
return ( return (
props.pageType === 'add' || props.pageType === 'add' ||
(props.pageType === 'update' && props.status && (props.status === 'draft' || props.status === 'cancel' || props.status === 'back')) (props.pageType === 'update' &&
props.status &&
(props.status === 'draft' || props.status === 'cancel' || props.status === 'back'))
); );
}); });
+50 -11
View File
@@ -1,6 +1,13 @@
<template> <template>
<div class="container"> <div class="container">
<el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false"> <el-dialog
v-model="visible"
draggable
title="审批记录"
:width="props.width"
:height="props.height"
:close-on-click-modal="false"
>
<el-tabs v-model="tabActiveName" class="demo-tabs"> <el-tabs v-model="tabActiveName" class="demo-tabs">
<el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh"> <el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh">
<flowChart :ins-id="insId" v-if="insId" /> <flowChart :ins-id="insId" v-if="insId" />
@@ -13,9 +20,11 @@
<el-table-column prop="approveName" :show-overflow-tooltip="true" label="办理人" sortable align="center"> <el-table-column prop="approveName" :show-overflow-tooltip="true" label="办理人" sortable align="center">
<template #default="scope"> <template #default="scope">
<template v-if="scope.row.approveName"> <template v-if="scope.row.approveName">
<el-tag v-for="(item, index) in scope.row.approveName.split(',')" :key="index" type="success">{{ item }}</el-tag> <el-tag v-for="(item, index) in scope.row.approveName.split(',')" :key="index" type="success">
{{ item }}
</el-tag>
</template> </template>
<template v-else> <el-tag type="success"></el-tag></template> <template v-else><el-tag type="success"></el-tag></template>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="flowStatus" label="状态" width="80" sortable align="center"> <el-table-column prop="flowStatus" label="状态" width="80" sortable align="center">
@@ -23,9 +32,29 @@
<dict-tag :options="wf_task_status" :value="scope.row.flowStatus"></dict-tag> <dict-tag :options="wf_task_status" :value="scope.row.flowStatus"></dict-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="message" label="审批意见" :show-overflow-tooltip="true" sortable align="center"></el-table-column> <el-table-column
<el-table-column prop="createTime" label="开始时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column> prop="message"
<el-table-column prop="updateTime" label="结束时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column> label="审批意见"
:show-overflow-tooltip="true"
sortable
align="center"
></el-table-column>
<el-table-column
prop="createTime"
label="开始时间"
width="160"
:show-overflow-tooltip="true"
sortable
align="center"
></el-table-column>
<el-table-column
prop="updateTime"
label="结束时间"
width="160"
:show-overflow-tooltip="true"
sortable
align="center"
></el-table-column>
<el-table-column <el-table-column
prop="runDuration" prop="runDuration"
label="运行时长" label="运行时长"
@@ -36,12 +65,22 @@
></el-table-column> ></el-table-column>
<el-table-column prop="attachmentList" width="120" label="附件" align="center"> <el-table-column prop="attachmentList" width="120" label="附件" align="center">
<template #default="scope"> <template #default="scope">
<el-popover v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" placement="right" :width="310" trigger="click"> <el-popover
v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0"
placement="right"
:width="310"
trigger="click"
>
<template #reference> <template #reference>
<el-button type="primary" style="margin-right: 16px">附件</el-button> <el-button type="primary" style="margin-right: 16px">附件</el-button>
</template> </template>
<el-table border :data="scope.row.attachmentList"> <el-table border :data="scope.row.attachmentList">
<el-table-column prop="originalName" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column> <el-table-column
prop="originalName"
width="202"
:show-overflow-tooltip="true"
label="附件名称"
></el-table-column>
<el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作"> <el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作">
<template #default="tool"> <template #default="tool">
<el-button type="text" @click="handleDownload(tool.row.ossId)">下载</el-button> <el-button type="text" @click="handleDownload(tool.row.ossId)">下载</el-button>
@@ -81,14 +120,14 @@ const init = async (businessId: string | number) => {
loading.value = true; loading.value = true;
tabActiveName.value = 'image'; tabActiveName.value = 'image';
historyList.value = []; historyList.value = [];
flowHisTaskList(businessId).then((resp) => { flowHisTaskList(businessId).then(resp => {
if (resp.data) { if (resp.data) {
historyList.value = resp.data.list; historyList.value = resp.data.list;
insId.value = resp.data.instanceId; insId.value = resp.data.instanceId;
if (historyList.value.length > 0) { if (historyList.value.length > 0) {
historyList.value.forEach((item) => { historyList.value.forEach(item => {
if (item.ext) { if (item.ext) {
getIds(item.ext).then((res) => { getIds(item.ext).then(res => {
item.attachmentList = res.data; item.attachmentList = res.data;
}); });
} else { } else {
+50 -13
View File
@@ -1,6 +1,19 @@
<template> <template>
<el-dialog v-model="visible" draggable title="流程干预" :width="props.width" :height="props.height" :close-on-click-modal="false"> <el-dialog
<el-descriptions v-loading="loading" class="margin-top" :title="`${task.flowName}(${task.flowCode})`" :column="2" border> v-model="visible"
draggable
title="流程干预"
:width="props.width"
:height="props.height"
:close-on-click-modal="false"
>
<el-descriptions
v-loading="loading"
class="margin-top"
:title="`${task.flowName}(${task.flowCode})`"
:column="2"
border
>
<el-descriptions-item label="任务名称">{{ task.nodeName }}</el-descriptions-item> <el-descriptions-item label="任务名称">{{ task.nodeName }}</el-descriptions-item>
<el-descriptions-item label="节点编码">{{ task.nodeCode }}</el-descriptions-item> <el-descriptions-item label="节点编码">{{ task.nodeCode }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ task.createTime }}</el-descriptions-item> <el-descriptions-item label="开始时间">{{ task.createTime }}</el-descriptions-item>
@@ -10,7 +23,14 @@
</el-descriptions> </el-descriptions>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> 转办 </el-button> <el-button
v-if="task.flowStatus === 'waiting'"
:disabled="buttonDisabled"
type="primary"
@click="openTransferTask"
>
转办
</el-button>
<el-button <el-button
v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0" v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
:disabled="buttonDisabled" :disabled="buttonDisabled"
@@ -27,21 +47,38 @@
> >
减签 减签
</el-button> </el-button>
<el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleTerminationTask"> 终止 </el-button> <el-button
v-if="task.flowStatus === 'waiting'"
:disabled="buttonDisabled"
type="danger"
@click="handleTerminationTask"
>
终止
</el-button>
</span> </span>
</template> </template>
<!-- 转办 --> <!-- 转办 -->
<UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect> <UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect>
<!-- 加签组件 --> <!-- 加签组件 -->
<UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect> <UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
<el-dialog v-model="deleteSignatureVisible" draggable title="减签人员" width="700px" height="400px" append-to-body :close-on-click-modal="false" <el-dialog
><div> v-model="deleteSignatureVisible"
draggable
title="减签人员"
width="700px"
height="400px"
append-to-body
:close-on-click-modal="false"
>
<div>
<el-table :data="deleteUserList" border> <el-table :data="deleteUserList" border>
<el-table-column prop="nodeName" label="任务名称" /> <el-table-column prop="nodeName" label="任务名称" />
<el-table-column prop="nickName" label="办理人" /> <el-table-column prop="nickName" label="办理人" />
<el-table-column label="操作" align="center" width="160"> <el-table-column label="操作" align="center" width="160">
<template #default="scope"> <template #default="scope">
<el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">删除</el-button> <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">
删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -96,7 +133,7 @@ const task = ref<FlowTaskVO>({
const open = (taskId: string) => { const open = (taskId: string) => {
visible.value = true; visible.value = true;
getTask(taskId).then((response) => { getTask(taskId).then(response => {
loading.value = false; loading.value = false;
buttonDisabled.value = false; buttonDisabled.value = false;
task.value = response.data; task.value = response.data;
@@ -108,7 +145,7 @@ const openTransferTask = () => {
transferTaskRef.value.open(); transferTaskRef.value.open();
}; };
//转办 //转办
const handleTransferTask = async (data) => { const handleTransferTask = async data => {
if (data && data.length > 0) { if (data && data.length > 0) {
const taskOperationBo = reactive<TaskOperationBo>({ const taskOperationBo = reactive<TaskOperationBo>({
userId: data[0].userId, userId: data[0].userId,
@@ -135,10 +172,10 @@ const openMultiInstanceUser = async () => {
multiInstanceUserRef.value.open(); multiInstanceUserRef.value.open();
}; };
//加签 //加签
const addMultiInstanceUser = async (data) => { const addMultiInstanceUser = async data => {
if (data && data.length > 0) { if (data && data.length > 0) {
const taskOperationBo = reactive<TaskOperationBo>({ const taskOperationBo = reactive<TaskOperationBo>({
userIds: data.map((e) => e.userId), userIds: data.map(e => e.userId),
taskId: task.value.id, taskId: task.value.id,
message: '', message: '',
messageType: ['1'] messageType: ['1']
@@ -158,7 +195,7 @@ const addMultiInstanceUser = async (data) => {
} }
}; };
//减签 //减签
const deleteMultiInstanceUser = async (row) => { const deleteMultiInstanceUser = async row => {
await proxy?.$modal.confirm('是否确认提交?'); await proxy?.$modal.confirm('是否确认提交?');
loading.value = true; loading.value = true;
buttonDisabled.value = true; buttonDisabled.value = true;
@@ -181,7 +218,7 @@ const handleTaskUser = async () => {
const data = await currentTaskAllUser(task.value.id); const data = await currentTaskAllUser(task.value.id);
deleteUserList.value = data.data; deleteUserList.value = data.data;
if (deleteUserList.value && deleteUserList.value.length > 0) { if (deleteUserList.value && deleteUserList.value.length > 0) {
deleteUserList.value.forEach((e) => { deleteUserList.value.forEach(e => {
e.nodeName = task.value.nodeName; e.nodeName = task.value.nodeName;
}); });
} }
+86 -29
View File
@@ -1,5 +1,13 @@
<template> <template>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false"> <el-dialog
v-model="dialog.visible"
:title="dialog.title"
width="50%"
draggable
:before-close="cancel"
center
:close-on-click-modal="false"
>
<el-form v-loading="loading" :model="form" label-width="120px"> <el-form v-loading="loading" :model="form" label-width="120px">
<el-form-item label="消息提醒"> <el-form-item label="消息提醒">
<el-checkbox-group v-model="form.messageType"> <el-checkbox-group v-model="form.messageType">
@@ -9,15 +17,29 @@
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<el-form-item label="附件" v-if="buttonObj.file"> <el-form-item label="附件" v-if="buttonObj.file">
<fileUpload v-model="form.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" /> <fileUpload
v-model="form.fileId"
:file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']"
:file-size="20"
/>
</el-form-item> </el-form-item>
<el-form-item label="抄送" v-if="buttonObj.copy"> <el-form-item label="抄送" v-if="buttonObj.copy">
<el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" /> <el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)"> <el-tag
v-for="user in selectCopyUserList"
:key="user.userId"
closable
style="margin: 2px"
@close="handleCopyCloseTag(user)"
>
{{ user.nickName }} {{ user.nickName }}
</el-tag> </el-tag>
</el-form-item> </el-form-item>
<el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" label="下一步审批人" prop="assigneeMap"> <el-form-item
v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0"
label="下一步审批人"
prop="assigneeMap"
>
<div v-for="(item, index) in nestNodeList" :key="index" style="margin-bottom: 5px; width: 500px"> <div v-for="(item, index) in nestNodeList" :key="index" style="margin-bottom: 5px; width: 500px">
<span>{{ item.nodeName }}</span> <span>{{ item.nodeName }}</span>
<el-input v-if="false" v-model="form.assigneeMap[item.nodeCode]" /> <el-input v-if="false" v-model="form.assigneeMap[item.nodeCode]" />
@@ -34,11 +56,21 @@
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask"> 提交 </el-button> <el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask">提交</el-button>
<el-button v-if="task.flowStatus === 'waiting' && buttonObj.trust" :disabled="buttonDisabled" type="primary" @click="openDelegateTask"> <el-button
v-if="task.flowStatus === 'waiting' && buttonObj.trust"
:disabled="buttonDisabled"
type="primary"
@click="openDelegateTask"
>
委托 委托
</el-button> </el-button>
<el-button v-if="task.flowStatus === 'waiting' && buttonObj.transfer" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> <el-button
v-if="task.flowStatus === 'waiting' && buttonObj.transfer"
:disabled="buttonDisabled"
type="primary"
@click="openTransferTask"
>
转办 转办
</el-button> </el-button>
<el-button <el-button
@@ -65,14 +97,24 @@
> >
终止 终止
</el-button> </el-button>
<el-button v-if="task.flowStatus === 'waiting' && buttonObj.back" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen"> <el-button
v-if="task.flowStatus === 'waiting' && buttonObj.back"
:disabled="buttonDisabled"
type="danger"
@click="handleBackProcessOpen"
>
退回 退回
</el-button> </el-button>
<el-button :disabled="buttonDisabled" @click="cancel">取消</el-button> <el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
</span> </span>
</template> </template>
<!-- 抄送 --> <!-- 抄送 -->
<UserSelect ref="userSelectCopyRef" :multiple="true" :data="selectCopyUserIds" @confirm-call-back="userSelectCopyCallBack"></UserSelect> <UserSelect
ref="userSelectCopyRef"
:multiple="true"
:data="selectCopyUserIds"
@confirm-call-back="userSelectCopyCallBack"
></UserSelect>
<!-- 转办 --> <!-- 转办 -->
<UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect> <UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect>
<!-- 委托 --> <!-- 委托 -->
@@ -93,7 +135,12 @@
<el-form v-if="task.flowStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px"> <el-form v-if="task.flowStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px">
<el-form-item label="驳回节点"> <el-form-item label="驳回节点">
<el-select v-model="backForm.nodeCode" clearable placeholder="请选择" style="width: 300px"> <el-select v-model="backForm.nodeCode" clearable placeholder="请选择" style="width: 300px">
<el-option v-for="item in taskNodeList" :key="item.nodeCode" :label="item.nodeName" :value="item.nodeCode" /> <el-option
v-for="item in taskNodeList"
:key="item.nodeCode"
:label="item.nodeName"
:value="item.nodeCode"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="消息提醒"> <el-form-item label="消息提醒">
@@ -122,14 +169,24 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 驳回结束 --> <!-- 驳回结束 -->
<el-dialog v-model="deleteSignatureVisible" draggable title="减签人员" width="700px" height="400px" append-to-body :close-on-click-modal="false"> <el-dialog
v-model="deleteSignatureVisible"
draggable
title="减签人员"
width="700px"
height="400px"
append-to-body
:close-on-click-modal="false"
>
<div> <div>
<el-table :data="deleteUserList" border> <el-table :data="deleteUserList" border>
<el-table-column prop="nodeName" label="任务名称" /> <el-table-column prop="nodeName" label="任务名称" />
<el-table-column prop="nickName" label="办理人" /> <el-table-column prop="nickName" label="办理人" />
<el-table-column label="操作" align="center" width="160"> <el-table-column label="操作" align="center" width="160">
<template #default="scope"> <template #default="scope">
<el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">删除 </el-button> <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">
删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -265,11 +322,11 @@ const openDialog = async (id?: string) => {
const response = await getTask(taskId.value); const response = await getTask(taskId.value);
task.value = response.data; task.value = response.data;
buttonObj.value = {}; buttonObj.value = {};
task.value.buttonList?.forEach((e) => { task.value.buttonList?.forEach(e => {
buttonObj.value[e.code] = e.show; buttonObj.value[e.code] = e.show;
}); });
selectCopyUserList.value = task.value.copyList; selectCopyUserList.value = task.value.copyList;
selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(','); selectCopyUserIds.value = task.value.copyList.map(e => e.userId).join(',');
varNodeList.value = task.value.varList; varNodeList.value = task.value.varList;
console.log('varNodeList', varNodeList.value); console.log('varNodeList', varNodeList.value);
buttonDisabled.value = false; buttonDisabled.value = false;
@@ -294,7 +351,7 @@ const handleCompleteTask = async () => {
form.value.variables = props.taskVariables; form.value.variables = props.taskVariables;
let verify = false; let verify = false;
if (buttonObj.value.pop && nestNodeList.value && nestNodeList.value.length > 0) { if (buttonObj.value.pop && nestNodeList.value && nestNodeList.value.length > 0) {
nestNodeList.value.forEach((e) => { nestNodeList.value.forEach(e => {
if ( if (
Object.keys(form.value.assigneeMap).length === 0 || Object.keys(form.value.assigneeMap).length === 0 ||
form.value.assigneeMap[e.nodeCode] === '' || form.value.assigneeMap[e.nodeCode] === '' ||
@@ -313,7 +370,7 @@ const handleCompleteTask = async () => {
} }
if (selectCopyUserList.value && selectCopyUserList.value.length > 0) { if (selectCopyUserList.value && selectCopyUserList.value.length > 0) {
const flowCopyList = []; const flowCopyList = [];
selectCopyUserList.value.forEach((e) => { selectCopyUserList.value.forEach(e => {
const copyUser = { const copyUser = {
userId: e.userId, userId: e.userId,
nickName: e.nickName nickName: e.nickName
@@ -382,26 +439,26 @@ const openUserSelectCopy = () => {
const userSelectCopyCallBack = (data: FlowCopyVo[]) => { const userSelectCopyCallBack = (data: FlowCopyVo[]) => {
if (data && data.length > 0) { if (data && data.length > 0) {
selectCopyUserList.value = data; selectCopyUserList.value = data;
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(','); selectCopyUserIds.value = selectCopyUserList.value.map(item => item.userId).join(',');
} }
}; };
//删除抄送人员 //删除抄送人员
const handleCopyCloseTag = (user: FlowCopyVo) => { const handleCopyCloseTag = (user: FlowCopyVo) => {
const userId = user.userId; const userId = user.userId;
// 使用split删除用户 // 使用split删除用户
const index = selectCopyUserList.value.findIndex((item) => item.userId === userId); const index = selectCopyUserList.value.findIndex(item => item.userId === userId);
selectCopyUserList.value.splice(index, 1); selectCopyUserList.value.splice(index, 1);
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(','); selectCopyUserIds.value = selectCopyUserList.value.map(item => item.userId).join(',');
}; };
//加签 //加签
const openMultiInstanceUser = async () => { const openMultiInstanceUser = async () => {
multiInstanceUserRef.value.open(); multiInstanceUserRef.value.open();
}; };
//加签 //加签
const addMultiInstanceUser = async (data) => { const addMultiInstanceUser = async data => {
if (data && data.length > 0) { if (data && data.length > 0) {
const taskOperationBo = reactive<TaskOperationBo>({ const taskOperationBo = reactive<TaskOperationBo>({
userIds: data.map((e) => e.userId), userIds: data.map(e => e.userId),
taskId: taskId.value, taskId: taskId.value,
message: form.value.message, message: form.value.message,
messageType: ['1'] messageType: ['1']
@@ -421,7 +478,7 @@ const addMultiInstanceUser = async (data) => {
} }
}; };
//减签 //减签
const deleteMultiInstanceUser = async (row) => { const deleteMultiInstanceUser = async row => {
await proxy?.$modal.confirm('是否确认提交?'); await proxy?.$modal.confirm('是否确认提交?');
loading.value = true; loading.value = true;
buttonDisabled.value = true; buttonDisabled.value = true;
@@ -444,7 +501,7 @@ const openTransferTask = () => {
transferTaskRef.value.open(); transferTaskRef.value.open();
}; };
//转办 //转办
const handleTransferTask = async (data) => { const handleTransferTask = async data => {
if (data && data.length > 0) { if (data && data.length > 0) {
const taskOperationBo = reactive<TaskOperationBo>({ const taskOperationBo = reactive<TaskOperationBo>({
userId: data[0].userId, userId: data[0].userId,
@@ -472,7 +529,7 @@ const openDelegateTask = () => {
delegateTaskRef.value.open(); delegateTaskRef.value.open();
}; };
//委托 //委托
const handleDelegateTask = async (data) => { const handleDelegateTask = async data => {
if (data && data.length > 0) { if (data && data.length > 0) {
const taskOperationBo = reactive<TaskOperationBo>({ const taskOperationBo = reactive<TaskOperationBo>({
userId: data[0].userId, userId: data[0].userId,
@@ -515,14 +572,14 @@ const handleTaskUser = async () => {
const data = await currentTaskAllUser(taskId.value); const data = await currentTaskAllUser(taskId.value);
deleteUserList.value = data.data; deleteUserList.value = data.data;
if (deleteUserList.value && deleteUserList.value.length > 0) { if (deleteUserList.value && deleteUserList.value.length > 0) {
deleteUserList.value.forEach((e) => { deleteUserList.value.forEach(e => {
e.nodeName = task.value.nodeName; e.nodeName = task.value.nodeName;
}); });
} }
deleteSignatureVisible.value = true; deleteSignatureVisible.value = true;
}; };
// 选择人员 // 选择人员
const choosePeople = async (data) => { const choosePeople = async data => {
if (!data.permissionFlag) { if (!data.permissionFlag) {
proxy?.$modal.msgError('没有可选择的人员,请联系管理员!'); proxy?.$modal.msgError('没有可选择的人员,请联系管理员!');
} }
@@ -531,11 +588,11 @@ const choosePeople = async (data) => {
porUserRef.value.open(); porUserRef.value.open();
}; };
//确认选择 //确认选择
const handlePopUser = async (userList) => { const handlePopUser = async userList => {
const userIds = userList.map((item) => { const userIds = userList.map(item => {
return item.userId; return item.userId;
}); });
const nickNames = userList.map((item) => { const nickNames = userList.map(item => {
return item.nickName; return item.nickName;
}); });
form.value.assigneeMap[nodeCode.value] = userIds.join(','); form.value.assigneeMap[nodeCode.value] = userIds.join(',');
+9 -3
View File
@@ -1,7 +1,13 @@
<template> <template>
<div class="top-right-btn" :style="style"> <div class="top-right-btn" :style="style">
<el-row class="toolbar-row"> <el-row class="toolbar-row">
<el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top"> <el-tooltip
v-if="search"
class="item"
effect="dark"
:content="showSearch ? '隐藏搜索' : '显示搜索'"
placement="top"
>
<el-button circle icon="Search" @click="toggleSearch()" /> <el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip> </el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top"> <el-tooltip class="item" effect="dark" content="刷新" placement="top">
@@ -77,7 +83,7 @@ function saveStorage() {
// 更改数据列的显示和隐藏 // 更改数据列的显示和隐藏
function columnChange(...args: any[]) { function columnChange(...args: any[]) {
props.columns?.forEach((item) => { props.columns?.forEach(item => {
item.visible = args[1].checkedKeys.includes(item.key); item.visible = args[1].checkedKeys.includes(item.key);
}); });
saveStorage(); saveStorage();
@@ -96,7 +102,7 @@ onMounted(() => {
} }
} catch (e) {} } catch (e) {}
} }
props.columns?.forEach((item) => { props.columns?.forEach(item => {
if (item.visible) { if (item.visible) {
columnRef.value?.setChecked(item.key, true, false); columnRef.value?.setChecked(item.key, true, false);
} }
+104 -85
View File
@@ -1,80 +1,99 @@
<template> <template>
<div> <div>
<el-dialog v-model="roleDialog.visible.value" :title="roleDialog.title.value" width="80%" append-to-body class="role-select-dialog"> <el-dialog
v-model="roleDialog.visible.value"
:title="roleDialog.title.value"
width="80%"
append-to-body
class="role-select-dialog"
>
<div class="p-2 role-select-shell"> <div class="p-2 role-select-shell">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition
<div v-show="showSearch"> :enter-active-class="proxy?.animate.searchAnimate.enter"
<el-card shadow="hover" class="search-panel selector-card"> :leave-active-class="proxy?.animate.searchAnimate.leave"
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover" class="table-panel selector-card">
<template #header>
<div class="toolbar-shell selector-header">
<div class="table-heading">
<h3>角色列表</h3>
</div>
<div v-if="selectRoleList.length" class="selector-tags">
<el-tag v-for="role in selectRoleList" :key="role.roleId" closable @close="handleCloseTag(role)">
{{ role.roleName }}
</el-tag>
</div>
</div>
</template>
<vxe-table
ref="tableRef"
class="selector-table"
height="400px"
border
show-overflow
:data="roleList"
:loading="loading"
:row-config="{ keyField: 'roleId' }"
:checkbox-config="{ reserve: true, checkRowKeys: defaultSelectRoleIds }"
highlight-current-row
@checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange"
> >
<vxe-column type="checkbox" width="50" align="center" /> <div v-show="showSearch">
<vxe-column v-if="false" key="roleId" label="角色编号" /> <el-card shadow="hover" class="search-panel selector-card">
<vxe-column field="roleName" title="角色名称" /> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<vxe-column field="roleKey" title="权限字符" /> <el-form-item label="角色名称" prop="roleName">
<vxe-column field="roleSort" title="显示顺序" width="100" /> <el-input
<vxe-column title="状态" align="center" width="100"> v-model="queryParams.roleName"
<template #default="scope"> placeholder="请输入角色名称"
<dict-tag :options="sys_normal_disable" :value="scope.row.status"></dict-tag> clearable
</template> @keyup.enter="handleQuery"
</vxe-column> />
<vxe-column field="createTime" title="创建时间" align="center"> </el-form-item>
<template #default="scope"> <el-form-item label="权限字符" prop="roleKey">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span> <el-input
</template> v-model="queryParams.roleKey"
</vxe-column> placeholder="请输入权限字符"
</vxe-table> clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<pagination <el-form-item>
v-if="total > 0" <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
v-model:total="total" <el-button icon="Refresh" @click="resetQuery">重置</el-button>
v-model:page="queryParams.pageNum" </el-form-item>
v-model:limit="queryParams.pageSize" </el-form>
@pagination="pageList" </el-card>
/> </div>
</el-card> </transition>
<el-card shadow="hover" class="table-panel selector-card">
<template #header>
<div class="toolbar-shell selector-header">
<div class="table-heading">
<h3>角色列表</h3>
</div>
<div v-if="selectRoleList.length" class="selector-tags">
<el-tag v-for="role in selectRoleList" :key="role.roleId" closable @close="handleCloseTag(role)">
{{ role.roleName }}
</el-tag>
</div>
</div>
</template>
<vxe-table
ref="tableRef"
class="selector-table"
height="400px"
border
show-overflow
:data="roleList"
:loading="loading"
:row-config="{ keyField: 'roleId' }"
:checkbox-config="{ reserve: true, checkRowKeys: defaultSelectRoleIds }"
highlight-current-row
@checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange"
>
<vxe-column type="checkbox" width="50" align="center" />
<vxe-column v-if="false" key="roleId" label="角色编号" />
<vxe-column field="roleName" title="角色名称" />
<vxe-column field="roleKey" title="权限字符" />
<vxe-column field="roleSort" title="显示顺序" width="100" />
<vxe-column title="状态" align="center" width="100">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status"></dict-tag>
</template>
</vxe-column>
<vxe-column field="createTime" title="创建时间" align="center">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</vxe-column>
</vxe-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="pageList"
/>
</el-card>
</div> </div>
<template #footer> <template #footer>
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
@@ -134,7 +153,7 @@ const confirm = () => {
roleDialog.closeDialog(); roleDialog.closeDialog();
}; };
const computedIds = (data) => { const computedIds = data => {
if (data instanceof Array) { if (data instanceof Array) {
return [...data]; return [...data];
} else if (typeof data === 'string') { } else if (typeof data === 'string') {
@@ -152,7 +171,7 @@ const computedIds = (data) => {
*/ */
const getList = () => { const getList = () => {
loading.value = true; loading.value = true;
api.listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => { api.listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
roleList.value = res.data?.rows; roleList.value = res.data?.rows;
total.value = res.data?.total; total.value = res.data?.total;
loading.value = false; loading.value = false;
@@ -160,8 +179,8 @@ const getList = () => {
}; };
const pageList = async () => { const pageList = async () => {
await getList(); await getList();
const roles = roleList.value.filter((item) => { const roles = roleList.value.filter(item => {
return selectRoleList.value.some((role) => role.roleId === item.roleId); return selectRoleList.value.some(role => role.roleId === item.roleId);
}); });
await tableRef.value.setCheckboxRow(roles, true); await tableRef.value.setCheckboxRow(roles, true);
}; };
@@ -180,7 +199,7 @@ const resetQuery = () => {
handleQuery(); handleQuery();
}; };
const handleCheckboxChange = (checked) => { const handleCheckboxChange = checked => {
if (!prop.multiple && checked.checked) { if (!prop.multiple && checked.checked) {
tableRef.value.setCheckboxRow(selectRoleList.value, false); tableRef.value.setCheckboxRow(selectRoleList.value, false);
selectRoleList.value = []; selectRoleList.value = [];
@@ -189,22 +208,22 @@ const handleCheckboxChange = (checked) => {
if (checked.checked) { if (checked.checked) {
selectRoleList.value.push(row); selectRoleList.value.push(row);
} else { } else {
selectRoleList.value = selectRoleList.value.filter((item) => { selectRoleList.value = selectRoleList.value.filter(item => {
return item.roleId !== row.roleId; return item.roleId !== row.roleId;
}); });
} }
}; };
const handleCheckboxAll = (checked) => { const handleCheckboxAll = checked => {
const rows = roleList.value; const rows = roleList.value;
if (checked.checked) { if (checked.checked) {
rows.forEach((row) => { rows.forEach(row => {
if (!selectRoleList.value.some((item) => item.roleId === row.roleId)) { if (!selectRoleList.value.some(item => item.roleId === row.roleId)) {
selectRoleList.value.push(row); selectRoleList.value.push(row);
} }
}); });
} else { } else {
selectRoleList.value = selectRoleList.value.filter((item) => { selectRoleList.value = selectRoleList.value.filter(item => {
return !rows.some((row) => row.roleId === item.roleId); return !rows.some(row => row.roleId === item.roleId);
}); });
} }
}; };
@@ -212,7 +231,7 @@ const handleCheckboxAll = (checked) => {
const handleCloseTag = (user: RoleVO) => { const handleCloseTag = (user: RoleVO) => {
const roleId = user.roleId; const roleId = user.roleId;
// 使用split删除用户 // 使用split删除用户
const index = selectRoleList.value.findIndex((item) => item.roleId === roleId); const index = selectRoleList.value.findIndex(item => item.roleId === roleId);
const rows = selectRoleList.value[index]; const rows = selectRoleList.value[index];
tableRef.value?.setCheckboxRow(rows, false); tableRef.value?.setCheckboxRow(rows, false);
selectRoleList.value.splice(index, 1); selectRoleList.value.splice(index, 1);
@@ -224,7 +243,7 @@ const initSelectRole = async () => {
if (defaultSelectRoleIds.value.length > 0) { if (defaultSelectRoleIds.value.length > 0) {
const { data } = await api.optionSelect(defaultSelectRoleIds.value); const { data } = await api.optionSelect(defaultSelectRoleIds.value);
selectRoleList.value = data; selectRoleList.value = data;
const users = roleList.value.filter((item) => { const users = roleList.value.filter(item => {
return defaultSelectRoleIds.value.includes(String(item.roleId)); return defaultSelectRoleIds.value.includes(String(item.roleId));
}); });
await nextTick(() => { await nextTick(() => {
+6 -1
View File
@@ -6,7 +6,12 @@
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value"> <el-dropdown-item
v-for="item of sizeOptions"
:key="item.value"
:disabled="size === item.value"
:command="item.value"
>
{{ item.label }} {{ item.label }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
+30 -15
View File
@@ -1,19 +1,34 @@
<template> <template>
<el-menu class="mix-topnav-menu" :default-active="activeMenu" mode="horizontal" :ellipsis="false" @select="handleSelect"> <el-menu
class="mix-topnav-menu"
:default-active="activeMenu"
mode="horizontal"
:ellipsis="false"
@select="handleSelect"
>
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item v-if="index < visibleNumber" :key="index" :index="item.path" <el-menu-item v-if="index < visibleNumber" :key="index" :index="item.path">
><svg-icon v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" :icon-class="item.meta ? item.meta.icon : ''" /> <svg-icon
{{ item.meta?.title }}</el-menu-item v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
> :icon-class="item.meta ? item.meta.icon : ''"
/>
{{ item.meta?.title }}
</el-menu-item>
</template> </template>
<!-- 顶部菜单超出数量折叠 --> <!-- 顶部菜单超出数量折叠 -->
<el-sub-menu v-if="topMenus.length > visibleNumber" class="el-sub-menu__hide-arrow" popper-class="mix-topnav-popper" index="more"> <el-sub-menu
v-if="topMenus.length > visibleNumber"
class="el-sub-menu__hide-arrow"
popper-class="mix-topnav-popper"
index="more"
>
<template #title>更多菜单</template> <template #title>更多菜单</template>
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path" <el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path">
><svg-icon :icon-class="item.meta ? item.meta.icon : ''" /> {{ item.meta?.title }}</el-menu-item <svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
> {{ item.meta?.title }}
</el-menu-item>
</template> </template>
</el-sub-menu> </el-sub-menu>
</el-menu> </el-menu>
@@ -46,7 +61,7 @@ const routers = computed(() => permissionStore.getTopbarRoutes());
// 顶部显示菜单 // 顶部显示菜单
const topMenus = computed(() => { const topMenus = computed(() => {
const topMenus: RouteRecordRaw[] = []; const topMenus: RouteRecordRaw[] = [];
routers.value.map((menu) => { routers.value.map(menu => {
if (menu.hidden !== true) { if (menu.hidden !== true) {
// 兼容顶部栏一级菜单内部跳转 // 兼容顶部栏一级菜单内部跳转
if (menu.path === '/' && menu.children) { if (menu.path === '/' && menu.children) {
@@ -62,8 +77,8 @@ const topMenus = computed(() => {
// 设置子路由 // 设置子路由
const childrenMenus = computed(() => { const childrenMenus = computed(() => {
const childrenMenus: RouteRecordRaw[] = []; const childrenMenus: RouteRecordRaw[] = [];
routers.value.map((router) => { routers.value.map(router => {
router.children?.forEach((item) => { router.children?.forEach(item => {
if (item.parentPath === undefined) { if (item.parentPath === undefined) {
if (router.path === '/') { if (router.path === '/') {
item.path = '/' + item.path; item.path = '/' + item.path;
@@ -110,13 +125,13 @@ const setVisibleNumber = () => {
}; };
const handleSelect = (key: string) => { const handleSelect = (key: string) => {
const route = routers.value.find((item) => item.path === key); const route = routers.value.find(item => item.path === key);
if (isHttp(key)) { if (isHttp(key)) {
// http(s):// 路径新窗口打开 // http(s):// 路径新窗口打开
window.open(key, '_blank'); window.open(key, '_blank');
} else if (!route || !route.children) { } else if (!route || !route.children) {
// 没有子路由路径内部打开 // 没有子路由路径内部打开
const routeMenu = childrenMenus.value.find((item) => item.path === key); const routeMenu = childrenMenus.value.find(item => item.path === key);
if (routeMenu && routeMenu.query) { if (routeMenu && routeMenu.query) {
const query = JSON.parse(routeMenu.query); const query = JSON.parse(routeMenu.query);
router.push({ path: key, query: query }); router.push({ path: key, query: query });
@@ -134,7 +149,7 @@ const handleSelect = (key: string) => {
const activeRoutes = (key: string) => { const activeRoutes = (key: string) => {
const routes: RouteRecordRaw[] = []; const routes: RouteRecordRaw[] = [];
if (childrenMenus.value && childrenMenus.value.length > 0) { if (childrenMenus.value && childrenMenus.value.length > 0) {
childrenMenus.value.map((item) => { childrenMenus.value.map(item => {
if (key == item.parentPath || (key == 'index' && '' == item.path)) { if (key == item.parentPath || (key == 'index' && '' == item.path)) {
routes.push(item); routes.push(item);
} }
+71 -23
View File
@@ -1,20 +1,45 @@
<template> <template>
<div> <div>
<el-dialog v-model="userDialog.visible.value" :title="userDialog.title.value" width="80%" append-to-body class="user-select-dialog"> <el-dialog
v-model="userDialog.visible.value"
:title="userDialog.title.value"
width="80%"
append-to-body
class="user-select-dialog"
>
<div class="p-2 user-select-shell"> <div class="p-2 user-select-shell">
<el-row :gutter="12" class="selector-layout"> <el-row :gutter="12" class="selector-layout">
<!-- 部门树 --> <!-- 部门树 -->
<el-col :lg="treeCollapsed ? 1 : 5" :xs="24" class="tree-panel-col" :class="{ 'is-collapsed': treeCollapsed }"> <el-col
<el-card shadow="hover" class="side-panel tree-panel-shell selector-card selector-side-card" :class="{ 'is-collapsed': treeCollapsed }"> :lg="treeCollapsed ? 1 : 5"
:xs="24"
class="tree-panel-col"
:class="{ 'is-collapsed': treeCollapsed }"
>
<el-card
shadow="hover"
class="side-panel tree-panel-shell selector-card selector-side-card"
:class="{ 'is-collapsed': treeCollapsed }"
>
<template #header> <template #header>
<div class="panel-heading search-panel-toggle tree-panel-header" :class="{ 'is-collapsed': treeCollapsed }" @click.stop="treeCollapsed = !treeCollapsed"> <div
class="panel-heading search-panel-toggle tree-panel-header"
:class="{ 'is-collapsed': treeCollapsed }"
@click.stop="treeCollapsed = !treeCollapsed"
>
<div v-show="!treeCollapsed" class="table-heading"> <div v-show="!treeCollapsed" class="table-heading">
<h3>部门结构</h3> <h3>部门结构</h3>
</div> </div>
</div> </div>
</template> </template>
<template v-if="!treeCollapsed"> <template v-if="!treeCollapsed">
<el-input v-model="deptName" class="selector-dept-input" placeholder="请输入部门名称" prefix-icon="Search" clearable /> <el-input
v-model="deptName"
class="selector-dept-input"
placeholder="请输入部门名称"
prefix-icon="Search"
clearable
/>
<el-tree <el-tree
ref="deptTreeRef" ref="deptTreeRef"
class="selector-tree" class="selector-tree"
@@ -30,17 +55,35 @@
</template> </template>
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="treeCollapsed ? 23 : 19" :xs="24" class="tree-content-col" :class="{ 'is-tree-collapsed': treeCollapsed }"> <el-col
:lg="treeCollapsed ? 23 : 19"
:xs="24"
class="tree-content-col"
:class="{ 'is-tree-collapsed': treeCollapsed }"
>
<div class="p-2user-select-main"> <div class="p-2user-select-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition
:enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave"
>
<div v-show="showSearch"> <div v-show="showSearch">
<el-card shadow="hover" class="search-panel selector-card"> <el-card shadow="hover" class="search-panel selector-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="用户名称" prop="userName"> <el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="手机号码" prop="phoneNumber"> <el-form-item label="手机号码" prop="phoneNumber">
<el-input v-model="queryParams.phoneNumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.phoneNumber"
placeholder="请输入手机号码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -74,7 +117,12 @@
:data="userList" :data="userList"
:loading="loading" :loading="loading"
:row-config="{ keyField: 'userId', isHover: true }" :row-config="{ keyField: 'userId', isHover: true }"
:checkbox-config="{ reserve: true, trigger: 'row', highlight: true, showHeader: prop.multiple }" :checkbox-config="{
reserve: true,
trigger: 'row',
highlight: true,
showHeader: prop.multiple
}"
@checkbox-all="handleCheckboxAll" @checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange" @checkbox-change="handleCheckboxChange"
> >
@@ -189,12 +237,12 @@ const confirm = () => {
userDialog.closeDialog(); userDialog.closeDialog();
}; };
const computedIds = (data) => { const computedIds = data => {
if (data === '' || data === null || data === undefined) { if (data === '' || data === null || data === undefined) {
return []; return [];
} }
if (data instanceof Array) { if (data instanceof Array) {
return data.map((item) => String(item)); return data.map(item => String(item));
} else if (typeof data === 'string') { } else if (typeof data === 'string') {
return data.split(','); return data.split(',');
} else if (typeof data === 'number') { } else if (typeof data === 'number') {
@@ -229,8 +277,8 @@ const getList = async () => {
const pageList = async () => { const pageList = async () => {
await getList(); await getList();
const users = userList.value.filter((item) => { const users = userList.value.filter(item => {
return selectUserList.value.some((user) => user.userId === item.userId); return selectUserList.value.some(user => user.userId === item.userId);
}); });
await tableRef.value.setCheckboxRow(users, true); await tableRef.value.setCheckboxRow(users, true);
}; };
@@ -256,7 +304,7 @@ const resetQuery = (refresh = true) => {
refresh && handleQuery(); refresh && handleQuery();
}; };
const handleCheckboxChange = (checked) => { const handleCheckboxChange = checked => {
if (!prop.multiple && checked.checked) { if (!prop.multiple && checked.checked) {
tableRef.value.setCheckboxRow(selectUserList.value, false); tableRef.value.setCheckboxRow(selectUserList.value, false);
selectUserList.value = []; selectUserList.value = [];
@@ -265,22 +313,22 @@ const handleCheckboxChange = (checked) => {
if (checked.checked) { if (checked.checked) {
selectUserList.value.push(row); selectUserList.value.push(row);
} else { } else {
selectUserList.value = selectUserList.value.filter((item) => { selectUserList.value = selectUserList.value.filter(item => {
return item.userId !== row.userId; return item.userId !== row.userId;
}); });
} }
}; };
const handleCheckboxAll = (checked) => { const handleCheckboxAll = checked => {
const rows = userList.value; const rows = userList.value;
if (checked.checked) { if (checked.checked) {
rows.forEach((row) => { rows.forEach(row => {
if (!selectUserList.value.some((item) => item.userId === row.userId)) { if (!selectUserList.value.some(item => item.userId === row.userId)) {
selectUserList.value.push(row); selectUserList.value.push(row);
} }
}); });
} else { } else {
selectUserList.value = selectUserList.value.filter((item) => { selectUserList.value = selectUserList.value.filter(item => {
return !rows.some((row) => row.userId === item.userId); return !rows.some(row => row.userId === item.userId);
}); });
} }
}; };
@@ -288,7 +336,7 @@ const handleCheckboxAll = (checked) => {
const handleCloseTag = (user: UserVO) => { const handleCloseTag = (user: UserVO) => {
const userId = user.userId; const userId = user.userId;
// 使用split删除用户 // 使用split删除用户
const index = selectUserList.value.findIndex((item) => item.userId === userId); const index = selectUserList.value.findIndex(item => item.userId === userId);
const rows = selectUserList.value[index]; const rows = selectUserList.value[index];
tableRef.value?.setCheckboxRow(rows, false); tableRef.value?.setCheckboxRow(rows, false);
selectUserList.value.splice(index, 1); selectUserList.value.splice(index, 1);
@@ -298,7 +346,7 @@ const initSelectUser = async () => {
if (defaultSelectUserIds.value.length > 0) { if (defaultSelectUserIds.value.length > 0) {
const { data } = await api.optionSelect(defaultSelectUserIds.value); const { data } = await api.optionSelect(defaultSelectUserIds.value);
selectUserList.value = data; selectUserList.value = data;
const users = userList.value.filter((item) => { const users = userList.value.filter(item => {
return defaultSelectUserIds.value.includes(String(item.userId)); return defaultSelectUserIds.value.includes(String(item.userId));
}); });
await nextTick(() => { await nextTick(() => {
+3 -1
View File
@@ -26,7 +26,9 @@ watch(
() => useSettingsStore().animationEnable, () => useSettingsStore().animationEnable,
(val: boolean) => { (val: boolean) => {
if (val) { if (val) {
animate.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string; animate.value = proxy?.animate.animateList[
Math.round(Math.random() * proxy?.animate.animateList.length)
] as string;
} else { } else {
animate.value = proxy?.animate.defaultAnimate as string; animate.value = proxy?.animate.defaultAnimate as string;
} }
+1 -1
View File
@@ -19,7 +19,7 @@ const tagsViewStore = useTagsViewStore();
function iframeUrl(url: string | undefined, query: any) { function iframeUrl(url: string | undefined, query: any) {
if (Object.keys(query).length > 0) { if (Object.keys(query).length > 0) {
const params = Object.keys(query) const params = Object.keys(query)
.map((key) => key + '=' + query[key]) .map(key => key + '=' + query[key])
.join('&'); .join('&');
return url + '?' + params; return url + '?' + params;
} }
+9 -2
View File
@@ -2,7 +2,12 @@
<div class="navbar" :class="'nav' + navType"> <div class="navbar" :class="'nav' + navType">
<div class="navbar-left"> <div class="navbar-left">
<div v-if="navType !== NavTypeEnum.TOP" class="hamburger-shell"> <div v-if="navType !== NavTypeEnum.TOP" class="hamburger-shell">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" /> <hamburger
id="hamburger-container"
:is-active="appStore.sidebar.opened"
class="hamburger-container"
@toggle-click="toggleSideBar"
/>
</div> </div>
<router-link v-else-if="showLogo" to="/" class="navtop-logo-shell"> <router-link v-else-if="showLogo" to="/" class="navtop-logo-shell">
<img :src="appLogo" class="navtop-logo-icon" alt="logo" /> <img :src="appLogo" class="navtop-logo-icon" alt="logo" />
@@ -31,7 +36,9 @@
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false"> <el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
<template #reference> <template #reference>
<el-badge :value="noticeStore.unreadCount.value > 0 ? noticeStore.unreadCount.value : ''" :max="99"> <el-badge :value="noticeStore.unreadCount.value > 0 ? noticeStore.unreadCount.value : ''" :max="99">
<div class="right-menu-item hover-effect message-trigger"><svg-icon icon-class="message" /></div> <div class="right-menu-item hover-effect message-trigger">
<svg-icon icon-class="message" />
</div>
</el-badge> </el-badge>
</template> </template>
<template #default> <template #default>
+33 -7
View File
@@ -1,5 +1,12 @@
<template> <template>
<el-drawer v-model="showSettings" class="settings-drawer" :with-header="false" direction="rtl" size="300px" close-on-click-modal> <el-drawer
v-model="showSettings"
class="settings-drawer"
:with-header="false"
direction="rtl"
size="300px"
close-on-click-modal
>
<h3 class="drawer-title">菜单导航设置</h3> <h3 class="drawer-title">菜单导航设置</h3>
<div class="nav-wrap"> <div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom"> <el-tooltip content="左侧菜单" placement="bottom">
@@ -9,7 +16,8 @@
:style="{ '--theme': theme }" :style="{ '--theme': theme }"
:class="{ activeItem: navType == NavTypeEnum.LEFT }" :class="{ activeItem: navType == NavTypeEnum.LEFT }"
> >
<b></b><b></b> <b></b>
<b></b>
</div> </div>
</el-tooltip> </el-tooltip>
@@ -20,7 +28,8 @@
:style="{ '--theme': theme }" :style="{ '--theme': theme }"
:class="{ activeItem: navType == NavTypeEnum.MIX }" :class="{ activeItem: navType == NavTypeEnum.MIX }"
> >
<b></b><b></b> <b></b>
<b></b>
</div> </div>
</el-tooltip> </el-tooltip>
<el-tooltip content="顶部菜单" placement="bottom"> <el-tooltip content="顶部菜单" placement="bottom">
@@ -30,7 +39,8 @@
:style="{ '--theme': theme }" :style="{ '--theme': theme }"
:class="{ activeItem: navType == NavTypeEnum.TOP }" :class="{ activeItem: navType == NavTypeEnum.TOP }"
> >
<b></b><b></b> <b></b>
<b></b>
</div> </div>
</el-tooltip> </el-tooltip>
</div> </div>
@@ -42,7 +52,15 @@
<img src="@/assets/images/dark.svg" alt="dark" /> <img src="@/assets/images/dark.svg" alt="dark" />
<div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block"> <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block">
<i aria-label="图标: check" class="anticon anticon-check"> <i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false"> <svg
viewBox="64 64 896 896"
data-icon="check"
width="1em"
height="1em"
:fill="theme"
aria-hidden="true"
focusable="false"
>
<path <path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/> />
@@ -54,7 +72,15 @@
<img src="@/assets/images/light.svg" alt="light" /> <img src="@/assets/images/light.svg" alt="light" />
<div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block"> <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block">
<i aria-label="图标: check" class="anticon anticon-check"> <i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false"> <svg
viewBox="64 64 896 896"
data-icon="check"
width="1em"
height="1em"
:fill="theme"
aria-hidden="true"
focusable="false"
>
<path <path
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
/> />
@@ -169,7 +195,7 @@ const toggleDark = () => useToggle(isDark);
/** 菜单导航设置 */ /** 菜单导航设置 */
watch( watch(
navType, navType,
(val) => { val => {
if (val === NavTypeEnum.TOP) { if (val === NavTypeEnum.TOP) {
appStore.toggleSideBarHide(true); appStore.toggleSideBarHide(true);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any); permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
+2 -9
View File
@@ -1,8 +1,5 @@
<template> <template>
<div <div class="sidebar-logo-container" :class="{ collapse: collapse }">
class="sidebar-logo-container"
:class="{ collapse: collapse }"
>
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in"> <transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="logo" :src="logo" class="sidebar-logo" />
@@ -94,11 +91,7 @@ const logoTextColor = computed(() => (isDarkSide.value ? '#f8fbff' : 'var(--app-
line-height: 1; line-height: 1;
font-size: 15px; font-size: 15px;
letter-spacing: 0.02em; letter-spacing: 0.02em;
font-family: font-family: 'MiSans', 'HarmonyOS Sans SC', 'PingFang SC', sans-serif;
'MiSans',
'HarmonyOS Sans SC',
'PingFang SC',
sans-serif;
vertical-align: middle; vertical-align: middle;
} }
} }
+16 -3
View File
@@ -1,6 +1,12 @@
<template> <template>
<div v-if="!item.hidden"> <div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item, item.children) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow"> <template
v-if="
hasOneShowingChild(item, item.children) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)"> <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }"> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" /> <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
@@ -11,7 +17,14 @@
</app-link> </app-link>
</template> </template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" :popper-offset="isNest ? 4 : 12" :popper-class="popperClass" teleported> <el-sub-menu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
:popper-offset="isNest ? 4 : 12"
:popper-class="popperClass"
teleported
>
<template v-if="item.meta" #title> <template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta ? item.meta.icon : ''" /> <svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span> <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
@@ -61,7 +74,7 @@ const hasOneShowingChild = (parent: RouteRecordRaw, children?: RouteRecordRaw[])
if (!children) { if (!children) {
children = []; children = [];
} }
const showingChildren = children.filter((item) => { const showingChildren = children.filter(item => {
if (item.hidden) { if (item.hidden) {
return false; return false;
} }
@@ -62,7 +62,7 @@ const moveToTarget = (currentTag: RouteLocationNormalized) => {
const containerWidth = container.offsetWidth; const containerWidth = container.offsetWidth;
const tagKey = currentTag.fullPath || currentTag.path; const tagKey = currentTag.fullPath || currentTag.path;
const tagListDom = Array.from(document.querySelectorAll('.tags-view-item')) as HTMLElement[]; const tagListDom = Array.from(document.querySelectorAll('.tags-view-item')) as HTMLElement[];
const currentIndex = tagListDom.findIndex((item) => item.dataset.tagKey === tagKey); const currentIndex = tagListDom.findIndex(item => item.dataset.tagKey === tagKey);
if (currentIndex === -1) { if (currentIndex === -1) {
return; return;
} }
+43 -14
View File
@@ -45,7 +45,7 @@
<span>全屏显示</span> <span>全屏显示</span>
</template> </template>
<template v-else> <template v-else>
<CloseBold /> <CloseBold />
<span>退出全屏</span> <span>退出全屏</span>
</template> </template>
</el-dropdown-item> </el-dropdown-item>
@@ -59,18 +59,47 @@
</span> </span>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu"> <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)"><RefreshRight style="width: 1em; height: 1em" /> 刷新页面</li> <li @click="refreshSelectedTag(selectedTag)">
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><Close style="width: 1em; height: 1em" /> 关闭当前</li> <RefreshRight style="width: 1em; height: 1em" />
<li @click="closeOthersTags"><CircleClose style="width: 1em; height: 1em" /> 关闭其他</li> 刷新页面
<li v-if="!isFirstView()" @click="closeLeftTags"><Back style="width: 1em; height: 1em" /> 关闭左侧</li> </li>
<li v-if="!isLastView()" @click="closeRightTags"><Right style="width: 1em; height: 1em" /> 关闭右侧</li> <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<li @click="closeAllTags(selectedTag)"><CircleClose style="width: 1em; height: 1em" /> 全部关闭</li> <Close style="width: 1em; height: 1em" />
关闭当前
</li>
<li @click="closeOthersTags">
<CircleClose style="width: 1em; height: 1em" />
关闭其他
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<Back style="width: 1em; height: 1em" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<Right style="width: 1em; height: 1em" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<CircleClose style="width: 1em; height: 1em" />
全部关闭
</li>
</ul> </ul>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ArrowDown, ArrowLeft, ArrowRight, Back, CircleClose, Close, CloseBold, FullScreen, RefreshRight, Right } from '@element-plus/icons-vue'; import {
ArrowDown,
ArrowLeft,
ArrowRight,
Back,
CircleClose,
Close,
CloseBold,
FullScreen,
RefreshRight,
Right
} from '@element-plus/icons-vue';
import ScrollPane from './ScrollPane.vue'; import ScrollPane from './ScrollPane.vue';
import { getNormalPath } from '@/utils/ruoyi'; import { getNormalPath } from '@/utils/ruoyi';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
@@ -100,7 +129,7 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews());
const routes = computed(() => permissionStore.getRoutes()); const routes = computed(() => permissionStore.getRoutes());
const tagsIcon = computed(() => settingsStore.tagsIcon); const tagsIcon = computed(() => settingsStore.tagsIcon);
const selectedDropdownTag = computed<RouteLocationNormalized | undefined>(() => { const selectedDropdownTag = computed<RouteLocationNormalized | undefined>(() => {
return visitedViews.value.find((tag) => isActive(tag)) || selectedTag.value; return visitedViews.value.find(tag => isActive(tag)) || selectedTag.value;
}); });
watch(route, () => { watch(route, () => {
@@ -108,7 +137,7 @@ watch(route, () => {
moveToCurrentTag(); moveToCurrentTag();
}); });
watch(visible, (value) => { watch(visible, value => {
if (value) { if (value) {
document.body.addEventListener('click', closeMenu); document.body.addEventListener('click', closeMenu);
} else { } else {
@@ -172,7 +201,7 @@ const isLastView = () => {
const filterAffixTags = (routeList: RouteRecordRaw[], basePath = '') => { const filterAffixTags = (routeList: RouteRecordRaw[], basePath = '') => {
let tags: RouteLocationNormalized[] = []; let tags: RouteLocationNormalized[] = [];
routeList.forEach((item) => { routeList.forEach(item => {
if (item.meta?.affix) { if (item.meta?.affix) {
const tagPath = getNormalPath(basePath + '/' + item.path); const tagPath = getNormalPath(basePath + '/' + item.path);
tags.push({ tags.push({
@@ -261,7 +290,7 @@ const closeRightTags = () => {
return; return;
} }
proxy?.$tab.closeRightPage(tag).then((views: RouteLocationNormalized[]) => { proxy?.$tab.closeRightPage(tag).then((views: RouteLocationNormalized[]) => {
if (!views.find((item) => item.fullPath === route.fullPath)) { if (!views.find(item => item.fullPath === route.fullPath)) {
toLastView(views); toLastView(views);
} }
}); });
@@ -273,7 +302,7 @@ const closeLeftTags = () => {
return; return;
} }
proxy?.$tab.closeLeftPage(tag).then((views: RouteLocationNormalized[]) => { proxy?.$tab.closeLeftPage(tag).then((views: RouteLocationNormalized[]) => {
if (!views.find((item) => item.fullPath === route.fullPath)) { if (!views.find(item => item.fullPath === route.fullPath)) {
toLastView(views); toLastView(views);
} }
}); });
@@ -292,7 +321,7 @@ const closeOthersTags = () => {
const closeAllTags = (view?: RouteLocationNormalized) => { const closeAllTags = (view?: RouteLocationNormalized) => {
proxy?.$tab.closeAllPage().then(({ visitedViews: views }: { visitedViews: RouteLocationNormalized[] }) => { proxy?.$tab.closeAllPage().then(({ visitedViews: views }: { visitedViews: RouteLocationNormalized[] }) => {
if (affixTags.value.some((tag) => tag.path === route.path)) { if (affixTags.value.some(tag => tag.path === route.path)) {
return; return;
} }
toLastView(views, view); toLastView(views, view);
+52 -29
View File
@@ -1,59 +1,82 @@
<template> <template>
<el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal"> <el-menu
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" popper-class="topbar-menu-popper" /> class="topbar-menu"
:ellipsis="false"
:default-active="activeMenu"
:active-text-color="theme"
mode="horizontal"
>
<sidebar-item
:key="route.path + index"
v-for="(route, index) in topMenus"
:item="route"
:base-path="route.path"
popper-class="topbar-menu-popper"
/>
<el-sub-menu index="more" class="el-sub-menu__hide-arrow" popper-class="topbar-menu-popper" v-if="moreRoutes.length > 0"> <el-sub-menu
index="more"
class="el-sub-menu__hide-arrow"
popper-class="topbar-menu-popper"
v-if="moreRoutes.length > 0"
>
<template #title> <template #title>
<span>更多菜单</span> <span>更多菜单</span>
</template> </template>
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" popper-class="topbar-menu-popper" /> <sidebar-item
:key="route.path + index"
v-for="(route, index) in moreRoutes"
:item="route"
:base-path="route.path"
popper-class="topbar-menu-popper"
/>
</el-sub-menu> </el-sub-menu>
</el-menu> </el-menu>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import SidebarItem from '../Sidebar/SidebarItem' import SidebarItem from '../Sidebar/SidebarItem';
import {useSettingsStore} from '@/store/modules/settings' import { useSettingsStore } from '@/store/modules/settings';
import {usePermissionStore} from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission';
const route = useRoute() const route = useRoute();
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore();
const theme = computed(() => settingsStore.theme) const theme = computed(() => settingsStore.theme);
const activeMenu = computed(() => { const activeMenu = computed(() => {
const { meta, path } = route const { meta, path } = route;
if (meta.activeMenu) { if (meta.activeMenu) {
return meta.activeMenu return meta.activeMenu;
} }
return path return path;
}) });
const visibleNumber = ref(5) const visibleNumber = ref(5);
const topMenus = computed(() => { const topMenus = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value) return permissionStore.sidebarRouters.filter(f => !f.hidden).slice(0, visibleNumber.value);
}) });
const moreRoutes = computed(() => { const moreRoutes = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value) return permissionStore.sidebarRouters.filter(f => !f.hidden).slice(visibleNumber.value);
}) });
function setVisibleNumber() { function setVisibleNumber() {
let width = document.body.getBoundingClientRect().width let width = document.body.getBoundingClientRect().width;
if (width >= 1000) { if (width >= 1000) {
width -= 420 width -= 420;
} }
visibleNumber.value = Math.max(1, Math.floor(width / 3 / 92) + 2) visibleNumber.value = Math.max(1, Math.floor(width / 3 / 92) + 2);
} }
onMounted(() => { onMounted(() => {
window.addEventListener('resize', setVisibleNumber) window.addEventListener('resize', setVisibleNumber);
}) });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', setVisibleNumber) window.removeEventListener('resize', setVisibleNumber);
}) });
onMounted(() => { onMounted(() => {
setVisibleNumber() setVisibleNumber();
}) });
</script> </script>
<style lang="scss"> <style lang="scss">
+21 -7
View File
@@ -26,7 +26,9 @@
</el-input> </el-input>
<div v-if="state.menuQuery && state.options.length > 0" class="result-count"> <div v-if="state.menuQuery && state.options.length > 0" class="result-count">
找到 <strong>{{ state.options.length }}</strong> 个结果 找到
<strong>{{ state.options.length }}</strong>
个结果
</div> </div>
<el-scrollbar wrap-class="layout-search-scrollbar"> <el-scrollbar wrap-class="layout-search-scrollbar">
@@ -57,7 +59,9 @@
<div v-else-if="state.menuQuery" class="empty-state"> <div v-else-if="state.menuQuery" class="empty-state">
<el-icon class="empty-icon"><Search /></el-icon> <el-icon class="empty-icon"><Search /></el-icon>
<p class="empty-text"> <p class="empty-text">
未找到 "<strong>{{ state.menuQuery }}</strong>" 相关菜单 未找到 "
<strong>{{ state.menuQuery }}</strong>
" 相关菜单
</p> </p>
<p class="empty-tip">试试其他关键词或路径</p> <p class="empty-tip">试试其他关键词或路径</p>
</div> </div>
@@ -65,9 +69,19 @@
</el-scrollbar> </el-scrollbar>
<div class="search-footer"> <div class="search-footer">
<span class="shortcut-item"><kbd></kbd><kbd></kbd> 切换</span> <span class="shortcut-item">
<span class="shortcut-item"><kbd></kbd> 选择</span> <kbd></kbd>
<span class="shortcut-item"><kbd>Esc</kbd> 关闭</span> <kbd></kbd>
切换
</span>
<span class="shortcut-item">
<kbd></kbd>
选择
</span>
<span class="shortcut-item">
<kbd>Esc</kbd>
关闭
</span>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
@@ -143,7 +157,7 @@ const handleClose = () => {
const generateRoutes = (routeList: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []): SearchMenuItem[] => { const generateRoutes = (routeList: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []): SearchMenuItem[] => {
let result: SearchMenuItem[] = []; let result: SearchMenuItem[] = [];
routeList.forEach((route) => { routeList.forEach(route => {
if (route.hidden) { if (route.hidden) {
return; return;
} }
@@ -183,7 +197,7 @@ const querySearch = (query: string) => {
} }
const keyword = query.toLowerCase(); const keyword = query.toLowerCase();
state.options = state.menuList.filter((item) => { state.options = state.menuList.filter(item => {
return item.fullTitle.toLowerCase().includes(keyword) || item.path.toLowerCase().includes(keyword); return item.fullTitle.toLowerCase().includes(keyword) || item.path.toLowerCase().includes(keyword);
}); });
}; };
+1 -1
View File
@@ -94,7 +94,7 @@ const readAll = () => {
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
.head-box { .head-box {
display: flex; display: flex;
border-bottom: 1px solid rgba(148, 163, 184, 0.14); border-bottom: 1px solid rgba(148, 163, 184, 0.14);
box-sizing: border-box; box-sizing: border-box;
+10 -3
View File
@@ -16,7 +16,7 @@ NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*']; const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
const isWhiteList = (path: string) => { const isWhiteList = (path: string) => {
return whiteList.some((pattern) => isPathMatch(pattern, path)); return whiteList.some(pattern => isPathMatch(pattern, path));
}; };
router.beforeEach(async (to, from) => { router.beforeEach(async (to, from) => {
@@ -42,13 +42,20 @@ router.beforeEach(async (to, from) => {
isRelogin.show = false; isRelogin.show = false;
const accessRoutes = await usePermissionStore().generateRoutes(); const accessRoutes = await usePermissionStore().generateRoutes();
// 根据roles权限生成可访问的路由表 // 根据roles权限生成可访问的路由表
accessRoutes.forEach((route) => { accessRoutes.forEach(route => {
if (!isHttp(route.path)) { if (!isHttp(route.path)) {
router.addRoute(route); // 动态添加可访问路由表 router.addRoute(route); // 动态添加可访问路由表
} }
}); });
// hack方法 确保addRoutes已完成 // hack方法 确保addRoutes已完成
return { path: to.path, replace: true, params: to.params, query: to.query, hash: to.hash, name: to.name as string }; return {
path: to.path,
replace: true,
params: to.params,
query: to.query,
hash: to.hash,
name: to.name as string
};
} }
} else { } else {
return true; return true;
+6 -6
View File
@@ -4,7 +4,7 @@ const authPermission = (permission: string): boolean => {
const all_permission = '*:*:*'; const all_permission = '*:*:*';
const permissions: string[] = useUserStore().permissions; const permissions: string[] = useUserStore().permissions;
if (permission && permission.length > 0) { if (permission && permission.length > 0) {
return permissions.some((v) => { return permissions.some(v => {
return all_permission === v || v === permission; return all_permission === v || v === permission;
}); });
} else { } else {
@@ -16,7 +16,7 @@ const authRole = (role: string): boolean => {
const super_admin = 'admin'; const super_admin = 'admin';
const roles = useUserStore().roles; const roles = useUserStore().roles;
if (role && role.length > 0) { if (role && role.length > 0) {
return roles.some((v) => { return roles.some(v => {
return super_admin === v || v === role; return super_admin === v || v === role;
}); });
} else { } else {
@@ -31,13 +31,13 @@ export default {
}, },
// 验证用户是否含有指定权限,只需包含其中一个 // 验证用户是否含有指定权限,只需包含其中一个
hasPermiOr(permissions: string[]): boolean { hasPermiOr(permissions: string[]): boolean {
return permissions.some((item) => { return permissions.some(item => {
return authPermission(item); return authPermission(item);
}); });
}, },
// 验证用户是否含有指定权限,必须全部拥有 // 验证用户是否含有指定权限,必须全部拥有
hasPermiAnd(permissions: string[]): boolean { hasPermiAnd(permissions: string[]): boolean {
return permissions.every((item) => { return permissions.every(item => {
return authPermission(item); return authPermission(item);
}); });
}, },
@@ -47,13 +47,13 @@ export default {
}, },
// 验证用户是否含有指定角色,只需包含其中一个 // 验证用户是否含有指定角色,只需包含其中一个
hasRoleOr(roles: string[]): boolean { hasRoleOr(roles: string[]): boolean {
return roles.some((item) => { return roles.some(item => {
return authRole(item); return authRole(item);
}); });
}, },
// 验证用户是否含有指定角色,必须全部拥有 // 验证用户是否含有指定角色,必须全部拥有
hasRoleAnd(roles: string[]): boolean { hasRoleAnd(roles: string[]): boolean {
return roles.every((item) => { return roles.every(item => {
return authRole(item); return authRole(item);
}); });
} }
+8 -2
View File
@@ -10,7 +10,10 @@ let downloadLoadingInstance: LoadingInstance | undefined;
export default { export default {
async oss(ossId: string | number) { async oss(ossId: string | number) {
const url = baseURL + '/resource/oss/download/' + ossId; const url = baseURL + '/resource/oss/download/' + ossId;
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' }); downloadLoadingInstance = ElLoading.service({
text: '正在下载数据,请稍候',
background: 'rgba(0, 0, 0, 0.7)'
});
try { try {
const res = await axios({ const res = await axios({
method: 'get', method: 'get',
@@ -35,7 +38,10 @@ export default {
}, },
async zip(url: string, name: string) { async zip(url: string, name: string) {
url = baseURL + url; url = baseURL + url;
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' }); downloadLoadingInstance = ElLoading.service({
text: '正在下载数据,请稍候',
background: 'rgba(0, 0, 0, 0.7)'
});
try { try {
const res = await axios({ const res = await axios({
method: 'get', method: 'get',
+3 -1
View File
@@ -52,7 +52,9 @@ export default {
} }
}, },
// 关闭指定tab页签 // 关闭指定tab页签
async closePage(obj?: RouteLocationNormalized): Promise<{ visitedViews: RouteLocationNormalized[]; cachedViews: string[] } | any> { async closePage(
obj?: RouteLocationNormalized
): Promise<{ visitedViews: RouteLocationNormalized[]; cachedViews: string[] } | any> {
if (obj === undefined) { if (obj === undefined) {
// prettier-ignore // prettier-ignore
const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value) const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value)
+1 -3
View File
@@ -92,9 +92,7 @@ export const constantRoutes: RouteRecordRaw[] = [
]; ];
// 动态路由,基于用户权限动态去加载 // 动态路由,基于用户权限动态去加载
export const dynamicRoutes: RouteRecordRaw[] = [ export const dynamicRoutes: RouteRecordRaw[] = [];
];
/** /**
* *
+6 -6
View File
@@ -21,7 +21,7 @@ export const useNoticeStore = defineStore('notice', () => {
notices: [] as NoticeItem[] notices: [] as NoticeItem[]
}); });
const unreadCount = computed(() => state.notices.filter((item) => !item.read).length); const unreadCount = computed(() => state.notices.filter(item => !item.read).length);
const buildNoticeKey = (notice: NoticeItem) => { const buildNoticeKey = (notice: NoticeItem) => {
if (notice.messageId !== undefined && notice.messageId !== null) { if (notice.messageId !== undefined && notice.messageId !== null) {
@@ -41,7 +41,7 @@ export const useNoticeStore = defineStore('notice', () => {
const addNotice = (notice: NoticeItem) => { const addNotice = (notice: NoticeItem) => {
const key = buildNoticeKey(notice); const key = buildNoticeKey(notice);
const index = state.notices.findIndex((item) => buildNoticeKey(item) === key); const index = state.notices.findIndex(item => buildNoticeKey(item) === key);
if (index > -1) { if (index > -1) {
state.notices[index] = { state.notices[index] = {
...state.notices[index], ...state.notices[index],
@@ -57,15 +57,15 @@ export const useNoticeStore = defineStore('notice', () => {
if (messageId === undefined || messageId === null) { if (messageId === undefined || messageId === null) {
return; return;
} }
const target = state.notices.find((item) => String(item.messageId) === String(messageId)); const target = state.notices.find(item => String(item.messageId) === String(messageId));
if (target) { if (target) {
target.read = true; target.read = true;
} }
}; };
const markReadBatch = (messageIds: Array<string | number>) => { const markReadBatch = (messageIds: Array<string | number>) => {
const idSet = new Set(messageIds.map((item) => String(item))); const idSet = new Set(messageIds.map(item => String(item)));
state.notices.forEach((item) => { state.notices.forEach(item => {
if (item.messageId !== undefined && item.messageId !== null && idSet.has(String(item.messageId))) { if (item.messageId !== undefined && item.messageId !== null && idSet.has(String(item.messageId))) {
item.read = true; item.read = true;
} }
@@ -75,7 +75,7 @@ export const useNoticeStore = defineStore('notice', () => {
const readAll = () => { const readAll = () => {
markReadBatch( markReadBatch(
state.notices state.notices
.map((item) => item.messageId) .map(item => item.messageId)
.filter((item): item is string | number => item !== undefined && item !== null) .filter((item): item is string | number => item !== undefined && item !== null)
); );
}; };
+12 -8
View File
@@ -56,7 +56,7 @@ export const usePermissionStore = defineStore('permission', () => {
const rewriteRoutes = filterAsyncRouter(rdata, undefined, true); const rewriteRoutes = filterAsyncRouter(rdata, undefined, true);
const defaultRoutes = filterAsyncRouter(defaultData); const defaultRoutes = filterAsyncRouter(defaultData);
const asyncRoutes = filterDynamicRoutes(dynamicRoutes); const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
asyncRoutes.forEach((route) => { asyncRoutes.forEach(route => {
router.addRoute(route); router.addRoute(route);
}); });
setRoutes(rewriteRoutes); setRoutes(rewriteRoutes);
@@ -65,7 +65,7 @@ export const usePermissionStore = defineStore('permission', () => {
setTopbarRoutes(defaultRoutes); setTopbarRoutes(defaultRoutes);
// 路由name重复检查 // 路由name重复检查
duplicateRouteChecker(asyncRoutes, sidebarRoutes); duplicateRouteChecker(asyncRoutes, sidebarRoutes);
return new Promise<RouteRecordRaw[]>((resolve) => resolve(rewriteRoutes)); return new Promise<RouteRecordRaw[]>(resolve => resolve(rewriteRoutes));
}; };
/** /**
@@ -74,8 +74,12 @@ export const usePermissionStore = defineStore('permission', () => {
* @param lastRouter * @param lastRouter
* @param type * @param type
*/ */
const filterAsyncRouter = (asyncRouterMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw, type = false): RouteRecordRaw[] => { const filterAsyncRouter = (
return asyncRouterMap.filter((route) => { asyncRouterMap: RouteRecordRaw[],
lastRouter?: RouteRecordRaw,
type = false
): RouteRecordRaw[] => {
return asyncRouterMap.filter(route => {
if (type && route.children) { if (type && route.children) {
route.children = filterChildren(route.children, undefined); route.children = filterChildren(route.children, undefined);
} }
@@ -100,7 +104,7 @@ export const usePermissionStore = defineStore('permission', () => {
}; };
const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => { const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => {
let children: RouteRecordRaw[] = []; let children: RouteRecordRaw[] = [];
childrenMap.forEach((el) => { childrenMap.forEach(el => {
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path; el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path;
if (el.children && el.children.length && el.component?.toString() === 'ParentView') { if (el.children && el.children.length && el.component?.toString() === 'ParentView') {
children = children.concat(filterChildren(el.children, el)); children = children.concat(filterChildren(el.children, el));
@@ -130,7 +134,7 @@ export const usePermissionStore = defineStore('permission', () => {
// 动态路由遍历,验证是否具备权限 // 动态路由遍历,验证是否具备权限
export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => { export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => {
const res: RouteRecordRaw[] = []; const res: RouteRecordRaw[] = [];
routes.forEach((route) => { routes.forEach(route => {
if (route.permissions) { if (route.permissions) {
if (auth.hasPermiOr(route.permissions)) { if (auth.hasPermiOr(route.permissions)) {
res.push(route); res.push(route);
@@ -178,7 +182,7 @@ function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
// 展平 // 展平
function flatRoutes(routes: Route[]) { function flatRoutes(routes: Route[]) {
const res: Route[] = []; const res: Route[] = [];
routes.forEach((route) => { routes.forEach(route => {
if (route.children) { if (route.children) {
res.push(...flatRoutes(route.children)); res.push(...flatRoutes(route.children));
} else { } else {
@@ -191,7 +195,7 @@ function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
const allRoutes = flatRoutes([...localRoutes, ...routes]); const allRoutes = flatRoutes([...localRoutes, ...routes]);
const nameList: string[] = []; const nameList: string[] = [];
allRoutes.forEach((route) => { allRoutes.forEach(route => {
const name = route.name?.toString() ?? ''; const name = route.name?.toString() ?? '';
if (name && nameList.includes(name)) { if (name && nameList.includes(name)) {
const message = `路由名称: [${name}] 重复, 会造成 404`; const message = `路由名称: [${name}] 重复, 会造成 404`;
+24 -21
View File
@@ -34,8 +34,8 @@ const saveVisitedViews = (views: any[]) => {
} }
const payload: PersistedTagView[] = (views as TagView[]) const payload: PersistedTagView[] = (views as TagView[])
.filter((view) => !view.meta?.affix) .filter(view => !view.meta?.affix)
.map((view) => ({ .map(view => ({
path: view.path, path: view.path,
fullPath: view.fullPath, fullPath: view.fullPath,
name: view.name, name: view.name,
@@ -91,7 +91,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delIframeView = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => { const delIframeView = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
iframeViews.value = iframeViews.value.filter((item: RouteLocationNormalized) => item.path !== view.path); iframeViews.value = iframeViews.value.filter((item: RouteLocationNormalized) => item.path !== view.path);
resolve(iframeViews.value.slice() as RouteLocationNormalized[]); resolve(iframeViews.value.slice() as RouteLocationNormalized[]);
}); });
@@ -105,7 +105,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
const addAffixView = (view: RouteLocationNormalized): void => { const addAffixView = (view: RouteLocationNormalized): void => {
if (visitedViews.value.some((v: RouteLocationNormalized) => v.path === view.path)) return; if (visitedViews.value.some((v: RouteLocationNormalized) => v.path === view.path)) return;
const insertIndex = visitedViews.value.findIndex((item) => !item.meta?.affix); const insertIndex = visitedViews.value.findIndex(item => !item.meta?.affix);
const normalizedView = normalizeVisitedView(view); const normalizedView = normalizeVisitedView(view);
if (insertIndex === -1) { if (insertIndex === -1) {
visitedViews.value.push(normalizedView); visitedViews.value.push(normalizedView);
@@ -115,8 +115,8 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const loadPersistedViews = (): void => { const loadPersistedViews = (): void => {
loadVisitedViews().forEach((view) => { loadVisitedViews().forEach(view => {
if (visitedViews.value.some((item) => item.path === view.path)) { if (visitedViews.value.some(item => item.path === view.path)) {
return; return;
} }
@@ -141,7 +141,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
visitedViews: RouteLocationNormalized[]; visitedViews: RouteLocationNormalized[];
cachedViews: string[]; cachedViews: string[];
}> => { }> => {
return new Promise((resolve) => { return new Promise(resolve => {
delVisitedView(view); delVisitedView(view);
if (!isDynamicRoute(view)) { if (!isDynamicRoute(view)) {
delCachedView(view); delCachedView(view);
@@ -154,7 +154,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delVisitedView = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => { const delVisitedView = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
for (const [i, v] of visitedViews.value.entries()) { for (const [i, v] of visitedViews.value.entries()) {
if (v.path === view.path) { if (v.path === view.path) {
visitedViews.value.splice(i, 1); visitedViews.value.splice(i, 1);
@@ -171,7 +171,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
if (view) { if (view) {
viewName = view.name as string; viewName = view.name as string;
} }
return new Promise((resolve) => { return new Promise(resolve => {
const index = cachedViews.value.indexOf(viewName); const index = cachedViews.value.indexOf(viewName);
index > -1 && cachedViews.value.splice(index, 1); index > -1 && cachedViews.value.splice(index, 1);
resolve([...cachedViews.value]); resolve([...cachedViews.value]);
@@ -184,7 +184,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
visitedViews: RouteLocationNormalized[]; visitedViews: RouteLocationNormalized[];
cachedViews: string[]; cachedViews: string[];
}> => { }> => {
return new Promise((resolve) => { return new Promise(resolve => {
delOthersVisitedViews(view); delOthersVisitedViews(view);
delOthersCachedViews(view); delOthersCachedViews(view);
resolve({ resolve({
@@ -195,7 +195,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delOthersVisitedViews = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => { const delOthersVisitedViews = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
visitedViews.value = visitedViews.value.filter((v: RouteLocationNormalized) => { visitedViews.value = visitedViews.value.filter((v: RouteLocationNormalized) => {
return v.meta?.affix || v.path === view.path; return v.meta?.affix || v.path === view.path;
}); });
@@ -206,7 +206,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
const delOthersCachedViews = (view: RouteLocationNormalized): Promise<string[]> => { const delOthersCachedViews = (view: RouteLocationNormalized): Promise<string[]> => {
const viewName = view.name as string; const viewName = view.name as string;
return new Promise((resolve) => { return new Promise(resolve => {
const index = cachedViews.value.indexOf(viewName); const index = cachedViews.value.indexOf(viewName);
if (index > -1) { if (index > -1) {
cachedViews.value = cachedViews.value.slice(index, index + 1); cachedViews.value = cachedViews.value.slice(index, index + 1);
@@ -217,8 +217,11 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}); });
}; };
const delAllViews = (): Promise<{ visitedViews: RouteLocationNormalized[]; cachedViews: string[] }> => { const delAllViews = (): Promise<{
return new Promise((resolve) => { visitedViews: RouteLocationNormalized[];
cachedViews: string[];
}> => {
return new Promise(resolve => {
delAllVisitedViews(); delAllVisitedViews();
delAllCachedViews(); delAllCachedViews();
resolve({ resolve({
@@ -229,7 +232,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delAllVisitedViews = (): Promise<RouteLocationNormalized[]> => { const delAllVisitedViews = (): Promise<RouteLocationNormalized[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
visitedViews.value = visitedViews.value.filter((tag: RouteLocationNormalized) => tag.meta?.affix); visitedViews.value = visitedViews.value.filter((tag: RouteLocationNormalized) => tag.meta?.affix);
clearVisitedViews(); clearVisitedViews();
resolve(visitedViews.value.slice() as RouteLocationNormalized[]); resolve(visitedViews.value.slice() as RouteLocationNormalized[]);
@@ -237,7 +240,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delAllCachedViews = (): Promise<string[]> => { const delAllCachedViews = (): Promise<string[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
cachedViews.value = []; cachedViews.value = [];
resolve([...cachedViews.value]); resolve([...cachedViews.value]);
}); });
@@ -254,10 +257,10 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delRightTags = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => { const delRightTags = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
const index = visitedViews.value.findIndex((v: RouteLocationNormalized) => v.path === view.path); const index = visitedViews.value.findIndex((v: RouteLocationNormalized) => v.path === view.path);
if (index === -1) { if (index === -1) {
resolve(visitedViews.value.slice() as RouteLocationNormalized[]); resolve(visitedViews.value.slice() as RouteLocationNormalized[]);
return; return;
} }
visitedViews.value = visitedViews.value.filter((item: RouteLocationNormalized, idx: number) => { visitedViews.value = visitedViews.value.filter((item: RouteLocationNormalized, idx: number) => {
@@ -276,10 +279,10 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const delLeftTags = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => { const delLeftTags = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
return new Promise((resolve) => { return new Promise(resolve => {
const index = visitedViews.value.findIndex((v: RouteLocationNormalized) => v.path === view.path); const index = visitedViews.value.findIndex((v: RouteLocationNormalized) => v.path === view.path);
if (index === -1) { if (index === -1) {
resolve(visitedViews.value.slice() as RouteLocationNormalized[]); resolve(visitedViews.value.slice() as RouteLocationNormalized[]);
return; return;
} }
visitedViews.value = visitedViews.value.filter((item: RouteLocationNormalized, idx: number) => { visitedViews.value = visitedViews.value.filter((item: RouteLocationNormalized, idx: number) => {
@@ -307,7 +310,7 @@ export const useTagsViewStore = defineStore('tagsView', () => {
}; };
const isDynamicRoute = (view: RouteLocationNormalized): boolean => { const isDynamicRoute = (view: RouteLocationNormalized): boolean => {
return view.matched.some((m) => m.path.includes(':')); return view.matched.some(m => m.path.includes(':'));
}; };
return { return {
-2
View File
@@ -10,8 +10,6 @@ import { getConfigKey, updateConfigByKey } from '@/api/system/config';
import { download as rd } from '@/utils/request'; import { download as rd } from '@/utils/request';
import type { LanguageType } from '@/lang'; import type { LanguageType } from '@/lang';
export {};
declare module 'vue' { declare module 'vue' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
// 全局方法声明 // 全局方法声明
+1 -1
View File
@@ -9,7 +9,7 @@ const CryptoJS = ('default' in CryptoJSModule ? CryptoJSModule.default : CryptoJ
const generateRandomString = (): string => { const generateRandomString = (): string => {
const array = new Uint8Array(32); const array = new Uint8Array(32);
crypto.getRandomValues(array); crypto.getRandomValues(array);
return Array.from(array, (b) => b.toString(16).padStart(2, '0')) return Array.from(array, b => b.toString(16).padStart(2, '0'))
.join('') .join('')
.slice(0, 32); .slice(0, 32);
}; };
+8 -3
View File
@@ -8,15 +8,20 @@ export const useDict = (...args: string[]): { [key: string]: DictDataOption[] }
[key: string]: DictDataOption[]; [key: string]: DictDataOption[];
}>({}); }>({});
args.forEach(async (dictType) => { args.forEach(async dictType => {
res.value[dictType] = []; res.value[dictType] = [];
const dicts = useDictStore().getDict(dictType); const dicts = useDictStore().getDict(dictType);
if (dicts) { if (dicts) {
res.value[dictType] = dicts; res.value[dictType] = dicts;
} else { } else {
await getDicts(dictType).then((resp) => { await getDicts(dictType).then(resp => {
res.value[dictType] = resp.data.map( res.value[dictType] = resp.data.map(
(p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }) (p): DictDataOption => ({
label: p.dictLabel,
value: p.dictValue,
elTagType: p.listClass,
elTagClass: p.cssClass
})
); );
useDictStore().setDict(dictType, res.value[dictType]); useDictStore().setDict(dictType, res.value[dictType]);
}); });
+4 -4
View File
@@ -105,7 +105,7 @@ export const cleanArray = (actual: Array<any>) => {
export const param = (json: any) => { export const param = (json: any) => {
if (!json) return ''; if (!json) return '';
return cleanArray( return cleanArray(
Object.keys(json).map((key) => { Object.keys(json).map(key => {
if (json[key] === undefined) return ''; if (json[key] === undefined) return '';
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]); return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
}) })
@@ -123,7 +123,7 @@ export const param2Obj = (url: string) => {
} }
const obj: any = {}; const obj: any = {};
const searchArr = search.split('&'); const searchArr = search.split('&');
searchArr.forEach((v) => { searchArr.forEach(v => {
const index = v.indexOf('='); const index = v.indexOf('=');
if (index !== -1) { if (index !== -1) {
const name = v.substring(0, index); const name = v.substring(0, index);
@@ -157,7 +157,7 @@ export const objectMerge = (target: any, source: any | any[]) => {
if (Array.isArray(source)) { if (Array.isArray(source)) {
return source.slice(); return source.slice();
} }
Object.keys(source).forEach((property) => { Object.keys(source).forEach(property => {
const sourceProperty = source[property]; const sourceProperty = source[property];
if (typeof sourceProperty === 'object') { if (typeof sourceProperty === 'object') {
target[property] = objectMerge(target[property], sourceProperty); target[property] = objectMerge(target[property], sourceProperty);
@@ -250,7 +250,7 @@ export const deepClone = (source: any) => {
throw new Error('error arguments', 'deepClone' as any); throw new Error('error arguments', 'deepClone' as any);
} }
const targetObj: any = source.constructor === Array ? [] : {}; const targetObj: any = source.constructor === Array ? [] : {};
Object.keys(source).forEach((keys) => { Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') { if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys]); targetObj[keys] = deepClone(source[keys]);
} else { } else {
+1 -3
View File
@@ -6,9 +6,7 @@ const MAX_READ_IDS = 300;
const buildCacheKey = (userId: string | number) => `${MESSAGE_READ_KEY}:${userId}`; const buildCacheKey = (userId: string | number) => `${MESSAGE_READ_KEY}:${userId}`;
const normalizeIds = (ids: Array<string | number>) => { const normalizeIds = (ids: Array<string | number>) => {
const values = ids const values = ids.map(item => String(item)).filter(item => !!item);
.map((item) => String(item))
.filter((item) => !!item);
return Array.from(new Set(values)).slice(0, MAX_READ_IDS); return Array.from(new Set(values)).slice(0, MAX_READ_IDS);
}; };
+2 -2
View File
@@ -11,7 +11,7 @@ export const checkPermi = (value: any) => {
const permissionDatas = value; const permissionDatas = value;
const all_permission = '*:*:*'; const all_permission = '*:*:*';
const hasPermission = permissions.some((permission) => { const hasPermission = permissions.some(permission => {
return all_permission === permission || permissionDatas.includes(permission); return all_permission === permission || permissionDatas.includes(permission);
}); });
@@ -36,7 +36,7 @@ export const checkRole = (value: any): boolean => {
const permissionRoles = value; const permissionRoles = value;
const super_admin = 'admin'; const super_admin = 'admin';
const hasRole = roles.some((role) => { const hasRole = roles.some(role => {
return super_admin === role || permissionRoles.includes(role); return super_admin === role || permissionRoles.includes(role);
}); });
+3 -1
View File
@@ -145,7 +145,9 @@ export const initMessageBox = async () => {
return; return;
} }
const { data } = await getMessageBox(); const { data } = await getMessageBox();
const notices = [...(data?.systemList ?? []), ...(data?.noticeList ?? []), ...(data?.workflowList ?? [])].map(toNoticeItem); const notices = [...(data?.systemList ?? []), ...(data?.noticeList ?? []), ...(data?.workflowList ?? [])].map(
toNoticeItem
);
useNoticeStore().setNotices(notices); useNoticeStore().setNotices(notices);
}; };
+9 -3
View File
@@ -145,7 +145,10 @@ service.interceptors.request.use(
// 生成一个 AES 密钥 // 生成一个 AES 密钥
const aesKey = generateAesKey(); const aesKey = generateAesKey();
config.headers[encryptHeader] = encrypt(encryptBase64(aesKey)); config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey); config.data =
typeof config.data === 'object'
? encryptWithAes(JSON.stringify(config.data), aesKey)
: encryptWithAes(config.data, aesKey);
} }
} }
// FormData数据去请求头Content-Type // FormData数据去请求头Content-Type
@@ -223,14 +226,17 @@ service.interceptors.response.use(
} }
}, },
async (error: any) => { async (error: any) => {
const message = await extractErrorMessage(error) || errorCode['default']; const message = (await extractErrorMessage(error)) || errorCode['default'];
ElMessage({ message: message, type: 'error', duration: 5 * 1000 }); ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
return Promise.reject(error); return Promise.reject(error);
} }
); );
// 通用下载方法 // 通用下载方法
export function download(url: string, params: any, fileName: string) { export function download(url: string, params: any, fileName: string) {
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' }); downloadLoadingInstance = ElLoading.service({
text: '正在下载数据,请稍候',
background: 'rgba(0, 0, 0, 0.7)'
});
// prettier-ignore // prettier-ignore
return service.post(url, params, { return service.post(url, params, {
transformRequest: [ transformRequest: [
+5 -4
View File
@@ -51,7 +51,8 @@ export function parseTime(time: any, pattern?: string) {
*/ */
export const addDateRange = (params: any, dateRange: any[], propName?: string) => { export const addDateRange = (params: any, dateRange: any[], propName?: string) => {
const search = params; const search = params;
search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; search.params =
typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
dateRange = Array.isArray(dateRange) ? dateRange : []; dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof propName === 'undefined') { if (typeof propName === 'undefined') {
search.params['beginTime'] = dateRange[0]; search.params['beginTime'] = dateRange[0];
@@ -69,7 +70,7 @@ export const selectDictLabel = (datas: any, value: number | string) => {
return ''; return '';
} }
const actions: Array<string | number> = []; const actions: Array<string | number> = [];
Object.keys(datas).some((key) => { Object.keys(datas).some(key => {
if (datas[key].value == '' + value) { if (datas[key].value == '' + value) {
actions.push(datas[key].label); actions.push(datas[key].label);
return true; return true;
@@ -92,9 +93,9 @@ export const selectDictLabels = (datas: any, value: any, separator: any) => {
const actions: any[] = []; const actions: any[] = [];
const currentSeparator = undefined === separator ? ',' : separator; const currentSeparator = undefined === separator ? ',' : separator;
const temp = value.split(currentSeparator); const temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => { Object.keys(value.split(currentSeparator)).some(val => {
let match = false; let match = false;
Object.keys(datas).some((key) => { Object.keys(datas).some(key => {
if (datas[key].value == '' + temp[val]) { if (datas[key].value == '' + temp[val]) {
actions.push(datas[key].label + currentSeparator); actions.push(datas[key].label + currentSeparator);
match = true; match = true;
+21 -4
View File
@@ -57,7 +57,24 @@ const TAG_ALLOWED_ATTRS: Record<string, Set<string>> = {
colgroup: new Set(['span']) colgroup: new Set(['span'])
}; };
const DANGEROUS_TAGS = new Set(['script', 'style', 'iframe', 'object', 'embed', 'link', 'meta', 'base', 'form', 'input', 'button', 'textarea', 'select', 'option', 'svg', 'math']); const DANGEROUS_TAGS = new Set([
'script',
'style',
'iframe',
'object',
'embed',
'link',
'meta',
'base',
'form',
'input',
'button',
'textarea',
'select',
'option',
'svg',
'math'
]);
const SAFE_URL_PATTERN = /^(https?:|mailto:|tel:|\/|#|data:image\/(?:png|jpeg|jpg|gif|webp);base64,)/i; const SAFE_URL_PATTERN = /^(https?:|mailto:|tel:|\/|#|data:image\/(?:png|jpeg|jpg|gif|webp);base64,)/i;
const ALLOWED_STYLE_PROPS = new Set([ const ALLOWED_STYLE_PROPS = new Set([
'background-color', 'background-color',
@@ -93,10 +110,10 @@ const sanitizeUrl = (value: string) => {
const sanitizeStyle = (value: string) => { const sanitizeStyle = (value: string) => {
const declarations = value const declarations = value
.split(';') .split(';')
.map((item) => item.trim()) .map(item => item.trim())
.filter(Boolean); .filter(Boolean);
const safeDeclarations = declarations.flatMap((declaration) => { const safeDeclarations = declarations.flatMap(declaration => {
const separatorIndex = declaration.indexOf(':'); const separatorIndex = declaration.indexOf(':');
if (separatorIndex <= 0) { if (separatorIndex <= 0) {
return []; return [];
@@ -146,7 +163,7 @@ const sanitizeElement = (element: Element) => {
return; return;
} }
Array.from(element.attributes).forEach((attr) => { Array.from(element.attributes).forEach(attr => {
const attrName = attr.name.toLowerCase(); const attrName = attr.name.toLowerCase();
if (!shouldKeepAttr(tagName, attrName)) { if (!shouldKeepAttr(tagName, attrName)) {
element.removeAttribute(attr.name); element.removeAttribute(attr.name);
+3 -1
View File
@@ -30,7 +30,9 @@ const move = (amount: number) => {
}; };
const position = () => { const position = () => {
return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop; return (
document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop
);
}; };
/** /**
+58 -10
View File
@@ -29,16 +29,44 @@
<h3>测试单列表</h3> <h3>测试单列表</h3>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> <el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">
<el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button> 新增
<el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button> </el-button>
<el-button v-hasPermi="['demo:demo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button> <el-button
v-hasPermi="['demo:demo:edit']"
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
>
修改
</el-button>
<el-button
v-hasPermi="['demo:demo:remove']"
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
>
删除
</el-button>
<el-button v-hasPermi="['demo:demo:export']" type="warning" plain icon="Download" @click="handleExport">
导出
</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
</div> </div>
</template> </template>
<el-table v-loading="loading" border class="data-table" :data="demoList" @selection-change="handleSelectionChange"> <el-table
v-loading="loading"
border
class="data-table"
:data="demoList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="主键" align="center" prop="id" /> <el-table-column v-if="true" label="主键" align="center" prop="id" />
<el-table-column label="部门id" align="center" prop="deptId" /> <el-table-column label="部门id" align="center" prop="deptId" />
@@ -49,16 +77,34 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['demo:demo:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> <el-button
v-hasPermi="['demo:demo:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['demo:demo:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> <el-button
v-hasPermi="['demo:demo:remove']"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> <pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card> </el-card>
<!-- 添加或修改测试单对话框 --> <!-- 添加或修改测试单对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body> <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
@@ -178,7 +224,7 @@ const resetQuery = () => {
/** 多选框选中数据 */ /** 多选框选中数据 */
const handleSelectionChange = (selection: DemoVO[]) => { const handleSelectionChange = (selection: DemoVO[]) => {
ids.value = selection.map((item) => item.id); ids.value = selection.map(item => item.id);
single.value = selection.length != 1; single.value = selection.length != 1;
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
@@ -220,7 +266,9 @@ const submitForm = () => {
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (row?: DemoVO) => { const handleDelete = async (row?: DemoVO) => {
const _ids = row?.id || ids.value; const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除测试单编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false)); await proxy?.$modal
.confirm('是否确认删除测试单编号为"' + _ids + '"的数据项?')
.finally(() => (loading.value = false));
await delDemo(_ids); await delDemo(_ids);
proxy?.$modal.msgSuccess('删除成功'); proxy?.$modal.msgSuccess('删除成功');
await getList(); await getList();
+24 -5
View File
@@ -9,7 +9,12 @@
</template> </template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="树节点名" prop="treeName"> <el-form-item label="树节点名" prop="treeName">
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.treeName"
placeholder="请输入树节点名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -26,7 +31,9 @@
<h3>测试树列表</h3> <h3>测试树列表</h3>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['demo:tree:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button> <el-button v-hasPermi="['demo:tree:add']" type="primary" plain icon="Plus" @click="handleAdd()">
新增
</el-button>
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button> <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
@@ -49,13 +56,25 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['demo:tree:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" /> <el-button
v-hasPermi="['demo:tree:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="新增" placement="top"> <el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['demo:tree:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" /> <el-button v-hasPermi="['demo:tree:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['demo:tree:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" /> <el-button
v-hasPermi="['demo:tree:remove']"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
/>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -213,7 +232,7 @@ const handleToggleExpandAll = () => {
/** 展开/折叠操作 */ /** 展开/折叠操作 */
const toggleExpandAll = (data: TreeVO[], status: boolean) => { const toggleExpandAll = (data: TreeVO[], status: boolean) => {
data.forEach((item) => { data.forEach(item => {
treeTableRef.value?.toggleRowExpansion(item, status); treeTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status); if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
}); });
+2 -2
View File
@@ -1,6 +1,6 @@
<template> <template>
<div class="errPage-container"> <div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back"> 返回 </el-button> <el-button icon="arrow-left" class="pan-back-btn" @click="back">返回</el-button>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<h1 class="text-jumbo text-ginormous">401错误!</h1> <h1 class="text-jumbo text-ginormous">401错误!</h1>
@@ -8,7 +8,7 @@
<h6>对不起您没有访问权限请不要进行非法操作您可以返回主页面</h6> <h6>对不起您没有访问权限请不要进行非法操作您可以返回主页面</h6>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li class="link-type"> <li class="link-type">
<router-link to="/"> 回首页 </router-link> <router-link to="/">回首页</router-link>
</li> </li>
</ul> </ul>
</el-col> </el-col>
+1 -1
View File
@@ -15,7 +15,7 @@
<div class="bullshit__info"> <div class="bullshit__info">
对不起您正在寻找的页面不存在尝试检查URL的错误然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容 对不起您正在寻找的页面不存在尝试检查URL的错误然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容
</div> </div>
<router-link to="/index" class="bullshit__return-home"> 返回首页 </router-link> <router-link to="/index" class="bullshit__return-home">返回首页</router-link>
</div> </div>
</div> </div>
</div> </div>
+10 -3
View File
@@ -3,10 +3,15 @@
<section class="hero-panel"> <section class="hero-panel">
<div class="hero-copy"> <div class="hero-copy">
<h1>RuoYi-Vue-Plus 控制台</h1> <h1>RuoYi-Vue-Plus 控制台</h1>
<p>企业级后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 等组件</p> <p>
企业级后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS
等组件
</p>
<div class="hero-actions"> <div class="hero-actions">
<el-button type="primary" @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">查看源码</el-button> <el-button type="primary" @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">查看源码</el-button>
<el-button plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')">更新日志</el-button> <el-button plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')">
更新日志
</el-button>
</div> </div>
</div> </div>
</section> </section>
@@ -31,7 +36,9 @@
<el-tag v-for="tag in product.tags" :key="tag" effect="plain">{{ tag }}</el-tag> <el-tag v-for="tag in product.tags" :key="tag" effect="plain">{{ tag }}</el-tag>
</div> </div>
<div class="product-actions"> <div class="product-actions">
<el-button type="primary" plain @click="goTarget(product.primaryUrl)">{{ product.primaryLabel }}</el-button> <el-button type="primary" plain @click="goTarget(product.primaryUrl)">
{{ product.primaryLabel }}
</el-button>
<el-button plain @click="goTarget(product.secondaryUrl)">{{ product.secondaryLabel }}</el-button> <el-button plain @click="goTarget(product.secondaryUrl)">{{ product.secondaryLabel }}</el-button>
</div> </div>
</article> </article>
+21 -5
View File
@@ -5,7 +5,8 @@
<span class="brand-pill">Plus UI Workspace</span> <span class="brand-pill">Plus UI Workspace</span>
<h1 class="brand-title">企业级后台管理系统</h1> <h1 class="brand-title">企业级后台管理系统</h1>
<p class="brand-desc"> <p class="brand-desc">
真正面向企业级的应用框架 组件化 模块化 轻耦合 高扩展 针对企业痛点 业界一流技术栈<br /> 真正面向企业级的应用框架 组件化 模块化 轻耦合 高扩展 针对企业痛点 业界一流技术栈
<br />
重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 定期同步 重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 定期同步
</p> </p>
<div class="brand-highlights"> <div class="brand-highlights">
@@ -30,7 +31,13 @@
</div> </div>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('login.username')"> <el-input
v-model="loginForm.username"
type="text"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.username')"
>
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -49,7 +56,13 @@
</el-form-item> </el-form-item>
<el-form-item v-if="captchaEnabled" prop="code" class="captcha-row"> <el-form-item v-if="captchaEnabled" prop="code" class="captcha-row">
<el-input v-model="loginForm.code" size="large" auto-complete="off" :placeholder="proxy.$t('login.code')" @keyup.enter="handleLogin"> <el-input
v-model="loginForm.code"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.code')"
@keyup.enter="handleLogin"
>
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
<div class="login-code"> <div class="login-code">
@@ -59,7 +72,9 @@
<div class="form-meta"> <div class="form-meta">
<el-checkbox v-model="loginForm.rememberMe">{{ proxy.$t('login.rememberPassword') }}</el-checkbox> <el-checkbox v-model="loginForm.rememberMe">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
<router-link v-if="register" class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link> <router-link v-if="register" class="link-type" :to="'/register'">
{{ proxy.$t('login.switchRegisterPage') }}
</router-link>
</div> </div>
<div class="social-panel"> <div class="social-panel">
@@ -227,7 +242,8 @@ onMounted(() => {
padding: 40px 24px 88px; padding: 40px 24px 88px;
background: background:
radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%), radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%), linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%); radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%),
linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%);
} }
.login-shell { .login-shell {
+12 -4
View File
@@ -26,7 +26,9 @@
<div class="cell">运行模式</div> <div class="cell">运行模式</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.redis_mode === 'standalone' ? '单机' : '集群' }}</div> <div v-if="cache.info" class="cell">
{{ cache.info.redis_mode === 'standalone' ? '单机' : '集群' }}
</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div class="cell">端口</div> <div class="cell">端口</div>
@@ -58,7 +60,9 @@
<div class="cell">使用CPU</div> <div class="cell">使用CPU</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div> <div v-if="cache.info" class="cell">
{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}
</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div class="cell">内存配置</div> <div class="cell">内存配置</div>
@@ -72,13 +76,17 @@
<div class="cell">AOF是否开启</div> <div class="cell">AOF是否开启</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.aof_enabled === '0' ? '' : '' }}</div> <div v-if="cache.info" class="cell">
{{ cache.info.aof_enabled === '0' ? '否' : '是' }}
</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div class="cell">RDB是否成功</div> <div class="cell">RDB是否成功</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.rdb_last_bgsave_status }}</div> <div v-if="cache.info" class="cell">
{{ cache.info.rdb_last_bgsave_status }}
</div>
</td> </td>
<td class="el-table__cell is-leaf"> <td class="el-table__cell is-leaf">
<div class="cell">Key数量</div> <div class="cell">Key数量</div>
+88 -46
View File
@@ -1,45 +1,50 @@
<template> <template>
<div class="p-2 app-container monitor-logininfo-page"> <div class="p-2 app-container monitor-logininfo-page">
<div class="search-wrap"> <div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header> <template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch"> <div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div> <div>
<span class="panel-kicker">Search Filters</span> <span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3> <h3>筛选条件</h3>
</div>
</div> </div>
</template> </div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> </template>
<el-form-item label="登录地址" prop="ipaddr"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" /> <el-form-item label="登录地址" prop="ipaddr">
</el-form-item> <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
<el-form-item label="用户名称" prop="userName"> </el-form-item>
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-form-item label="用户名称" prop="userName">
</el-form-item> <el-input
<el-form-item label="状态" prop="status"> v-model="queryParams.userName"
<el-select v-model="queryParams.status" placeholder="登录状态" clearable> placeholder="请输入用户名称"
<el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" /> clearable
</el-select> @keyup.enter="handleQuery"
</el-form-item> />
<el-form-item label="登录时间" style="width: 308px"> </el-form-item>
<el-date-picker <el-form-item label="状态" prop="status">
v-model="dateRange" <el-select v-model="queryParams.status" placeholder="登录状态" clearable>
value-format="YYYY-MM-DD HH:mm:ss" <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
type="daterange" </el-select>
range-separator="-" </el-form-item>
start-placeholder="开始日期" <el-form-item label="登录时间" style="width: 308px">
end-placeholder="结束日期" <el-date-picker
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" v-model="dateRange"
></el-date-picker> value-format="YYYY-MM-DD HH:mm:ss"
</el-form-item> type="daterange"
<el-form-item> range-separator="-"
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> start-placeholder="开始日期"
<el-button icon="Refresh" @click="resetQuery">重置</el-button> end-placeholder="结束日期"
</el-form-item> :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
</el-form> ></el-date-picker>
</el-card> </el-form-item>
</div> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
@@ -50,14 +55,38 @@
<p> {{ total }} 条记录支持排序批量清理导出和账号解锁</p> <p> {{ total }} 条记录支持排序批量清理导出和账号解锁</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['monitor:logininfo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button
v-hasPermi="['monitor:logininfo:remove']"
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
>
删除 删除
</el-button> </el-button>
<el-button v-hasPermi="['monitor:logininfo:remove']" type="danger" plain icon="Delete" @click="handleClean">清空</el-button> <el-button v-hasPermi="['monitor:logininfo:remove']" type="danger" plain icon="Delete" @click="handleClean">
<el-button v-hasPermi="['monitor:logininfo:unlock']" type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock"> 清空
</el-button>
<el-button
v-hasPermi="['monitor:logininfo:unlock']"
type="primary"
plain
icon="Unlock"
:disabled="single"
@click="handleUnlock"
>
解锁 解锁
</el-button> </el-button>
<el-button v-hasPermi="['monitor:logininfo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button> <el-button
v-hasPermi="['monitor:logininfo:export']"
type="warning"
plain
icon="Download"
@click="handleExport"
>
导出
</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
</div> </div>
@@ -99,14 +128,27 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" /> <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
<el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180"> <el-table-column
label="访问时间"
align="center"
prop="loginTime"
sortable="custom"
:sort-orders="['descending', 'ascending']"
width="180"
>
<template #default="scope"> <template #default="scope">
<span>{{ proxy.parseTime(scope.row.loginTime) }}</span> <span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> <pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card> </el-card>
</div> </div>
</template> </template>
@@ -165,10 +207,10 @@ const resetQuery = () => {
}; };
/** 多选框选中数据 */ /** 多选框选中数据 */
const handleSelectionChange = (selection: LoginInfoVO[]) => { const handleSelectionChange = (selection: LoginInfoVO[]) => {
ids.value = selection.map((item) => item.infoId); ids.value = selection.map(item => item.infoId);
multiple.value = !selection.length; multiple.value = !selection.length;
single.value = selection.length != 1; single.value = selection.length != 1;
selectName.value = selection.map((item) => item.userName); selectName.value = selection.map(item => item.userName);
}; };
/** 排序触发事件 */ /** 排序触发事件 */
const handleSortChange = (column: any) => { const handleSortChange = (column: any) => {
+22 -5
View File
@@ -15,7 +15,12 @@
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item label="用户名称" prop="userName"> <el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -38,7 +43,9 @@
v-loading="loading" v-loading="loading"
border border
class="data-table" class="data-table"
:data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)" :data="
onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)
"
style="width: 100%" style="width: 100%"
> >
<el-table-column label="序号" width="50" type="index" align="center"> <el-table-column label="序号" width="50" type="index" align="center">
@@ -67,14 +74,24 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="强退" placement="top"> <el-tooltip content="强退" placement="top">
<el-button v-hasPermi="['monitor:online:forceLogout']" link type="primary" icon="Delete" @click="handleForceLogout(scope.row)"> <el-button
</el-button> v-hasPermi="['monitor:online:forceLogout']"
link
type="primary"
icon="Delete"
@click="handleForceLogout(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" /> <pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
/>
</el-card> </el-card>
</div> </div>
</template> </template>
+100 -53
View File
@@ -1,53 +1,58 @@
<template> <template>
<div class="p-2 app-container monitor-operlog-page"> <div class="p-2 app-container monitor-operlog-page">
<div class="search-wrap"> <div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header> <template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch"> <div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div> <div>
<span class="panel-kicker">Search Filters</span> <span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3> <h3>筛选条件</h3>
</div>
</div> </div>
</template> </div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> </template>
<el-form-item label="操作地址" prop="operIp"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" /> <el-form-item label="操作地址" prop="operIp">
</el-form-item> <el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" />
<el-form-item label="系统模块" prop="title"> </el-form-item>
<el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable @keyup.enter="handleQuery" /> <el-form-item label="系统模块" prop="title">
</el-form-item> <el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable @keyup.enter="handleQuery" />
<el-form-item label="操作人员" prop="operName"> </el-form-item>
<el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable @keyup.enter="handleQuery" /> <el-form-item label="操作人员" prop="operName">
</el-form-item> <el-input
<el-form-item label="类型" prop="businessType"> v-model="queryParams.operName"
<el-select v-model="queryParams.businessType" placeholder="操作类型" clearable> placeholder="请输入操作人员"
<el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" /> clearable
</el-select> @keyup.enter="handleQuery"
</el-form-item> />
<el-form-item label="状态" prop="status"> </el-form-item>
<el-select v-model="queryParams.status" placeholder="操作状态" clearable> <el-form-item label="类型" prop="businessType">
<el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-select v-model="queryParams.businessType" placeholder="操作类型" clearable>
</el-select> <el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-form-item> </el-select>
<el-form-item label="操作时间" style="width: 308px"> </el-form-item>
<el-date-picker <el-form-item label="状态" prop="status">
v-model="dateRange" <el-select v-model="queryParams.status" placeholder="操作状态" clearable>
value-format="YYYY-MM-DD HH:mm:ss" <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
type="daterange" </el-select>
range-separator="-" </el-form-item>
start-placeholder="开始日期" <el-form-item label="操作时间" style="width: 308px">
end-placeholder="结束日期" <el-date-picker
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" v-model="dateRange"
></el-date-picker> value-format="YYYY-MM-DD HH:mm:ss"
</el-form-item> type="daterange"
<el-form-item> range-separator="-"
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> start-placeholder="开始日期"
<el-button icon="Refresh" @click="resetQuery">重置</el-button> end-placeholder="结束日期"
</el-form-item> :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
</el-form> ></el-date-picker>
</el-card> </el-form-item>
</div> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
@@ -58,11 +63,34 @@
<p> {{ total }} 条记录支持类型过滤详情查看批量清空和导出</p> <p> {{ total }} 条记录支持类型过滤详情查看批量清空和导出</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button
v-hasPermi="['monitor:operlog:remove']"
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
>
删除 删除
</el-button> </el-button>
<el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="WarnTriangleFilled" @click="handleClean">清空</el-button> <el-button
<el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button> v-hasPermi="['monitor:operlog:remove']"
type="danger"
plain
icon="WarnTriangleFilled"
@click="handleClean"
>
清空
</el-button>
<el-button
v-hasPermi="['monitor:operlog:export']"
type="warning"
plain
icon="Download"
@click="handleExport"
>
导出
</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
</div> </div>
@@ -102,7 +130,14 @@
<dict-tag :options="sys_common_status" :value="scope.row.status" /> <dict-tag :options="sys_common_status" :value="scope.row.status" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']"> <el-table-column
label="操作日期"
align="center"
prop="operTime"
width="180"
sortable="custom"
:sort-orders="['descending', 'ascending']"
>
<template #default="scope"> <template #default="scope">
<span>{{ proxy.parseTime(scope.row.operTime) }}</span> <span>{{ proxy.parseTime(scope.row.operTime) }}</span>
</template> </template>
@@ -123,13 +158,25 @@
<el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="详细" placement="top"> <el-tooltip content="详细" placement="top">
<el-button v-hasPermi="['monitor:operlog:query']" link type="primary" icon="View" @click="handleView(scope.row)"> </el-button> <el-button
v-hasPermi="['monitor:operlog:query']"
link
type="primary"
icon="View"
@click="handleView(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> <pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card> </el-card>
<!-- 操作日志详细 --> <!-- 操作日志详细 -->
<OperInfoDialog ref="operInfoDialogRef" /> <OperInfoDialog ref="operInfoDialogRef" />
@@ -220,7 +267,7 @@ const resetQuery = () => {
}; };
/** 多选框选中数据 */ /** 多选框选中数据 */
const handleSelectionChange = (selection: OperLogVO[]) => { const handleSelectionChange = (selection: OperLogVO[]) => {
ids.value = selection.map((item) => item.operId); ids.value = selection.map(item => item.operId);
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
/** 排序触发事件 */ /** 排序触发事件 */
+16 -7
View File
@@ -1,5 +1,12 @@
<template> <template>
<el-dialog v-model="open" title="操作日志详细" width="700px" append-to-body close-on-click-modal @closed="info = null"> <el-dialog
v-model="open"
title="操作日志详细"
width="700px"
append-to-body
close-on-click-modal
@closed="info = null"
>
<el-descriptions v-if="info" :column="1" border> <el-descriptions v-if="info" :column="1" border>
<el-descriptions-item label="操作状态"> <el-descriptions-item label="操作状态">
<template #default> <template #default>
@@ -8,13 +15,15 @@
</template> </template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="登录信息"> <el-descriptions-item label="登录信息">
<template #default> {{ info.operName }} / {{ info.deptName }} / {{ info.operIp }} / {{ info.operLocation }} </template> <template #default>
{{ info.operName }} / {{ info.deptName }} / {{ info.operIp }} / {{ info.operLocation }}
</template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="请求信息"> <el-descriptions-item label="请求信息">
<template #default> {{ info.requestMethod }} {{ info.operUrl }} </template> <template #default>{{ info.requestMethod }} {{ info.operUrl }}</template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="操作模块"> <el-descriptions-item label="操作模块">
<template #default> {{ info.title }} / {{ typeFormat(info) }} </template> <template #default>{{ info.title }} / {{ typeFormat(info) }}</template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="操作方法"> <el-descriptions-item label="操作方法">
<template #default> <template #default>
@@ -37,15 +46,15 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="消耗时间"> <el-descriptions-item label="消耗时间">
<template #default> <template #default>
<span> {{ info.costTime }}ms </span> <span>{{ info.costTime }}ms</span>
</template> </template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="操作时间"> <el-descriptions-item label="操作时间">
<template #default> {{ proxy.parseTime(info.operTime) }}</template> <template #default>{{ proxy.parseTime(info.operTime) }}</template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item v-if="info.status === 1" label="异常信息"> <el-descriptions-item v-if="info.status === 1" label="异常信息">
<template #default> <template #default>
<span class="text-danger"> {{ info.errorMsg }}</span> <span class="text-danger">{{ info.errorMsg }}</span>
</template> </template>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
+42 -8
View File
@@ -5,7 +5,8 @@
<span class="brand-pill">Plus UI Workspace</span> <span class="brand-pill">Plus UI Workspace</span>
<h1 class="brand-title">企业级后台管理系统</h1> <h1 class="brand-title">企业级后台管理系统</h1>
<p class="brand-desc"> <p class="brand-desc">
真正面向企业级的应用框架 组件化 模块化 轻耦合 高扩展 针对企业痛点 业界一流技术栈<br /> 真正面向企业级的应用框架 组件化 模块化 轻耦合 高扩展 针对企业痛点 业界一流技术栈
<br />
重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 定期同步 重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 定期同步
</p> </p>
<div class="brand-highlights"> <div class="brand-highlights">
@@ -30,7 +31,13 @@
</div> </div>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('register.username')"> <el-input
v-model="registerForm.username"
type="text"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.username')"
>
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -59,7 +66,13 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="captchaEnabled" prop="code" class="captcha-row"> <el-form-item v-if="captchaEnabled" prop="code" class="captcha-row">
<el-input v-model="registerForm.code" size="large" auto-complete="off" :placeholder="proxy.$t('register.code')" @keyup.enter="handleRegister"> <el-input
v-model="registerForm.code"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.code')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
<div class="register-code"> <div class="register-code">
@@ -73,7 +86,13 @@
</div> </div>
<el-form-item class="submit-row"> <el-form-item class="submit-row">
<el-button :loading="loading" size="large" type="primary" class="submit-button" @click.prevent="handleRegister"> <el-button
:loading="loading"
size="large"
type="primary"
class="submit-button"
@click.prevent="handleRegister"
>
<span v-if="!loading">{{ proxy.$t('register.register') }}</span> <span v-if="!loading">{{ proxy.$t('register.register') }}</span>
<span v-else>{{ proxy.$t('register.registering') }}</span> <span v-else>{{ proxy.$t('register.registering') }}</span>
</el-button> </el-button>
@@ -127,12 +146,26 @@ const equalToPassword = (rule: any, value: string, callback: any) => {
const registerRules: ElFormRules = { const registerRules: ElFormRules = {
username: [ username: [
{ required: true, trigger: 'blur', message: t('register.rule.username.required') }, { required: true, trigger: 'blur', message: t('register.rule.username.required') },
{ min: 2, max: 20, message: t('register.rule.username.length', { min: 2, max: 20 }), trigger: 'blur' } {
min: 2,
max: 20,
message: t('register.rule.username.length', { min: 2, max: 20 }),
trigger: 'blur'
}
], ],
password: [ password: [
{ required: true, trigger: 'blur', message: t('register.rule.password.required') }, { required: true, trigger: 'blur', message: t('register.rule.password.required') },
{ min: 5, max: 20, message: t('register.rule.password.length', { min: 5, max: 20 }), trigger: 'blur' }, {
{ pattern: /^[^<>"'|\\]+$/, message: t('register.rule.password.pattern', { strings: '< > " \' \\ |' }), trigger: 'blur' } min: 5,
max: 20,
message: t('register.rule.password.length', { min: 5, max: 20 }),
trigger: 'blur'
},
{
pattern: /^[^<>"'|\\]+$/,
message: t('register.rule.password.pattern', { strings: '< > " \' \\ |' }),
trigger: 'blur'
}
], ],
confirmPassword: [ confirmPassword: [
{ required: true, trigger: 'blur', message: t('register.rule.confirmPassword.required') }, { required: true, trigger: 'blur', message: t('register.rule.confirmPassword.required') },
@@ -190,7 +223,8 @@ onMounted(() => {
padding: 40px 24px 88px; padding: 40px 24px 88px;
background: background:
radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%), radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%), linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%); radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%),
linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%);
} }
.register-shell { .register-shell {
+107 -38
View File
@@ -1,31 +1,41 @@
<template> <template>
<div class="p-2 app-container system-client-page"> <div class="p-2 app-container system-client-page">
<div class="search-wrap"> <div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header> <template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch"> <div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div> <div><h3>筛选条件</h3></div>
</div> </div>
</template> </template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="85px" class="query-form"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="85px" class="query-form">
<el-form-item label="客户端key" prop="clientKey"> <el-form-item label="客户端key" prop="clientKey">
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" /> <el-input
</el-form-item> v-model="queryParams.clientKey"
<el-form-item label="客户端秘钥" prop="clientSecret"> placeholder="请输入客户端key"
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable @keyup.enter="handleQuery" /> clearable
</el-form-item> @keyup.enter="handleQuery"
<el-form-item label="状态" prop="status"> />
<el-select v-model="queryParams.status" placeholder="状态" clearable> </el-form-item>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-form-item label="客户端秘钥" prop="clientSecret">
</el-select> <el-input
</el-form-item> v-model="queryParams.clientSecret"
<el-form-item> placeholder="请输入客户端秘钥"
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> clearable
<el-button icon="Refresh" @click="resetQuery">重置</el-button> @keyup.enter="handleQuery"
</el-form-item> />
</el-form> </el-form-item>
</el-card> <el-form-item label="状态" prop="status">
</div> <el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
@@ -34,20 +44,44 @@
<h3>客户端列表</h3> <h3>客户端列表</h3>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['system:client:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> <el-button v-hasPermi="['system:client:add']" type="primary" plain icon="Plus" @click="handleAdd">
<el-button v-hasPermi="['system:client:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"> 新增
</el-button>
<el-button
v-hasPermi="['system:client:edit']"
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
>
修改 修改
</el-button> </el-button>
<el-button v-hasPermi="['system:client:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button
v-hasPermi="['system:client:remove']"
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
>
删除 删除
</el-button> </el-button>
<el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button> <el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">
导出
</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
</div> </div>
</template> </template>
<el-table v-loading="loading" :data="clientList" border class="data-table" @selection-change="handleSelectionChange"> <el-table
v-loading="loading"
:data="clientList"
border
class="data-table"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="id" align="center" prop="id" /> <el-table-column v-if="true" label="id" align="center" prop="id" />
<el-table-column label="客户端id" align="center" prop="clientId" /> <el-table-column label="客户端id" align="center" prop="clientId" />
@@ -67,22 +101,45 @@
<el-table-column label="Token固定超时时间" align="center" prop="timeout" /> <el-table-column label="Token固定超时时间" align="center" prop="timeout" />
<el-table-column key="status" label="状态" align="center"> <el-table-column key="status" label="状态" align="center">
<template #default="scope"> <template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> <el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:client:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> <el-button
v-hasPermi="['system:client:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:client:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> <el-button
v-hasPermi="['system:client:remove']"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> <pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card> </el-card>
<!-- 添加或修改客户端管理对话框 --> <!-- 添加或修改客户端管理对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body> <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
@@ -95,12 +152,22 @@
</el-form-item> </el-form-item>
<el-form-item label="授权类型" prop="grantTypeList"> <el-form-item label="授权类型" prop="grantTypeList">
<el-select v-model="form.grantTypeList" multiple placeholder="请输入授权类型"> <el-select v-model="form.grantTypeList" multiple placeholder="请输入授权类型">
<el-option v-for="dict in sys_grant_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> <el-option
v-for="dict in sys_grant_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="设备类型" prop="deviceType"> <el-form-item label="设备类型" prop="deviceType">
<el-select v-model="form.deviceType" placeholder="请输入设备类型"> <el-select v-model="form.deviceType" placeholder="请输入设备类型">
<el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> <el-option
v-for="dict in sys_device_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="activeTimeout" label-width="auto"> <el-form-item prop="activeTimeout" label-width="auto">
@@ -241,7 +308,7 @@ const resetQuery = () => {
/** 多选框选中数据 */ /** 多选框选中数据 */
const handleSelectionChange = (selection: ClientVO[]) => { const handleSelectionChange = (selection: ClientVO[]) => {
ids.value = selection.map((item) => item.id); ids.value = selection.map(item => item.id);
single.value = selection.length != 1; single.value = selection.length != 1;
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
@@ -283,7 +350,9 @@ const submitForm = () => {
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (row?: ClientVO) => { const handleDelete = async (row?: ClientVO) => {
const _ids = row?.id || ids.value; const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除客户端管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false)); await proxy?.$modal
.confirm('是否确认删除客户端管理编号为"' + _ids + '"的数据项?')
.finally(() => (loading.value = false));
await delClient(_ids); await delClient(_ids);
proxy?.$modal.msgSuccess('删除成功'); proxy?.$modal.msgSuccess('删除成功');
await getList(); await getList();
+108 -48
View File
@@ -1,45 +1,55 @@
<template> <template>
<div class="p-2 app-container system-config-page"> <div class="p-2 app-container system-config-page">
<div class="search-wrap"> <div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header> <template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch"> <div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div> <div>
<span class="panel-kicker">Search Filters</span> <span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3> <h3>筛选条件</h3>
</div>
</div> </div>
</template> </div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> </template>
<el-form-item label="参数名称" prop="configName"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" /> <el-form-item label="参数名称" prop="configName">
</el-form-item> <el-input
<el-form-item label="参数键名" prop="configKey"> v-model="queryParams.configName"
<el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable @keyup.enter="handleQuery" /> placeholder="请输入参数名称"
</el-form-item> clearable
<el-form-item label="系统内置" prop="configType"> @keyup.enter="handleQuery"
<el-select v-model="queryParams.configType" placeholder="系统内置" clearable> />
<el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-form-item>
</el-select> <el-form-item label="参数键名" prop="configKey">
</el-form-item> <el-input
<el-form-item label="创建时间" style="width: 308px"> v-model="queryParams.configKey"
<el-date-picker placeholder="请输入参数键名"
v-model="dateRange" clearable
value-format="YYYY-MM-DD HH:mm:ss" @keyup.enter="handleQuery"
type="daterange" />
range-separator="-" </el-form-item>
start-placeholder="开始日期" <el-form-item label="系统内置" prop="configType">
end-placeholder="结束日期" <el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
></el-date-picker> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item label="创建时间" style="width: 308px">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-date-picker
<el-button icon="Refresh" @click="resetQuery">重置</el-button> v-model="dateRange"
</el-form-item> value-format="YYYY-MM-DD HH:mm:ss"
</el-form> type="daterange"
</el-card> range-separator="-"
</div> start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
@@ -50,21 +60,53 @@
<p> {{ total }} 条记录支持键值维护导出和缓存刷新</p> <p> {{ total }} 条记录支持键值维护导出和缓存刷新</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['system:config:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> <el-button v-hasPermi="['system:config:add']" type="primary" plain icon="Plus" @click="handleAdd">
<el-button v-hasPermi="['system:config:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"> 新增
</el-button>
<el-button
v-hasPermi="['system:config:edit']"
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
>
修改 修改
</el-button> </el-button>
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button
v-hasPermi="['system:config:remove']"
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
>
删除 删除
</el-button> </el-button>
<el-button v-hasPermi="['system:config:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button> <el-button v-hasPermi="['system:config:export']" type="warning" plain icon="Download" @click="handleExport">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button> 导出
</el-button>
<el-button
v-hasPermi="['system:config:remove']"
type="danger"
plain
icon="Refresh"
@click="handleRefreshCache"
>
刷新缓存
</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
</div> </div>
</template> </template>
<el-table v-loading="loading" border class="data-table" :data="configList" @selection-change="handleSelectionChange"> <el-table
v-loading="loading"
border
class="data-table"
:data="configList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="参数主键" align="center" prop="configId" /> <el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" /> <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
@@ -84,15 +126,33 @@
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:config:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> <el-button
v-hasPermi="['system:config:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:config:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> <el-button
v-hasPermi="['system:config:remove']"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> <pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card> </el-card>
<!-- 添加或修改参数配置对话框 --> <!-- 添加或修改参数配置对话框 -->
@@ -205,7 +265,7 @@ const resetQuery = () => {
}; };
/** 多选框选中数据 */ /** 多选框选中数据 */
const handleSelectionChange = (selection: ConfigVO[]) => { const handleSelectionChange = (selection: ConfigVO[]) => {
ids.value = selection.map((item) => item.configId); ids.value = selection.map(item => item.configId);
single.value = selection.length != 1; single.value = selection.length != 1;
multiple.value = !selection.length; multiple.value = !selection.length;
}; };
+72 -34
View File
@@ -1,34 +1,45 @@
<template> <template>
<div class="p-2 app-container system-dept-page"> <div class="p-2 app-container system-dept-page">
<div class="search-wrap"> <div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header> <template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch"> <div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div> <div>
<span class="panel-kicker">Search Filters</span> <span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3> <h3>筛选条件</h3>
</div>
</div> </div>
</template> </div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> </template>
<el-form-item label="部门名称" prop="deptName"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" /> <el-form-item label="部门名称" prop="deptName">
</el-form-item> <el-input
<el-form-item label="类别编码" prop="deptCategory"> v-model="queryParams.deptName"
<el-input v-model="queryParams.deptCategory" placeholder="请输入类别编码" clearable style="width: 240px" @keyup.enter="handleQuery" /> placeholder="请输入部门名称"
</el-form-item> clearable
<el-form-item label="状态" prop="status"> @keyup.enter="handleQuery"
<el-select v-model="queryParams.status" placeholder="部门状态" clearable> />
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-form-item>
</el-select> <el-form-item label="类别编码" prop="deptCategory">
</el-form-item> <el-input
<el-form-item> v-model="queryParams.deptCategory"
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> placeholder="请输入类别编码"
<el-button icon="Refresh" @click="resetQuery">重置</el-button> clearable
</el-form-item> style="width: 240px"
</el-form> @keyup.enter="handleQuery"
</el-card> />
</div> </el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
@@ -39,7 +50,9 @@
<p>支持树形层级维护负责人绑定和部门状态管理</p> <p>支持树形层级维护负责人绑定和部门状态管理</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button> <el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">
新增
</el-button>
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button> <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
@@ -72,13 +85,31 @@
<el-table-column fixed="right" align="center" label="操作"> <el-table-column fixed="right" align="center" label="操作">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dept:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" /> <el-button
v-hasPermi="['system:dept:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="新增" placement="top"> <el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:dept:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" /> <el-button
v-hasPermi="['system:dept:add']"
link
type="primary"
icon="Plus"
@click="handleAdd(scope.row)"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dept:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" /> <el-button
v-hasPermi="['system:dept:remove']"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
/>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -118,7 +149,12 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="负责人" prop="leader"> <el-form-item label="负责人" prop="leader">
<el-select v-model="form.leader" placeholder="请选择负责人"> <el-select v-model="form.leader" placeholder="请选择负责人">
<el-option v-for="item in deptUserList" :key="item.userId" :label="item.userName" :value="item.userId" /> <el-option
v-for="item in deptUserList"
:key="item.userId"
:label="item.userName"
:value="item.userId"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -135,7 +171,9 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="部门状态"> <el-form-item label="部门状态">
<el-radio-group v-model="form.status"> <el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio> <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -261,7 +299,7 @@ const handleToggleExpandAll = () => {
}; };
/** 展开/折叠所有 */ /** 展开/折叠所有 */
const toggleExpandAll = (data: DeptVO[], status: boolean) => { const toggleExpandAll = (data: DeptVO[], status: boolean) => {
data.forEach((item) => { data.forEach(item => {
deptTableRef.value?.toggleRowExpansion(item, status); deptTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status); if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
}); });
+148 -33
View File
@@ -7,7 +7,10 @@
<template #header> <template #header>
<div class="toolbar-shell dict-card__header"> <div class="toolbar-shell dict-card__header">
<div class="table-heading"> <div class="table-heading">
<div class="panel-heading search-panel-toggle dict-title-toggle" @click.stop="showTypeSearch = !showTypeSearch"> <div
class="panel-heading search-panel-toggle dict-title-toggle"
@click.stop="showTypeSearch = !showTypeSearch"
>
<div> <div>
<h3>字典管理</h3> <h3>字典管理</h3>
</div> </div>
@@ -22,10 +25,20 @@
<div class="dict-search" :class="{ 'is-collapsed': !showTypeSearch }"> <div class="dict-search" :class="{ 'is-collapsed': !showTypeSearch }">
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true" class="query-form"> <el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true" class="query-form">
<el-form-item label="字典名称" prop="dictName"> <el-form-item label="字典名称" prop="dictName">
<el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" /> <el-input
v-model="typeQueryParams.dictName"
placeholder="请输入字典名称"
clearable
@keyup.enter="handleTypeQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="字典类型" prop="dictType"> <el-form-item label="字典类型" prop="dictType">
<el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" /> <el-input
v-model="typeQueryParams.dictType"
placeholder="请输入字典类型"
clearable
@keyup.enter="handleTypeQuery"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button>
@@ -35,15 +48,47 @@
</div> </div>
<div class="toolbar-actions dict-actions"> <div class="toolbar-actions dict-actions">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleTypeAdd">新增</el-button> <el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleTypeAdd">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="typeSingle" @click="handleTypeUpdate()" 新增
>修改</el-button </el-button>
<el-button
v-hasPermi="['system:dict:edit']"
type="success"
plain
icon="Edit"
:disabled="typeSingle"
@click="handleTypeUpdate()"
> >
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="typeMultiple" @click="handleTypeDelete()" 修改
>删除</el-button </el-button>
<el-button
v-hasPermi="['system:dict:remove']"
type="danger"
plain
icon="Delete"
:disabled="typeMultiple"
@click="handleTypeDelete()"
> >
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleTypeExport">导出</el-button> 删除
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button> </el-button>
<el-button
v-hasPermi="['system:dict:export']"
type="warning"
plain
icon="Download"
@click="handleTypeExport"
>
导出
</el-button>
<el-button
v-hasPermi="['system:dict:remove']"
type="danger"
plain
icon="Refresh"
@click="handleRefreshCache"
>
刷新缓存
</el-button>
</div> </div>
<div class="dict-table-wrap"> <div class="dict-table-wrap">
@@ -71,13 +116,31 @@
<span>{{ proxy.parseTime(scope.row.createTime) }}</span> <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width"> <el-table-column
label="操作"
fixed="right"
align="center"
width="120"
class-name="small-padding fixed-width"
>
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleTypeUpdate(scope.row)"></el-button> <el-button
v-hasPermi="['system:dict:edit']"
link
type="primary"
icon="Edit"
@click="handleTypeUpdate(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleTypeDelete(scope.row)"></el-button> <el-button
v-hasPermi="['system:dict:remove']"
link
type="primary"
icon="Delete"
@click="handleTypeDelete(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -100,7 +163,10 @@
<template #header> <template #header>
<div class="toolbar-shell dict-card__header"> <div class="toolbar-shell dict-card__header">
<div class="table-heading"> <div class="table-heading">
<div class="panel-heading search-panel-toggle dict-title-toggle" @click.stop="showDataSearch = !showDataSearch"> <div
class="panel-heading search-panel-toggle dict-title-toggle"
@click.stop="showDataSearch = !showDataSearch"
>
<div> <div>
<h3>字典数据</h3> <h3>字典数据</h3>
<p v-if="hasCurrentDict" class="dict-card__subtitle">{{ currentDictLabel }}</p> <p v-if="hasCurrentDict" class="dict-card__subtitle">{{ currentDictLabel }}</p>
@@ -125,16 +191,25 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" :disabled="!hasCurrentDict" @click="handleDataQuery">搜索</el-button> <el-button type="primary" icon="Search" :disabled="!hasCurrentDict" @click="handleDataQuery">
搜索
</el-button>
<el-button icon="Refresh" :disabled="!hasCurrentDict" @click="handleDataResetQuery">重置</el-button> <el-button icon="Refresh" :disabled="!hasCurrentDict" @click="handleDataResetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<div class="toolbar-actions dict-actions"> <div class="toolbar-actions dict-actions">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" :disabled="!hasCurrentDict" @click="handleDataAdd" <el-button
>新增</el-button v-hasPermi="['system:dict:add']"
type="primary"
plain
icon="Plus"
:disabled="!hasCurrentDict"
@click="handleDataAdd"
> >
新增
</el-button>
<el-button <el-button
v-hasPermi="['system:dict:edit']" v-hasPermi="['system:dict:edit']"
type="success" type="success"
@@ -142,8 +217,9 @@
icon="Edit" icon="Edit"
:disabled="dataSingle || !hasCurrentDict" :disabled="dataSingle || !hasCurrentDict"
@click="handleDataUpdate()" @click="handleDataUpdate()"
>修改</el-button
> >
修改
</el-button>
<el-button <el-button
v-hasPermi="['system:dict:remove']" v-hasPermi="['system:dict:remove']"
type="danger" type="danger"
@@ -151,31 +227,52 @@
icon="Delete" icon="Delete"
:disabled="dataMultiple || !hasCurrentDict" :disabled="dataMultiple || !hasCurrentDict"
@click="handleDataDelete()" @click="handleDataDelete()"
>删除</el-button
> >
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" :disabled="!hasCurrentDict" @click="handleDataExport" 删除
>导出</el-button </el-button>
<el-button
v-hasPermi="['system:dict:export']"
type="warning"
plain
icon="Download"
:disabled="!hasCurrentDict"
@click="handleDataExport"
> >
导出
</el-button>
</div> </div>
<div class="dict-table-wrap"> <div class="dict-table-wrap">
<el-table v-loading="dataLoading" border class="data-table" :data="dataList" @selection-change="handleDataSelectionChange"> <el-table
v-loading="dataLoading"
border
class="data-table"
:data="dataList"
@selection-change="handleDataSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" /> <el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel" width="100"> <el-table-column label="字典标签" align="center" prop="dictLabel" width="100">
<template #default="scope"> <template #default="scope">
<span <span
v-if=" v-if="
(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null) (scope.row.listClass === '' || scope.row.listClass === 'default') &&
(scope.row.cssClass === '' || scope.row.cssClass == null)
" "
>{{ scope.row.dictLabel }}</span
> >
{{ scope.row.dictLabel }}
</span>
<el-tag <el-tag
v-else v-else
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass" :type="
scope.row.listClass === 'primary' || scope.row.listClass === 'default'
? 'primary'
: scope.row.listClass
"
:class="scope.row.cssClass" :class="scope.row.cssClass"
>{{ scope.row.dictLabel }}</el-tag
> >
{{ scope.row.dictLabel }}
</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" width="100" /> <el-table-column label="字典键值" align="center" prop="dictValue" width="100" />
@@ -186,13 +283,31 @@
<span>{{ proxy.parseTime(scope.row.createTime) }}</span> <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width"> <el-table-column
label="操作"
fixed="right"
align="center"
width="120"
class-name="small-padding fixed-width"
>
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleDataUpdate(scope.row)"></el-button> <el-button
v-hasPermi="['system:dict:edit']"
link
type="primary"
icon="Edit"
@click="handleDataUpdate(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDataDelete(scope.row)"></el-button> <el-button
v-hasPermi="['system:dict:remove']"
link
type="primary"
icon="Delete"
@click="handleDataDelete(scope.row)"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -392,7 +507,7 @@ const { queryParams: dataQueryParams, form: dataForm, rules: dataRules } = toRef
const getTypeList = () => { const getTypeList = () => {
typeLoading.value = true; typeLoading.value = true;
listType(typeQueryParams.value).then((res) => { listType(typeQueryParams.value).then(res => {
typeList.value = res.data?.rows; typeList.value = res.data?.rows;
typeTotal.value = res.data?.total; typeTotal.value = res.data?.total;
typeLoading.value = false; typeLoading.value = false;
@@ -409,7 +524,7 @@ const ensureCurrentType = () => {
return; return;
} }
const current = currentDict.value && typeList.value.find((item) => item.dictId === currentDict.value?.dictId); const current = currentDict.value && typeList.value.find(item => item.dictId === currentDict.value?.dictId);
const nextRow = current || typeList.value[0]; const nextRow = current || typeList.value[0];
setCurrentType(nextRow); setCurrentType(nextRow);
}; };
@@ -454,7 +569,7 @@ const handleTypeAdd = () => {
}; };
const handleTypeSelectionChange = (selection: DictTypeVO[]) => { const handleTypeSelectionChange = (selection: DictTypeVO[]) => {
typeIds.value = selection.map((item) => item.dictId); typeIds.value = selection.map(item => item.dictId);
typeSingle.value = selection.length != 1; typeSingle.value = selection.length != 1;
typeMultiple.value = !selection.length; typeMultiple.value = !selection.length;
}; };
@@ -551,7 +666,7 @@ const handleDataAdd = () => {
}; };
const handleDataSelectionChange = (selection: DictDataVO[]) => { const handleDataSelectionChange = (selection: DictDataVO[]) => {
dataIds.value = selection.map((item) => item.dictCode); dataIds.value = selection.map(item => item.dictCode);
dataSingle.value = selection.length != 1; dataSingle.value = selection.length != 1;
dataMultiple.value = !selection.length; dataMultiple.value = !selection.length;
}; };
+82 -36
View File
@@ -1,31 +1,36 @@
<template> <template>
<div class="p-2 app-container system-menu-page"> <div class="p-2 app-container system-menu-page">
<div class="search-wrap"> <div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header> <template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch"> <div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div> <div>
<span class="panel-kicker">Search Filters</span> <span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3> <h3>筛选条件</h3>
</div>
</div> </div>
</template> </div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form"> </template>
<el-form-item label="菜单名称" prop="menuName"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" /> <el-form-item label="菜单名称" prop="menuName">
</el-form-item> <el-input
<el-form-item label="状态" prop="status"> v-model="queryParams.menuName"
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable> placeholder="请输入菜单名称"
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> clearable
</el-select> @keyup.enter="handleQuery"
</el-form-item> />
<el-form-item> </el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-form-item label="状态" prop="status">
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
</el-form-item> <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-form> </el-select>
</el-card> </el-form-item>
</div> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
@@ -36,8 +41,19 @@
<p>支持树形加载图标选择级联删除和目录/菜单/按钮三级维护</p> <p>支持树形加载图标选择级联删除和目录/菜单/按钮三级维护</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button> <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">
<el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button> 新增
</el-button>
<el-button
v-hasPermi="['system:menu:remove']"
type="danger"
plain
icon="Delete"
@click="handleCascadeDelete"
:loading="deleteLoading"
>
级联删除
</el-button>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div> </div>
</div> </div>
@@ -82,13 +98,31 @@
<el-table-column fixed="right" label="操作" width="180"> <el-table-column fixed="right" label="操作" width="180">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" /> <el-button
v-hasPermi="['system:menu:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="新增" placement="top"> <el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" /> <el-button
v-hasPermi="['system:menu:add']"
link
type="primary"
icon="Plus"
@click="handleAdd(scope.row)"
/>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" /> <el-button
v-hasPermi="['system:menu:remove']"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
/>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -142,8 +176,9 @@
<el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top"> <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
<el-icon> <el-icon>
<question-filled /> <question-filled />
</el-icon> </el-tooltip </el-icon>
>是否外链 </el-tooltip>
是否外链
</span> </span>
</template> </template>
<el-radio-group v-model="form.isFrame"> <el-radio-group v-model="form.isFrame">
@@ -156,7 +191,10 @@
<el-form-item prop="path"> <el-form-item prop="path">
<template #label> <template #label>
<span> <span>
<el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top"> <el-tooltip
content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头"
placement="top"
>
<el-icon> <el-icon>
<question-filled /> <question-filled />
</el-icon> </el-icon>
@@ -187,7 +225,10 @@
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" /> <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
<template #label> <template #label>
<span> <span>
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top"> <el-tooltip
content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')"
placement="top"
>
<el-icon> <el-icon>
<question-filled /> <question-filled />
</el-icon> </el-icon>
@@ -216,7 +257,10 @@
<el-form-item> <el-form-item>
<template #label> <template #label>
<span> <span>
<el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top"> <el-tooltip
content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致"
placement="top"
>
<el-icon> <el-icon>
<question-filled /> <question-filled />
</el-icon> </el-icon>
@@ -243,7 +287,9 @@
</span> </span>
</template> </template>
<el-radio-group v-model="form.visible"> <el-radio-group v-model="form.visible">
<el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio> <el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -449,7 +495,7 @@ const getList = async () => {
} }
menuChildrenListMap.value = tempMap; menuChildrenListMap.value = tempMap;
// IDID // IDID
menuList.value = res.data.filter((menu) => !menuIdSet.has(menu.parentId)); menuList.value = res.data.filter(menu => !menuIdSet.has(menu.parentId));
// //
refreshAllExpandMenuData(); refreshAllExpandMenuData();
loading.value = false; loading.value = false;

Some files were not shown because too many files have changed in this diff Show More