chore: 🔨 合并冲突解决

This commit is contained in:
ray
2024-11-16 22:44:29 +08:00
26 changed files with 1065 additions and 1349 deletions

View File

@@ -9,3 +9,4 @@ public
src/assets src/assets
stats.html stats.html
pnpm-lock.yaml

View File

@@ -1,28 +0,0 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "ignore",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"endOfLine": "auto",
"overrides": [
{
"files": "*.html",
"options": {
"parser": "html"
}
}
]
}

41
.prettierrc.yaml Normal file
View File

@@ -0,0 +1,41 @@
# 在单参数箭头函数中始终添加括号
arrowParens: "always"
# JSX 多行元素的闭合标签另起一行
bracketSameLine: false
# 对象字面量中的括号之间添加空格
bracketSpacing: true
# 自动格式化嵌入的代码(如 Markdown 和 HTML 内的代码)
embeddedLanguageFormatting: "auto"
# 忽略 HTML 空白敏感度,将空白视为非重要内容
htmlWhitespaceSensitivity: "ignore"
# 不插入 @prettier 的 pragma 注释
insertPragma: false
# 在 JSX 中使用双引号
jsxSingleQuote: false
# 每行代码的最大长度限制为 100 字符
printWidth: 100
# 在 Markdown 中保留原有的换行格式
proseWrap: "preserve"
# 仅在必要时添加对象属性的引号
quoteProps: "as-needed"
# 不要求文件开头插入 @prettier 的 pragma 注释
requirePragma: false
# 在语句末尾添加分号
semi: true
# 使用双引号而不是单引号
singleQuote: false
# 缩进使用 2 个空格
tabWidth: 2
# 在多行元素的末尾添加逗号ES5 支持的对象、数组等)
trailingComma: "es5"
# 使用空格而不是制表符缩进
useTabs: false
# Vue 文件中的 <script> 和 <style> 不增加额外的缩进
vueIndentScriptAndStyle: false
# 根据系统自动检测换行符
endOfLine: "auto"
# 对 HTML 文件应用特定格式化规则
overrides:
- files: "*.html"
options:
parser: "html"

View File

@@ -1,6 +1,6 @@
{ {
"name": "vue3-element-admin", "name": "vue3-element-admin",
"version": "2.18.5", "version": "2.19.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -54,65 +54,65 @@
"codemirror": "^5.65.18", "codemirror": "^5.65.18",
"codemirror-editor-vue3": "^2.8.0", "codemirror-editor-vue3": "^2.8.0",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"element-plus": "^2.8.5", "element-plus": "^2.8.7",
"exceljs": "^4.4.0", "exceljs": "^4.4.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"path-to-regexp": "^6.3.0", "path-to-regexp": "^6.3.0",
"pinia": "^2.2.4", "pinia": "^2.2.6",
"qs": "^6.13.0", "qs": "^6.13.0",
"sortablejs": "^1.15.3", "sortablejs": "^1.15.3",
"vue": "^3.5.11", "vue": "^3.5.12",
"vue-i18n": "9.9.1", "vue-i18n": "9.9.1",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.5.0", "@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0", "@commitlint/config-conventional": "^19.5.0",
"@eslint/js": "^9.12.0", "@eslint/js": "^9.14.0",
"@types/codemirror": "^5.60.15", "@types/codemirror": "^5.60.15",
"@types/lodash": "^4.17.10", "@types/lodash": "^4.17.13",
"@types/node": "^22.7.5", "@types/node": "^22.9.0",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3", "@types/path-browserify": "^1.0.3",
"@types/qs": "^6.9.16", "@types/qs": "^6.9.17",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.8.1", "@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.8.1", "@typescript-eslint/parser": "^8.14.0",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "^5.1.5",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"cz-git": "1.9.4", "cz-git": "1.9.4",
"eslint": "^9.12.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.29.0", "eslint-plugin-vue": "^9.31.0",
"globals": "^15.11.0", "globals": "^15.12.0",
"husky": "^9.1.6", "husky": "^9.1.6",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"postcss": "^8.4.47", "postcss": "^8.4.49",
"postcss-html": "^1.7.0", "postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"sass": "^1.79.5", "sass": "^1.80.6",
"stylelint": "^16.9.0", "stylelint": "^16.10.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^5.1.1", "stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended-scss": "^14.1.0", "stylelint-config-recommended-scss": "^14.1.0",
"stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^36.0.1", "stylelint-config-standard": "^36.0.1",
"terser": "^5.34.1", "terser": "^5.36.0",
"typescript": "5.5.4", "typescript": "5.5.4",
"typescript-eslint": "^8.8.1", "typescript-eslint": "^8.14.0",
"unocss": "^0.63.4", "unocss": "^0.63.6",
"unplugin-auto-import": "^0.18.3", "unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4", "unplugin-vue-components": "^0.27.4",
"vite": "^5.4.8", "vite": "^5.4.11",
"vite-plugin-mock-dev-server": "^1.8.0", "vite-plugin-mock-dev-server": "^1.8.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-eslint-parser": "^9.4.3", "vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.6" "vue-tsc": "^2.1.10"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"

1232
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ import request from "@/utils/request";
const AUTH_BASE_URL = "/api/v1/auth"; const AUTH_BASE_URL = "/api/v1/auth";
const AuthAPI = { const AuthAPI = {
/** 登录 接口*/ /** 登录接口*/
login(data: LoginData) { login(data: LoginData) {
const formData = new FormData(); const formData = new FormData();
formData.append("username", data.username); formData.append("username", data.username);
@@ -20,7 +20,19 @@ const AuthAPI = {
}); });
}, },
/** 注销 接口*/ /** 刷新 token 接口*/
refreshToken(refreshToken: string) {
return request<any, LoginResult>({
url: `${AUTH_BASE_URL}/refresh-token`,
method: "post",
data: { refreshToken: refreshToken },
headers: {
Authorization: "no-auth",
},
});
},
/** 注销接口*/
logout() { logout() {
return request({ return request({
url: `${AUTH_BASE_URL}/logout`, url: `${AUTH_BASE_URL}/logout`,
@@ -28,7 +40,7 @@ const AuthAPI = {
}); });
}, },
/** 获取验证码 接口*/ /** 获取验证码接口*/
getCaptcha() { getCaptcha() {
return request<any, CaptchaResult>({ return request<any, CaptchaResult>({
url: `${AUTH_BASE_URL}/captcha`, url: `${AUTH_BASE_URL}/captcha`,
@@ -53,14 +65,14 @@ export interface LoginData {
/** 登录响应 */ /** 登录响应 */
export interface LoginResult { export interface LoginResult {
/** 访问token */ /** 访问令牌 */
accessToken?: string; accessToken: string;
/** 过期时间(单位:毫秒) */ /** 刷新令牌 */
expires?: number; refreshToken: string;
/** 刷新token */ /** 令牌类型 */
refreshToken?: string; tokenType: string;
/** token 类型 */ /** 过期时间(秒) */
tokenType?: string; expiresIn: number;
} }
/** 验证码响应 */ /** 验证码响应 */

View File

@@ -61,8 +61,8 @@ const ConfigAPI = {
refreshCache() { refreshCache() {
return request({ return request({
url: `${CONFIG_BASE_URL}`, url: `${CONFIG_BASE_URL}/refresh`,
method: "patch", method: "PUT",
}); });
}, },
}; };

View File

@@ -113,7 +113,7 @@ const UserAPI = {
url: `${USER_BASE_URL}/export`, url: `${USER_BASE_URL}/export`,
method: "get", method: "get",
params: queryParams, params: queryParams,
responseType: "arraybuffer", responseType: "blob",
}); });
}, },

View File

@@ -12,7 +12,12 @@ export const enum ResultEnum {
ERROR = "B0001", ERROR = "B0001",
/** /**
* 令牌无效或过期 * 访问令牌无效或过期
*/ */
TOKEN_INVALID = "A0230", ACCESS_TOKEN_INVALID = "A0230",
/**
* 刷新令牌无效或过期
*/
REFRESH_TOKEN_INVALID = "A0231",
} }

View File

@@ -7,7 +7,7 @@
(hasOneShowingChild(item.children, item) && (hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.meta?.alwaysShow) || !item.meta?.alwaysShow) ||
// 父节点即使配置了始终显示,但无子节点,也显示叶子节点 // 父节点即使配置了始终显示,但无子节点,也显示叶子节点
(item.meta?.alwaysShow && !item.children) (item.meta?.alwaysShow && !item.children)
" "
> >
@@ -33,11 +33,7 @@
<!--【非叶子节点】显示含多个子节点的父菜单,或始终显示的单子节点 --> <!--【非叶子节点】显示含多个子节点的父菜单,或始终显示的单子节点 -->
<el-sub-menu v-else :index="resolvePath(item.path)" teleported> <el-sub-menu v-else :index="resolvePath(item.path)" teleported>
<template #title> <template #title>
<SidebarMenuItemTitle <SidebarMenuItemTitle v-if="item.meta" :icon="item.meta.icon" :title="item.meta.title" />
v-if="item.meta"
:icon="item.meta.icon"
:title="item.meta.title"
/>
</template> </template>
<SidebarMenuItem <SidebarMenuItem
@@ -98,10 +94,7 @@ const onlyOneChild = ref();
* @param parent 父级路由 * @param parent 父级路由
* @returns 是否仅有一个可见子节点 * @returns 是否仅有一个可见子节点
*/ */
function hasOneShowingChild( function hasOneShowingChild(children: RouteRecordRaw[] = [], parent: RouteRecordRaw) {
children: RouteRecordRaw[] = [],
parent: RouteRecordRaw
) {
// 过滤出可见子节点 // 过滤出可见子节点
const showingChildren = children.filter((route: RouteRecordRaw) => { const showingChildren = children.filter((route: RouteRecordRaw) => {
if (!route.meta?.hidden) { if (!route.meta?.hidden) {

View File

@@ -7,6 +7,8 @@ import "virtual:svg-icons-register";
// 样式 // 样式
import "element-plus/theme-chalk/dark/css-vars.css"; import "element-plus/theme-chalk/dark/css-vars.css";
// 暗黑模式自定义变量
import "@/styles/dark/css-vars.css";
import "@/styles/index.scss"; import "@/styles/index.scss";
import "uno.css"; import "uno.css";
import "animate.css"; import "animate.css";

View File

@@ -1,8 +1,4 @@
import type { import type { NavigationGuardNext, RouteLocationNormalized, RouteRecordRaw } from "vue-router";
NavigationGuardNext,
RouteLocationNormalized,
RouteRecordRaw,
} from "vue-router";
import NProgress from "@/utils/nprogress"; import NProgress from "@/utils/nprogress";
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
import router from "@/router"; import router from "@/router";
@@ -22,15 +18,14 @@ export function setupPermission() {
next({ path: "/" }); next({ path: "/" });
} else { } else {
const permissionStore = usePermissionStore(); const permissionStore = usePermissionStore();
// 判断路由是否加载 // 判断路由是否加载完成
if (permissionStore.isRoutesLoaded) { if (permissionStore.isRoutesLoaded) {
if (to.matched.length === 0) { if (to.matched.length === 0) {
// 路由未匹配跳转到404 // 路由未匹配跳转到404
next("/404"); next("/404");
} else { } else {
// 动态设置页面标题 // 动态设置页面标题
const title = const title = (to.params.title as string) || (to.query.title as string);
(to.params.title as string) || (to.query.title as string);
if (title) { if (title) {
to.meta.title = title; to.meta.title = title;
} }
@@ -40,14 +35,12 @@ export function setupPermission() {
try { try {
// 生成动态路由 // 生成动态路由
const dynamicRoutes = await permissionStore.generateRoutes(); const dynamicRoutes = await permissionStore.generateRoutes();
dynamicRoutes.forEach((route: RouteRecordRaw) => dynamicRoutes.forEach((route: RouteRecordRaw) => router.addRoute(route));
router.addRoute(route) next({ ...to, replace: true });
);
next({ ...to, replace: true }); // 添加动态路由后重新导航
} catch (error) { } catch (error) {
console.error(error); console.error(error);
// 路由加载失败,重置 token 并重定向到登录页 // 路由加载失败,重置 token 并重定向到登录页
await useUserStore().clearUserSession(); await useUserStore().clearUserData();
redirectToLogin(to, next); redirectToLogin(to, next);
NProgress.done(); NProgress.done();
} }
@@ -60,7 +53,7 @@ export function setupPermission() {
} else { } else {
// 不在白名单,重定向到登录页 // 不在白名单,重定向到登录页
redirectToLogin(to, next); redirectToLogin(to, next);
NProgress.done(); // 关闭进度条 NProgress.done();
} }
} }
}); });
@@ -72,10 +65,7 @@ export function setupPermission() {
} }
/** 重定向到登录页 */ /** 重定向到登录页 */
function redirectToLogin( function redirectToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {
to: RouteLocationNormalized,
next: NavigationGuardNext
) {
const params = new URLSearchParams(to.query as Record<string, string>); const params = new URLSearchParams(to.query as Record<string, string>);
const queryString = params.toString(); const queryString = params.toString();
const redirect = queryString ? `${to.path}?${queryString}` : to.path; const redirect = queryString ? `${to.path}?${queryString}` : to.path;
@@ -83,10 +73,7 @@ function redirectToLogin(
} }
/** 判断是否有权限 */ /** 判断是否有权限 */
export function hasAuth( export function hasAuth(value: string | string[], type: "button" | "role" = "button") {
value: string | string[],
type: "button" | "role" = "button"
) {
const { roles, perms } = useUserStore().userInfo; const { roles, perms } = useUserStore().userInfo;
// 超级管理员 拥有所有权限 // 超级管理员 拥有所有权限

View File

@@ -25,7 +25,9 @@ const defaultSettings: AppSettings = {
layout: LayoutEnum.LEFT, layout: LayoutEnum.LEFT,
// 主题,根据操作系统的色彩方案自动选择 // 主题,根据操作系统的色彩方案自动选择
theme: mediaQueryList.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT, theme: mediaQueryList.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT,
// 组件大小 default | medium | small | large
size: SizeEnum.DEFAULT, size: SizeEnum.DEFAULT,
// 语言
language: LanguageEnum.ZH_CN, language: LanguageEnum.ZH_CN,
// 主题颜色 // 主题颜色
themeColor: "#4080FF", themeColor: "#4080FF",

View File

@@ -5,7 +5,7 @@ import { useDictStoreHook } from "@/store/modules/dict";
import AuthAPI, { type LoginData } from "@/api/auth"; import AuthAPI, { type LoginData } from "@/api/auth";
import UserAPI, { type UserInfo } from "@/api/system/user"; import UserAPI, { type UserInfo } from "@/api/system/user";
import { setToken, clearToken } from "@/utils/auth"; import { setToken, setRefreshToken, getRefreshToken, clearToken } from "@/utils/auth";
export const useUserStore = defineStore("user", () => { export const useUserStore = defineStore("user", () => {
const userInfo = useStorage<UserInfo>("userInfo", {} as UserInfo); const userInfo = useStorage<UserInfo>("userInfo", {} as UserInfo);
@@ -20,8 +20,9 @@ export const useUserStore = defineStore("user", () => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
AuthAPI.login(loginData) AuthAPI.login(loginData)
.then((data) => { .then((data) => {
const { tokenType, accessToken } = data; const { tokenType, accessToken, refreshToken } = data;
setToken(tokenType + " " + accessToken); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx setToken(tokenType + " " + accessToken); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
setRefreshToken(refreshToken);
resolve(); resolve();
}) })
.catch((error) => { .catch((error) => {
@@ -59,7 +60,7 @@ export const useUserStore = defineStore("user", () => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
AuthAPI.logout() AuthAPI.logout()
.then(() => { .then(() => {
clearUserSession(); clearUserData();
resolve(); resolve();
}) })
.catch((error) => { .catch((error) => {
@@ -69,11 +70,31 @@ export const useUserStore = defineStore("user", () => {
} }
/** /**
* 清理用户会话 * 刷新 token
*/
function refreshToken() {
const refreshToken = getRefreshToken();
return new Promise<void>((resolve, reject) => {
AuthAPI.refreshToken(refreshToken)
.then((data) => {
const { tokenType, accessToken, refreshToken } = data;
setToken(tokenType + " " + accessToken);
setRefreshToken(refreshToken);
resolve();
})
.catch((error) => {
console.log(" refreshToken 刷新失败", error);
reject(error);
});
});
}
/**
* 清理用户数据
* *
* @returns * @returns
*/ */
function clearUserSession() { function clearUserData() {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
clearToken(); clearToken();
usePermissionStoreHook().resetRouter(); usePermissionStoreHook().resetRouter();
@@ -87,7 +108,8 @@ export const useUserStore = defineStore("user", () => {
getUserInfo, getUserInfo,
login, login,
logout, logout,
clearUserSession, clearUserData,
refreshToken,
}; };
}); });

View File

@@ -0,0 +1,7 @@
/* 暗黑模式通过 CSS 自定义变量官方链接https://element-plus.org/zh-CN/guide/dark-mode.html#%E9%80%9A%E8%BF%87-css */
html.dark {
.el-table {
/* 自定义表格选中高亮时当前行的背景颜色 */
--el-table-current-row-bg-color: var(--el-fill-color-light);
}
}

View File

@@ -1,15 +1,27 @@
const TOKEN_KEY = "admin-token"; // 访问 token 缓存的 key
const ACCESS_TOKEN_KEY = "access_token";
// 刷新 token 缓存的 key
const REFRESH_TOKEN_KEY = "refresh_token";
function getToken(): string { function getToken(): string {
return localStorage.getItem(TOKEN_KEY) || ""; return localStorage.getItem(ACCESS_TOKEN_KEY) || "";
} }
function setToken(token: string) { function setToken(token: string) {
return localStorage.setItem(TOKEN_KEY, token); localStorage.setItem(ACCESS_TOKEN_KEY, token);
}
function getRefreshToken(): string {
return localStorage.getItem(REFRESH_TOKEN_KEY) || "";
}
function setRefreshToken(token: string) {
localStorage.setItem(REFRESH_TOKEN_KEY, token);
} }
function clearToken() { function clearToken() {
return localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(ACCESS_TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
} }
export { getToken, setToken, clearToken }; export { getToken, setToken, clearToken, getRefreshToken, setRefreshToken };

View File

@@ -1,44 +1,38 @@
import axios, { import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from "axios";
type InternalAxiosRequestConfig,
type AxiosResponse,
} from "axios";
import qs from "qs"; import qs from "qs";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { ResultEnum } from "@/enums/ResultEnum"; import { ResultEnum } from "@/enums/ResultEnum";
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
import router from "@/router";
// 创建 axios 实例 // 创建 axios 实例
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000, timeout: 50000,
headers: { "Content-Type": "application/json;charset=utf-8" }, headers: { "Content-Type": "application/json;charset=utf-8" },
paramsSerializer: (params) => { paramsSerializer: (params) => qs.stringify(params),
return qs.stringify(params);
},
}); });
// 请求拦截器 // 请求拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
const accessToken = getToken(); const accessToken = getToken();
if (accessToken) { // 如果 Authorization 设置为 no-auth则不携带 Token用于登录、刷新 Token 等接口
if (config.headers.Authorization !== "no-auth" && accessToken) {
config.headers.Authorization = accessToken; config.headers.Authorization = accessToken;
} else {
delete config.headers.Authorization;
} }
return config; return config;
}, },
(error: any) => { (error) => Promise.reject(error)
return Promise.reject(error);
}
); );
// 响应拦截器 // 响应拦截器
service.interceptors.response.use( service.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象 // 如果响应是二进制流则直接返回用于下载文件、Excel 导出等
if ( if (response.config.responseType === "blob") {
response.config.responseType === "blob" ||
response.config.responseType === "arraybuffer"
) {
return response; return response;
} }
@@ -50,21 +44,15 @@ service.interceptors.response.use(
ElMessage.error(msg || "系统出错"); ElMessage.error(msg || "系统出错");
return Promise.reject(new Error(msg || "Error")); return Promise.reject(new Error(msg || "Error"));
}, },
(error: any) => { async (error: any) => {
// 异常处理 非 2xx 状态码 会进入这里 const { config, response } = error;
if (error.response.data) { if (response) {
const { code, msg } = error.response.data; const { code, msg } = response.data;
if (code === ResultEnum.TOKEN_INVALID) { if (code === ResultEnum.ACCESS_TOKEN_INVALID) {
ElNotification({ // Token 过期,刷新 Token
title: "提示", return handleTokenRefresh(config);
message: "您的会话已过期,请重新登录", } else if (code === ResultEnum.REFRESH_TOKEN_INVALID) {
type: "info", return Promise.reject(new Error(msg || "Error"));
});
useUserStoreHook()
.clearUserSession()
.then(() => {
location.reload();
});
} else { } else {
ElMessage.error(msg || "系统出错"); ElMessage.error(msg || "系统出错");
} }
@@ -74,3 +62,50 @@ service.interceptors.response.use(
); );
export default service; export default service;
// 刷新 Token 的锁
let isRefreshing = false;
// 因 Token 过期导致失败的请求队列
let requestsQueue: Array<() => void> = [];
// 刷新 Token 处理
async function handleTokenRefresh(config: InternalAxiosRequestConfig) {
return new Promise((resolve) => {
const requestCallback = () => {
config.headers.Authorization = getToken();
resolve(service(config));
};
requestsQueue.push(requestCallback);
if (!isRefreshing) {
isRefreshing = true;
// 刷新 Token
useUserStoreHook()
.refreshToken()
.then(() => {
// Token 刷新成功,执行请求队列
requestsQueue.forEach((callback) => callback());
requestsQueue = [];
})
.catch((error) => {
console.log("handleTokenRefresh error", error);
// Token 刷新失败,清除用户数据并跳转到登录
ElNotification({
title: "提示",
message: "您的会话已过期,请重新登录",
type: "info",
});
useUserStoreHook()
.clearUserData()
.then(() => {
router.push("/login");
});
})
.finally(() => {
isRefreshing = false;
});
}
});
}

View File

@@ -12,43 +12,31 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery"> <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<template #icon> <el-button icon="refresh" @click="handleResetQuery">重置</el-button>
<Search />
</template>
搜索
</el-button>
<el-button @click="handleResetQuery">
<template #icon>
<Refresh />
</template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-card shadow="never" class="table-wrapper"> <el-card shadow="never">
<template #header> <div class="mb-10px">
<el-button <el-button
v-hasPerm="['sys:config:add']" v-hasPerm="['sys:config:add']"
type="success" type="success"
icon="plus"
@click="handleOpenDialog()" @click="handleOpenDialog()"
> >
<template #icon>
<Plus />
</template>
新增 新增
</el-button> </el-button>
<el-button <el-button
v-hasPerm="['sys:config:refresh']" v-hasPerm="['sys:config:refresh']"
color="#626aef" color="#626aef"
icon="RefreshLeft"
@click="handleRefreshCache" @click="handleRefreshCache"
> >
<el-icon><RefreshLeft /></el-icon>
刷新缓存 刷新缓存
</el-button> </el-button>
</template> </div>
<el-table <el-table
ref="dataTableRef" ref="dataTableRef"
@@ -58,30 +46,10 @@
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="index" label="序号" width="60" /> <el-table-column type="index" label="序号" width="60" />
<el-table-column <el-table-column key="configName" label="配置名称" prop="configName" min-width="100" />
key="configName" <el-table-column key="configKey" label="配置键" prop="configKey" min-width="100" />
label="配置名称" <el-table-column key="configValue" label="配置值" prop="configValue" min-width="100" />
prop="configName" <el-table-column key="remark" label="描述" prop="remark" min-width="100" />
min-width="100"
/>
<el-table-column
key="configKey"
label="配置键"
prop="configKey"
min-width="100"
/>
<el-table-column
key="configValue"
label="配置值"
prop="configValue"
min-width="100"
/>
<el-table-column
key="remark"
label="描述"
prop="remark"
min-width="100"
/>
<el-table-column fixed="right" label="操作" width="220"> <el-table-column fixed="right" label="操作" width="220">
<template #default="scope"> <template #default="scope">
<el-button <el-button
@@ -89,11 +57,9 @@
type="primary" type="primary"
size="small" size="small"
link link
icon="edit"
@click="handleOpenDialog(scope.row.id)" @click="handleOpenDialog(scope.row.id)"
> >
<template #icon>
<Edit />
</template>
编辑 编辑
</el-button> </el-button>
<el-button <el-button
@@ -101,11 +67,9 @@
type="danger" type="danger"
size="small" size="small"
link link
icon="delete"
@click="handleDelete(scope.row.id)" @click="handleDelete(scope.row.id)"
> >
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -136,25 +100,13 @@
label-width="100px" label-width="100px"
> >
<el-form-item label="配置名称" prop="configName"> <el-form-item label="配置名称" prop="configName">
<el-input <el-input v-model="formData.configName" placeholder="请输入配置名称" :maxlength="50" />
v-model="formData.configName"
placeholder="请输入配置名称"
:maxlength="50"
/>
</el-form-item> </el-form-item>
<el-form-item label="配置键" prop="configKey"> <el-form-item label="配置键" prop="configKey">
<el-input <el-input v-model="formData.configKey" placeholder="请输入配置键" :maxlength="50" />
v-model="formData.configKey"
placeholder="请输入配置键"
:maxlength="50"
/>
</el-form-item> </el-form-item>
<el-form-item label="配置值" prop="configValue"> <el-form-item label="配置值" prop="configValue">
<el-input <el-input v-model="formData.configValue" placeholder="请输入配置值" :maxlength="100" />
v-model="formData.configValue"
placeholder="请输入配置值"
:maxlength="100"
/>
</el-form-item> </el-form-item>
<el-form-item label="描述" prop="remark"> <el-form-item label="描述" prop="remark">
<el-input <el-input
@@ -183,17 +135,13 @@ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}); });
import ConfigAPI, { import ConfigAPI, { ConfigPageVO, ConfigForm, ConfigPageQuery } from "@/api/system/config";
ConfigPageVO,
ConfigForm,
ConfigPageQuery,
} from "@/api/system/config";
const queryFormRef = ref(ElForm); const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm); const dataFormRef = ref(ElForm);
const loading = ref(false); const loading = ref(false);
const ids = ref<number[]>([]); const selectIds = ref<number[]>([]);
const total = ref(0); const total = ref(0);
const queryParams = reactive<ConfigPageQuery>({ const queryParams = reactive<ConfigPageQuery>({
@@ -205,12 +153,11 @@ const queryParams = reactive<ConfigPageQuery>({
// 系统配置表格数据 // 系统配置表格数据
const pageData = ref<ConfigPageVO[]>([]); const pageData = ref<ConfigPageVO[]>([]);
// 弹窗
const dialog = reactive({ const dialog = reactive({
title: "", title: "",
visible: false, visible: false,
}); });
// 系统配置表单
const formData = reactive<ConfigForm>({ const formData = reactive<ConfigForm>({
id: undefined, id: undefined,
configName: "", configName: "",
@@ -220,18 +167,12 @@ const formData = reactive<ConfigForm>({
}); });
const rules = reactive({ const rules = reactive({
configName: [ configName: [{ required: true, message: "请输入系统配置名称", trigger: "blur" }],
{ required: true, message: "请输入系统配置名称", trigger: "blur" }, configKey: [{ required: true, message: "请输入系统配置编码", trigger: "blur" }],
], configValue: [{ required: true, message: "请输入系统配置值", trigger: "blur" }],
configKey: [
{ required: true, message: "请输入系统配置编码", trigger: "blur" },
],
configValue: [
{ required: true, message: "请输入系统配置值", trigger: "blur" },
],
}); });
/** 查询系统配置 */ // 查询系统配置
function handleQuery() { function handleQuery() {
loading.value = true; loading.value = true;
ConfigAPI.getPage(queryParams) ConfigAPI.getPage(queryParams)
@@ -243,19 +184,20 @@ function handleQuery() {
loading.value = false; loading.value = false;
}); });
} }
/** 重置系统配置查询 */
// 重置查询
function handleResetQuery() { function handleResetQuery() {
queryFormRef.value.resetFields(); queryFormRef.value.resetFields();
queryParams.pageNum = 1; queryParams.pageNum = 1;
handleQuery(); handleQuery();
} }
/** 行复选框选中记录选中ID集合 */ // 行复选框选中项变化
function handleSelectionChange(selection: any) { function handleSelectionChange(selection: any) {
ids.value = selection.map((item: any) => item.id); selectIds.value = selection.map((item: any) => item.id);
} }
/** 打开系统配置弹窗 */ // 打开系统配置弹窗
function handleOpenDialog(id?: number) { function handleOpenDialog(id?: number) {
dialog.visible = true; dialog.visible = true;
if (id) { if (id) {
@@ -269,14 +211,14 @@ function handleOpenDialog(id?: number) {
} }
} }
/** 刷新缓存 **/ // 刷新缓存
function handleRefreshCache() { function handleRefreshCache() {
ConfigAPI.refreshCache().then(() => { ConfigAPI.refreshCache().then(() => {
ElMessage.success("刷新成功"); ElMessage.success("刷新成功");
}); });
} }
/** 提交系统配置表单 */ // 系统配置表单提交
function handleSubmit() { function handleSubmit() {
dataFormRef.value.validate((valid: any) => { dataFormRef.value.validate((valid: any) => {
if (valid) { if (valid) {
@@ -303,32 +245,34 @@ function handleSubmit() {
}); });
} }
/** 关闭系统配置弹窗 */ // 重置表单
function handleCloseDialog() { function resetForm() {
dialog.visible = false;
dataFormRef.value.resetFields(); dataFormRef.value.resetFields();
dataFormRef.value.clearValidate(); dataFormRef.value.clearValidate();
formData.id != undefined; formData.id = undefined;
} }
/** 删除系统配置 */ // 关闭系统配置弹窗
function handleCloseDialog() {
dialog.visible = false;
resetForm();
}
// 删除系统配置
function handleDelete(id: number) { function handleDelete(id: number) {
ElMessageBox.confirm("确认删除该项配置?", "警告", { ElMessageBox.confirm("确认删除该项配置?", "警告", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning", type: "warning",
}).then( }).then(() => {
() => { loading.value = true;
loading.value = true; ConfigAPI.deleteById(id)
ConfigAPI.deleteById(id) .then(() => {
.then(() => { ElMessage.success("删除成功");
ElMessage.success("删除成功"); handleResetQuery();
handleResetQuery(); })
}) .finally(() => (loading.value = false));
.finally(() => (loading.value = false)); });
},
() => {}
);
} }
onMounted(() => { onMounted(() => {

View File

@@ -17,18 +17,10 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button class="filter-item" type="primary" @click="handleQuery"> <el-button class="filter-item" type="primary" icon="search" @click="handleQuery">
<template #icon>
<Search />
</template>
搜索 搜索
</el-button> </el-button>
<el-button @click="handleResetQuery"> <el-button icon="refresh" @click="handleResetQuery">重置</el-button>
<template #icon>
<Refresh />
</template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -38,22 +30,18 @@
<el-button <el-button
v-hasPerm="['sys:dept:add']" v-hasPerm="['sys:dept:add']"
type="success" type="success"
icon="plus"
@click="handleOpenDialog(0, undefined)" @click="handleOpenDialog(0, undefined)"
> >
<template #icon>
<Plus />
</template>
新增 新增
</el-button> </el-button>
<el-button <el-button
v-hasPerm="['sys:dept:delete']" v-hasPerm="['sys:dept:delete']"
type="danger" type="danger"
:disabled="ids.length === 0" :disabled="selectIds.length === 0"
icon="delete"
@click="handleDelete()" @click="handleDelete()"
> >
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</div> </div>
@@ -85,11 +73,9 @@
type="primary" type="primary"
link link
size="small" size="small"
icon="plus"
@click.stop="handleOpenDialog(scope.row.id, undefined)" @click.stop="handleOpenDialog(scope.row.id, undefined)"
> >
<template #icon>
<Plus />
</template>
新增 新增
</el-button> </el-button>
<el-button <el-button
@@ -97,11 +83,9 @@
type="primary" type="primary"
link link
size="small" size="small"
icon="edit"
@click.stop="handleOpenDialog(scope.row.parentId, scope.row.id)" @click.stop="handleOpenDialog(scope.row.parentId, scope.row.id)"
> >
<template #icon>
<Edit />
</template>
编辑 编辑
</el-button> </el-button>
<el-button <el-button
@@ -109,11 +93,9 @@
type="danger" type="danger"
link link
size="small" size="small"
icon="delete"
@click.stop="handleDelete(scope.row.id)" @click.stop="handleDelete(scope.row.id)"
> >
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -182,17 +164,16 @@ const queryFormRef = ref(ElForm);
const deptFormRef = ref(ElForm); const deptFormRef = ref(ElForm);
const loading = ref(false); const loading = ref(false);
const ids = ref<number[]>([]); const selectIds = ref<number[]>([]);
const queryParams = reactive<DeptQuery>({});
const dialog = reactive({ const dialog = reactive({
title: "", title: "",
visible: false, visible: false,
}); });
const queryParams = reactive<DeptQuery>({});
const deptList = ref<DeptVO[]>(); const deptList = ref<DeptVO[]>();
const deptOptions = ref<OptionType[]>(); const deptOptions = ref<OptionType[]>();
const formData = reactive<DeptForm>({ const formData = reactive<DeptForm>({
status: 1, status: 1,
parentId: "0", parentId: "0",
@@ -221,9 +202,9 @@ function handleResetQuery() {
handleQuery(); handleQuery();
} }
// 行复选框选中记录选中ID集合 // 处理选中项变化
function handleSelectionChange(selection: any) { function handleSelectionChange(selection: any) {
ids.value = selection.map((item: any) => item.id); selectIds.value = selection.map((item: any) => item.id);
} }
/** /**
@@ -284,7 +265,7 @@ function handleSubmit() {
// 删除部门 // 删除部门
function handleDelete(deptId?: number) { function handleDelete(deptId?: number) {
const deptIds = [deptId || ids.value].join(","); const deptIds = [deptId || selectIds.value].join(",");
if (!deptIds) { if (!deptIds) {
ElMessage.warning("请勾选删除项"); ElMessage.warning("请勾选删除项");

View File

@@ -12,38 +12,16 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery()"> <el-button type="primary" icon="search" @click="handleQuery()">搜索</el-button>
<template #icon> <el-button icon="refresh" @click="handleResetQuery()">重置</el-button>
<Search />
</template>
搜索
</el-button>
<el-button @click="handleResetQuery()">
<template #icon>
<Refresh />
</template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-card shadow="never"> <el-card shadow="never">
<div class="mb-[10px]"> <div class="mb-[10px]">
<el-button type="success" @click="handleOpenDialog()"> <el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>
<template #icon> <el-button type="danger" :disabled="ids.length === 0" icon="delete" @click="handleDelete()">
<Plus />
</template>
新增
</el-button>
<el-button
type="danger"
:disabled="ids.length === 0"
@click="handleDelete()"
>
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</div> </div>
@@ -73,22 +51,18 @@
type="primary" type="primary"
link link
size="small" size="small"
icon="edit"
@click.stop="handleOpenDialog(scope.row)" @click.stop="handleOpenDialog(scope.row)"
> >
<template #icon>
<Edit />
</template>
编辑 编辑
</el-button> </el-button>
<el-button <el-button
type="danger" type="danger"
link link
size="small" size="small"
icon="delete"
@click.stop="handleDelete(scope.row.id)" @click.stop="handleDelete(scope.row.id)"
> >
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -111,12 +85,7 @@
width="820px" width="820px"
@close="handleCloseDialog" @close="handleCloseDialog"
> >
<el-form <el-form ref="dataFormRef" :model="formData" :rules="computedRules" label-width="100px">
ref="dataFormRef"
:model="formData"
:rules="computedRules"
label-width="100px"
>
<el-card shadow="never"> <el-card shadow="never">
<el-form-item label="字典标签" prop="label"> <el-form-item label="字典标签" prop="label">
<el-input v-model="formData.label" placeholder="请输入字典标签" /> <el-input v-model="formData.label" placeholder="请输入字典标签" />
@@ -131,17 +100,10 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="排序"> <el-form-item label="排序">
<el-input-number <el-input-number v-model="formData.sort" controls-position="right" />
v-model="formData.sort"
controls-position="right"
/>
</el-form-item> </el-form-item>
<el-form-item label="标签类型"> <el-form-item label="标签类型">
<el-tag <el-tag v-if="formData.tagType" :type="formData.tagType" class="mr-2">
v-if="formData.tagType"
:type="formData.tagType"
class="mr-2"
>
{{ formData.label }} {{ formData.label }}
</el-tag> </el-tag>
<el-radio-group v-model="formData.tagType"> <el-radio-group v-model="formData.tagType">

View File

@@ -12,38 +12,16 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery()"> <el-button type="primary" icon="search" @click="handleQuery()">搜索</el-button>
<template #icon> <el-button icon="refresh" @click="handleResetQuery()">重置</el-button>
<Search />
</template>
搜索
</el-button>
<el-button @click="handleResetQuery()">
<template #icon>
<Refresh />
</template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-card shadow="never"> <el-card shadow="never">
<div class="mb-[10px]"> <div class="mb-[10px]">
<el-button type="success" @click="handleAddClick()"> <el-button type="success" icon="plus" @click="handleAddClick()">新增</el-button>
<template #icon> <el-button type="danger" :disabled="ids.length === 0" icon="delete" @click="handleDelete()">
<Plus />
</template>
新增
</el-button>
<el-button
type="danger"
:disabled="ids.length === 0"
@click="handleDelete()"
>
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</div> </div>
@@ -67,12 +45,7 @@
</el-table-column> </el-table-column>
<el-table-column fixed="right" label="操作" align="center" width="220"> <el-table-column fixed="right" label="操作" align="center" width="220">
<template #default="scope"> <template #default="scope">
<el-button <el-button type="primary" link size="small" @click.stop="handleOpenDictData(scope.row)">
type="primary"
link
size="small"
@click.stop="handleOpenDictData(scope.row)"
>
<template #icon> <template #icon>
<Collection /> <Collection />
</template> </template>
@@ -83,22 +56,18 @@
type="primary" type="primary"
link link
size="small" size="small"
icon="edit"
@click.stop="handleEditClick(scope.row.id, scope.row.name)" @click.stop="handleEditClick(scope.row.id, scope.row.name)"
> >
<template #icon>
<Edit />
</template>
编辑 编辑
</el-button> </el-button>
<el-button <el-button
type="danger" type="danger"
link link
size="small" size="small"
icon="delete"
@click.stop="handleDelete(scope.row.id)" @click.stop="handleDelete(scope.row.id)"
> >
<template #icon>
<Delete />
</template>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -121,22 +90,14 @@
width="500px" width="500px"
@close="handleCloseDialog" @close="handleCloseDialog"
> >
<el-form <el-form ref="dataFormRef" :model="formData" :rules="computedRules" label-width="100px">
ref="dataFormRef"
:model="formData"
:rules="computedRules"
label-width="100px"
>
<el-card shadow="never"> <el-card shadow="never">
<el-form-item label="字典名称" prop="name"> <el-form-item label="字典名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入字典名称" /> <el-input v-model="formData.name" placeholder="请输入字典名称" />
</el-form-item> </el-form-item>
<el-form-item label="字典编码" prop="dictCode"> <el-form-item label="字典编码" prop="dictCode">
<el-input <el-input v-model="formData.dictCode" placeholder="请输入字典编码" />
v-model="formData.dictCode"
placeholder="请输入字典编码"
/>
</el-form-item> </el-form-item>
<el-form-item label="状态"> <el-form-item label="状态">
@@ -147,11 +108,7 @@
</el-form-item> </el-form-item>
<el-form-item label="备注"> <el-form-item label="备注">
<el-input <el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
v-model="formData.remark"
type="textarea"
placeholder="请输入备注"
/>
</el-form-item> </el-form-item>
</el-card> </el-card>
</el-form> </el-form>
@@ -172,11 +129,7 @@ defineOptions({
inherititems: false, inherititems: false,
}); });
import DictAPI, { import DictAPI, { DictPageQuery, DictPageVO, DictForm } from "@/api/system/dict";
DictPageQuery,
DictPageVO,
DictForm,
} from "@/api/system/dict";
import router from "@/router"; import router from "@/router";

View File

@@ -24,29 +24,14 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery"> <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<template #icon> <el-button icon="refresh" @click="handleResetQuery">重置</el-button>
<Search />
</template>
搜索
</el-button>
<el-button @click="handleResetQuery">
<template #icon>
<Refresh />
</template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-card shadow="never"> <el-card shadow="never">
<el-table <el-table v-loading="loading" :data="pageData" highlight-current-row border>
v-loading="loading"
:data="pageData"
highlight-current-row
border
>
<el-table-column label="操作时间" prop="createTime" width="180" /> <el-table-column label="操作时间" prop="createTime" width="180" />
<el-table-column label="操作人" prop="operator" width="120" /> <el-table-column label="操作人" prop="operator" width="120" />
<el-table-column label="日志模块" prop="module" width="100" /> <el-table-column label="日志模块" prop="module" width="100" />
@@ -54,17 +39,8 @@
<el-table-column label="IP 地址" prop="ip" width="150" /> <el-table-column label="IP 地址" prop="ip" width="150" />
<el-table-column label="地区" prop="region" width="150" /> <el-table-column label="地区" prop="region" width="150" />
<el-table-column label="浏览器" prop="browser" width="150" /> <el-table-column label="浏览器" prop="browser" width="150" />
<el-table-column <el-table-column label="终端系统" prop="os" width="200" show-overflow-tooltip />
label="终端系统" <el-table-column label="执行时间(ms)" prop="executionTime" width="150" />
prop="os"
width="200"
show-overflow-tooltip
/>
<el-table-column
label="执行时间(ms)"
prop="executionTime"
width="150"
/>
</el-table> </el-table>
<pagination <pagination

View File

@@ -11,29 +11,23 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery"> <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<template #icon><Search /></template> <el-button icon="refresh" @click="handleResetQuery">重置</el-button>
搜索
</el-button>
<el-button @click="handleResetQuery">
<template #icon><Refresh /></template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-card shadow="never" class="table-wrapper"> <el-card shadow="never">
<template #header> <div class="mb-10px">
<el-button <el-button
v-hasPerm="['sys:menu:add']" v-hasPerm="['sys:menu:add']"
type="success" type="success"
icon="plus"
@click="handleOpenDialog(0)" @click="handleOpenDialog(0)"
> >
<template #icon><Plus /></template>
新增 新增
</el-button> </el-button>
</template> </div>
<el-table <el-table
v-loading="loading" v-loading="loading"
@@ -49,9 +43,7 @@
> >
<el-table-column label="菜单名称" min-width="200"> <el-table-column label="菜单名称" min-width="200">
<template #default="scope"> <template #default="scope">
<template <template v-if="scope.row.icon && scope.row.icon.startsWith('el-icon')">
v-if="scope.row.icon && scope.row.icon.startsWith('el-icon')"
>
<el-icon style="vertical-align: -0.15em"> <el-icon style="vertical-align: -0.15em">
<component :is="scope.row.icon.replace('el-icon-', '')" /> <component :is="scope.row.icon.replace('el-icon-', '')" />
</el-icon> </el-icon>
@@ -59,70 +51,29 @@
<template v-else-if="scope.row.icon"> <template v-else-if="scope.row.icon">
<svg-icon :icon-class="scope.row.icon" /> <svg-icon :icon-class="scope.row.icon" />
</template> </template>
<template v-else>
<svg-icon icon-class="menu" />
</template>
{{ scope.row.name }} {{ scope.row.name }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="类型" align="center" width="80"> <el-table-column label="类型" align="center" width="80">
<template #default="scope"> <template #default="scope">
<el-tag <el-tag v-if="scope.row.type === MenuTypeEnum.CATALOG" type="warning">目录</el-tag>
v-if="scope.row.type === MenuTypeEnum.CATALOG" <el-tag v-if="scope.row.type === MenuTypeEnum.MENU" type="success">菜单</el-tag>
type="warning" <el-tag v-if="scope.row.type === MenuTypeEnum.BUTTON" type="danger">按钮</el-tag>
> <el-tag v-if="scope.row.type === MenuTypeEnum.EXTLINK" type="info">外链</el-tag>
目录
</el-tag>
<el-tag v-if="scope.row.type === MenuTypeEnum.MENU" type="success">
菜单
</el-tag>
<el-tag v-if="scope.row.type === MenuTypeEnum.BUTTON" type="danger">
按钮
</el-tag>
<el-tag v-if="scope.row.type === MenuTypeEnum.EXTLINK" type="info">
外链
</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="路由名称" align="left" width="150" prop="routeName" />
<el-table-column <el-table-column label="路由路径" align="left" width="150" prop="routePath" />
label="路由名称" <el-table-column label="组件路径" align="left" width="250" prop="component" />
align="left" <el-table-column label="权限标识" align="center" width="200" prop="perm" />
width="150"
prop="routeName"
/>
<el-table-column
label="路由路径"
align="left"
width="150"
prop="routePath"
/>
<el-table-column
label="组件路径"
align="left"
width="250"
prop="component"
/>
<el-table-column
label="权限标识"
align="center"
width="200"
prop="perm"
/>
<el-table-column label="状态" align="center" width="80"> <el-table-column label="状态" align="center" width="80">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.visible === 1" type="success">显示</el-tag> <el-tag v-if="scope.row.visible === 1" type="success">显示</el-tag>
<el-tag v-else type="info">隐藏</el-tag> <el-tag v-else type="info">隐藏</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="排序" align="center" width="80" prop="sort" /> <el-table-column label="排序" align="center" width="80" prop="sort" />
<el-table-column fixed="right" align="center" label="操作" width="220"> <el-table-column fixed="right" align="center" label="操作" width="220">
<template #default="scope"> <template #default="scope">
<el-button <el-button
@@ -131,9 +82,9 @@
type="primary" type="primary"
link link
size="small" size="small"
icon="plus"
@click.stop="handleOpenDialog(scope.row.id)" @click.stop="handleOpenDialog(scope.row.id)"
> >
<template #icon><Plus /></template>
新增 新增
</el-button> </el-button>
@@ -142,9 +93,9 @@
type="primary" type="primary"
link link
size="small" size="small"
icon="edit"
@click.stop="handleOpenDialog(undefined, scope.row.id)" @click.stop="handleOpenDialog(undefined, scope.row.id)"
> >
<template #icon><Edit /></template>
编辑 编辑
</el-button> </el-button>
<el-button <el-button
@@ -152,9 +103,9 @@
type="danger" type="danger"
link link
size="small" size="small"
icon="delete"
@click.stop="handleDelete(scope.row.id)" @click.stop="handleDelete(scope.row.id)"
> >
<template #icon><Delete /></template>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -162,18 +113,8 @@
</el-table> </el-table>
</el-card> </el-card>
<el-drawer <el-drawer v-model="dialog.visible" :title="dialog.title" size="50%" @close="handleCloseDialog">
v-model="dialog.visible" <el-form ref="menuFormRef" :model="formData" :rules="rules" label-width="100px">
:title="dialog.title"
size="50%"
@close="handleCloseDialog"
>
<el-form
ref="menuFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="父级菜单" prop="parentId"> <el-form-item label="父级菜单" prop="parentId">
<el-tree-select <el-tree-select
v-model="formData.parentId" v-model="formData.parentId"
@@ -190,10 +131,7 @@
</el-form-item> </el-form-item>
<el-form-item label="菜单类型" prop="type"> <el-form-item label="菜单类型" prop="type">
<el-radio-group <el-radio-group v-model="formData.type" @change="handleMenuTypeChange">
v-model="formData.type"
@change="handleMenuTypeChange"
>
<el-radio value="CATALOG">目录</el-radio> <el-radio value="CATALOG">目录</el-radio>
<el-radio value="MENU">菜单</el-radio> <el-radio value="MENU">菜单</el-radio>
<el-radio value="BUTTON">按钮</el-radio> <el-radio value="BUTTON">按钮</el-radio>
@@ -201,30 +139,18 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type == 'EXTLINK'" label="外链地址" prop="path">
v-if="formData.type == 'EXTLINK'" <el-input v-model="formData.routePath" placeholder="请输入外链完整路径" />
label="外链地址"
prop="path"
>
<el-input
v-model="formData.routePath"
placeholder="请输入外链完整路径"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="routeName">
v-if="formData.type == MenuTypeEnum.MENU"
prop="routeName"
>
<template #label> <template #label>
<div class="flex-y-center"> <div class="flex-y-center">
路由名称 路由名称
<el-tooltip placement="bottom" effect="light"> <el-tooltip placement="bottom" effect="light">
<template #content> <template #content>
如果需要开启缓存需保证页面 defineOptions 中的 name 如果需要开启缓存需保证页面 defineOptions 中的 name 与此处一致建议使用驼峰
与此处一致建议使用驼峰
</template> </template>
<el-icon class="ml-1 cursor-pointer"> <el-icon class="ml-1 cursor-pointer">
<QuestionFilled /> <QuestionFilled />
</el-icon> </el-icon>
@@ -235,10 +161,7 @@
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if=" v-if="formData.type == MenuTypeEnum.CATALOG || formData.type == MenuTypeEnum.MENU"
formData.type == MenuTypeEnum.CATALOG ||
formData.type == MenuTypeEnum.MENU
"
prop="routePath" prop="routePath"
> >
<template #label> <template #label>
@@ -246,8 +169,7 @@
路由路径 路由路径
<el-tooltip placement="bottom" effect="light"> <el-tooltip placement="bottom" effect="light">
<template #content> <template #content>
定义应用中不同页面对应的 URL 路径,目录需以 / 定义应用中不同页面对应的 URL 路径目录需以 / 开头菜单项不用例如系统管理目录
开头,菜单项不用。例如:系统管理目录
/system系统管理下的用户管理菜单 user /system系统管理下的用户管理菜单 user
</template> </template>
<el-icon class="ml-1 cursor-pointer"> <el-icon class="ml-1 cursor-pointer">
@@ -264,17 +186,13 @@
<el-input v-else v-model="formData.routePath" placeholder="user" /> <el-input v-else v-model="formData.routePath" placeholder="user" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="component">
v-if="formData.type == MenuTypeEnum.MENU"
prop="component"
>
<template #label> <template #label>
<div class="flex-y-center"> <div class="flex-y-center">
组件路径 组件路径
<el-tooltip placement="bottom" effect="light"> <el-tooltip placement="bottom" effect="light">
<template #content> <template #content>
组件页面完整路径,相对于 src/views/,如 组件页面完整路径相对于 src/views/ system/user/index缺省后缀 .vue
system/user/index缺省后缀 .vue
</template> </template>
<el-icon class="ml-1 cursor-pointer"> <el-icon class="ml-1 cursor-pointer">
<QuestionFilled /> <QuestionFilled />
@@ -283,17 +201,9 @@
</div> </div>
</template> </template>
<el-input <el-input v-model="formData.component" placeholder="system/user/index" style="width: 95%">
v-model="formData.component" <template v-if="formData.type == MenuTypeEnum.MENU" #prepend>src/views/</template>
placeholder="system/user/index" <template v-if="formData.type == MenuTypeEnum.MENU" #append>.vue</template>
style="width: 95%"
>
<template v-if="formData.type == MenuTypeEnum.MENU" #prepend>
src/views/
</template>
<template v-if="formData.type == MenuTypeEnum.MENU" #append>
.vue
</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -313,35 +223,21 @@
</template> </template>
<div v-if="!formData.params || formData.params.length === 0"> <div v-if="!formData.params || formData.params.length === 0">
<el-button <el-button type="success" plain @click="formData.params = [{ key: '', value: '' }]">
type="success"
plain
@click="formData.params = [{ key: '', value: '' }]"
>
添加路由参数 添加路由参数
</el-button> </el-button>
</div> </div>
<div v-else> <div v-else>
<div v-for="(item, index) in formData.params" :key="index"> <div v-for="(item, index) in formData.params" :key="index">
<el-input <el-input v-model="item.key" placeholder="参数名" style="width: 100px" />
v-model="item.key"
placeholder="参数名"
style="width: 100px"
/>
<span class="mx-1">=</span> <span class="mx-1">=</span>
<el-input <el-input v-model="item.value" placeholder="参数值" style="width: 100px" />
v-model="item.value"
placeholder="参数值"
style="width: 100px"
/>
<el-icon <el-icon
v-if=" v-if="formData.params.indexOf(item) === formData.params.length - 1"
formData.params.indexOf(item) === formData.params.length - 1
"
class="ml-2 cursor-pointer color-[var(--el-color-success)]" class="ml-2 cursor-pointer color-[var(--el-color-success)]"
style="vertical-align: -0.15em" style="vertical-align: -0.15em"
@click="formData.params.push({ key: '', value: '' })" @click="formData.params.push({ key: '', value: '' })"
@@ -351,9 +247,7 @@
<el-icon <el-icon
class="ml-2 cursor-pointer color-[var(--el-color-danger)]" class="ml-2 cursor-pointer color-[var(--el-color-danger)]"
style="vertical-align: -0.15em" style="vertical-align: -0.15em"
@click=" @click="formData.params.splice(formData.params.indexOf(item), 1)"
formData.params.splice(formData.params.indexOf(item), 1)
"
> >
<DeleteFilled /> <DeleteFilled />
</el-icon> </el-icon>
@@ -361,11 +255,7 @@
</div> </div>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type !== MenuTypeEnum.BUTTON" prop="visible" label="显示状态">
v-if="formData.type !== MenuTypeEnum.BUTTON"
prop="visible"
label="显示状态"
>
<el-radio-group v-model="formData.visible"> <el-radio-group v-model="formData.visible">
<el-radio :value="1">显示</el-radio> <el-radio :value="1">显示</el-radio>
<el-radio :value="0">隐藏</el-radio> <el-radio :value="0">隐藏</el-radio>
@@ -373,10 +263,7 @@
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if=" v-if="formData.type === MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
formData.type === MenuTypeEnum.CATALOG ||
formData.type === MenuTypeEnum.MENU
"
> >
<template #label> <template #label>
<div class="flex-y-center"> <div class="flex-y-center">
@@ -402,10 +289,7 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type === MenuTypeEnum.MENU" label="缓存页面">
v-if="formData.type === MenuTypeEnum.MENU"
label="缓存页面"
>
<el-radio-group v-model="formData.keepAlive"> <el-radio-group v-model="formData.keepAlive">
<el-radio :value="1">开启</el-radio> <el-radio :value="1">开启</el-radio>
<el-radio :value="0">关闭</el-radio> <el-radio :value="0">关闭</el-radio>
@@ -422,34 +306,23 @@
</el-form-item> </el-form-item>
<!-- 权限标识 --> <!-- 权限标识 -->
<el-form-item <el-form-item v-if="formData.type == MenuTypeEnum.BUTTON" label="权限标识" prop="perm">
v-if="formData.type == MenuTypeEnum.BUTTON"
label="权限标识"
prop="perm"
>
<el-input v-model="formData.perm" placeholder="sys:user:add" /> <el-input v-model="formData.perm" placeholder="sys:user:add" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type !== MenuTypeEnum.BUTTON" label="图标" prop="icon">
v-if="formData.type !== MenuTypeEnum.BUTTON"
label="图标"
prop="icon"
>
<!-- 图标选择器 --> <!-- 图标选择器 -->
<icon-select v-model="formData.icon" /> <icon-select v-model="formData.icon" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.type == MenuTypeEnum.CATALOG" label="跳转路由">
v-if="formData.type == MenuTypeEnum.CATALOG"
label="跳转路由"
>
<el-input v-model="formData.redirect" placeholder="跳转路由" /> <el-input v-model="formData.redirect" placeholder="跳转路由" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button> <el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleCloseDialog"> </el-button> <el-button @click="handleCloseDialog"> </el-button>
</div> </div>
</template> </template>
@@ -481,7 +354,6 @@ const queryParams = reactive<MenuQuery>({});
const menuTableData = ref<MenuVO[]>([]); const menuTableData = ref<MenuVO[]>([]);
// 顶级菜单下拉选项 // 顶级菜单下拉选项
const menuOptions = ref<OptionType[]>([]); const menuOptions = ref<OptionType[]>([]);
// 初始菜单表单数据 // 初始菜单表单数据
const initialMenuFormData = ref<MenuForm>({ const initialMenuFormData = ref<MenuForm>({
id: undefined, id: undefined,
@@ -493,10 +365,8 @@ const initialMenuFormData = ref<MenuForm>({
keepAlive: 1, keepAlive: 1,
params: [], params: [],
}); });
// 菜单表单数据 // 菜单表单数据
const formData = ref({ ...initialMenuFormData.value }); const formData = ref({ ...initialMenuFormData.value });
// 表单验证规则 // 表单验证规则
const rules = reactive({ const rules = reactive({
parentId: [{ required: true, message: "请选择顶级菜单", trigger: "blur" }], parentId: [{ required: true, message: "请选择顶级菜单", trigger: "blur" }],
@@ -578,8 +448,10 @@ function handleMenuTypeChange() {
} }
} }
/** 菜单保存提交 */ /**
function submitForm() { * 提交表单
*/
function handleSubmit() {
menuFormRef.value.validate((isValid: boolean) => { menuFormRef.value.validate((isValid: boolean) => {
if (isValid) { if (isValid) {
const menuId = formData.value.id; const menuId = formData.value.id;

View File

@@ -27,13 +27,8 @@
</el-form> </el-form>
</div> </div>
<el-card shadow="never" class="table-wrapper"> <el-card shadow="never">
<el-table <el-table ref="dataTableRef" v-loading="loading" :data="pageData" highlight-current-row>
ref="dataTableRef"
v-loading="loading"
:data="pageData"
highlight-current-row
>
<el-table-column type="index" label="序号" width="60" /> <el-table-column type="index" label="序号" width="60" />
<el-table-column label="通知标题" prop="title" min-width="200" /> <el-table-column label="通知标题" prop="title" min-width="200" />
<el-table-column align="center" label="通知类型" width="150"> <el-table-column align="center" label="通知类型" width="150">
@@ -41,12 +36,7 @@
<DictLabel v-model="scope.row.type" code="notice_type" /> <DictLabel v-model="scope.row.type" code="notice_type" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="center" label="发布人" prop="publisherName" width="100" />
align="center"
label="发布人"
prop="publisherName"
width="100"
/>
<el-table-column align="center" label="通知等级" width="100"> <el-table-column align="center" label="通知等级" width="100">
<template #default="scope"> <template #default="scope">
<DictLabel v-model="scope.row.level" code="notice_level" /> <DictLabel v-model="scope.row.level" code="notice_level" />
@@ -60,12 +50,7 @@
width="150" width="150"
/> />
<el-table-column <el-table-column align="center" label="发布人" prop="publisherName" width="150" />
align="center"
label="发布人"
prop="publisherName"
width="150"
/>
<el-table-column align="center" label="状态" width="100"> <el-table-column align="center" label="状态" width="100">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.isRead == 1" type="success">已读</el-tag> <el-tag v-if="scope.row.isRead == 1" type="success">已读</el-tag>
@@ -74,12 +59,7 @@
</el-table-column> </el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="80"> <el-table-column align="center" fixed="right" label="操作" width="80">
<template #default="scope"> <template #default="scope">
<el-button <el-button type="primary" size="small" link @click="viewNoticeDetail(scope.row.id)">
type="primary"
size="small"
link
@click="viewNoticeDetail(scope.row.id)"
>
查看 查看
</el-button> </el-button>
</template> </template>

View File

@@ -1,12 +1,7 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="search-bar"> <div class="search-bar">
<el-form <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-suffix=":">
ref="queryFormRef"
:model="queryParams"
:inline="true"
label-suffix=":"
>
<el-form-item label="标题" prop="title"> <el-form-item label="标题" prop="title">
<el-input <el-input
v-model="queryParams.title" v-model="queryParams.title"
@@ -28,14 +23,8 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery()"> <el-button type="primary" icon="search" @click="handleQuery()">搜索</el-button>
<template #icon><Search /></template> <el-button icon="refresh" @click="handleResetQuery()">重置</el-button>
搜索
</el-button>
<el-button @click="handleResetQuery()">
<template #icon><Refresh /></template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -45,18 +34,18 @@
<el-button <el-button
v-hasPerm="['sys:notice:add']" v-hasPerm="['sys:notice:add']"
type="success" type="success"
icon="plus"
@click="handleOpenDialog()" @click="handleOpenDialog()"
> >
<template #icon><Plus /></template>
新增通知 新增通知
</el-button> </el-button>
<el-button <el-button
v-hasPerm="['sys:notice:delete']" v-hasPerm="['sys:notice:delete']"
type="danger" type="danger"
:disabled="ids.length === 0" :disabled="selectIds.length === 0"
icon="delete"
@click="handleDelete()" @click="handleDelete()"
> >
<template #icon><Delete /></template>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -76,43 +65,23 @@
<DictLabel v-model="scope.row.type" :code="'notice_type'" /> <DictLabel v-model="scope.row.type" :code="'notice_type'" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="center" label="发布人" prop="publisherName" width="150" />
align="center"
label="发布人"
prop="publisherName"
width="150"
/>
<el-table-column align="center" label="通知等级" width="100"> <el-table-column align="center" label="通知等级" width="100">
<template #default="scope"> <template #default="scope">
<DictLabel v-model="scope.row.level" code="notice_level" /> <DictLabel v-model="scope.row.level" code="notice_level" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="center" label="通告目标类型" prop="targetType" min-width="100">
align="center"
label="通告目标类型"
prop="targetType"
min-width="100"
>
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.targetType == 1" type="warning"> <el-tag v-if="scope.row.targetType == 1" type="warning">全体</el-tag>
全体 <el-tag v-if="scope.row.targetType == 2" type="success">指定</el-tag>
</el-tag>
<el-tag v-if="scope.row.targetType == 2" type="success">
指定
</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="发布状态" min-width="100"> <el-table-column align="center" label="发布状态" min-width="100">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.publishStatus == 0" type="info"> <el-tag v-if="scope.row.publishStatus == 0" type="info">未发布</el-tag>
未发布 <el-tag v-if="scope.row.publishStatus == 1" type="success">已发布</el-tag>
</el-tag> <el-tag v-if="scope.row.publishStatus == -1" type="warning">已撤回</el-tag>
<el-tag v-if="scope.row.publishStatus == 1" type="success">
已发布
</el-tag>
<el-tag v-if="scope.row.publishStatus == -1" type="warning">
已撤回
</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作时间" width="250"> <el-table-column label="操作时间" width="250">
@@ -126,10 +95,7 @@
<span>发布时间</span> <span>发布时间</span>
<span>{{ scope.row.publishTime || "-" }}</span> <span>{{ scope.row.publishTime || "-" }}</span>
</div> </div>
<div <div v-else-if="scope.row.publishStatus === -1" class="flex-x-start">
v-else-if="scope.row.publishStatus === -1"
class="flex-x-start"
>
<span>撤回时间</span> <span>撤回时间</span>
<span>{{ scope.row.revokeTime || "-" }}</span> <span>{{ scope.row.revokeTime || "-" }}</span>
</div> </div>
@@ -141,7 +107,7 @@
type="primary" type="primary"
size="small" size="small"
link link
@click="openNoticeDetailDialog(scope.row.id)" @click="handleOpenNoticeDetailDialog(scope.row.id)"
> >
查看 查看
</el-button> </el-button>
@@ -151,7 +117,7 @@
type="primary" type="primary"
size="small" size="small"
link link
@click="handlePublishNotice(scope.row.id)" @click="handlePublish(scope.row.id)"
> >
发布 发布
</el-button> </el-button>
@@ -161,7 +127,7 @@
type="primary" type="primary"
size="small" size="small"
link link
@click="revokeNotice(scope.row.id)" @click="handleRevoke(scope.row.id)"
> >
撤回 撤回
</el-button> </el-button>
@@ -206,26 +172,18 @@
width="1250" width="1250"
@close="handleCloseDialog" @close="handleCloseDialog"
> >
<el-form <el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="通知标题" prop="title"> <el-form-item label="通知标题" prop="title">
<el-input v-model="formData.title" placeholder="通知标题" clearable /> <el-input v-model="formData.title" placeholder="通知标题" clearable />
</el-form-item> </el-form-item>
<el-form-item label="通知内容" prop="content"> <el-form-item label="通知内容" prop="content">
<WangEditor <WangEditor v-model="formData.content" style="min-height: 480px; max-height: 500px" />
v-model="formData.content"
style="min-height: 480px; max-height: 500px"
/>
</el-form-item> </el-form-item>
<el-form-item label="通知类型" prop="type"> <el-form-item label="通知类型" prop="type">
<Dict v-model="formData.type" type="button" code="notice_type" /> <Dict v-model="formData.type" code="notice_type" />
</el-form-item> </el-form-item>
<el-form-item label="通知等级" prop="level"> <el-form-item label="通知等级" prop="level">
<Dict v-model="formData.level" type="button" code="notice_level" /> <Dict v-model="formData.level" code="notice_level" />
</el-form-item> </el-form-item>
<el-form-item label="目标类型" prop="targetType"> <el-form-item label="目标类型" prop="targetType">
<el-radio-group v-model="formData.targetType"> <el-radio-group v-model="formData.targetType">
@@ -233,17 +191,8 @@
<el-radio :value="2">指定</el-radio> <el-radio :value="2">指定</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="formData.targetType == 2" label="指定用户" prop="targetUserIds">
v-if="formData.targetType == 2" <el-select v-model="formData.targetUserIds" multiple search placeholder="请选择指定用户">
label="指定用户"
prop="targetUserIds"
>
<el-select
v-model="formData.targetUserIds"
multiple
search
placeholder="请选择指定用户"
>
<el-option <el-option
v-for="item in userOptions" v-for="item in userOptions"
:key="item.value" :key="item.value"
@@ -271,11 +220,7 @@ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}); });
import NoticeAPI, { import NoticeAPI, { NoticePageVO, NoticeForm, NoticePageQuery } from "@/api/system/notice";
NoticePageVO,
NoticeForm,
NoticePageQuery,
} from "@/api/system/notice";
import UserAPI from "@/api/system/user"; import UserAPI from "@/api/system/user";
const queryFormRef = ref(ElForm); const queryFormRef = ref(ElForm);
@@ -283,7 +228,7 @@ const dataFormRef = ref(ElForm);
const noticeDetailRef = ref(); const noticeDetailRef = ref();
const loading = ref(false); const loading = ref(false);
const ids = ref<number[]>([]); const selectIds = ref<number[]>([]);
const total = ref(0); const total = ref(0);
const queryParams = reactive<NoticePageQuery>({ const queryParams = reactive<NoticePageQuery>({
@@ -327,7 +272,7 @@ const rules = reactive({
type: [{ required: true, message: "请选择通知类型", trigger: "change" }], type: [{ required: true, message: "请选择通知类型", trigger: "change" }],
}); });
/** 查询通知公告 */ // 查询通知公告
function handleQuery() { function handleQuery() {
loading.value = true; loading.value = true;
NoticeAPI.getPage(queryParams) NoticeAPI.getPage(queryParams)
@@ -340,19 +285,19 @@ function handleQuery() {
}); });
} }
/** 重置通知公告查询 */ // 重置查询
function handleResetQuery() { function handleResetQuery() {
queryFormRef.value!.resetFields(); queryFormRef.value!.resetFields();
queryParams.pageNum = 1; queryParams.pageNum = 1;
handleQuery(); handleQuery();
} }
/** 行复选框选中记录选中ID集合 */ // 行复选框选中项变化
function handleSelectionChange(selection: any) { function handleSelectionChange(selection: any) {
ids.value = selection.map((item: any) => item.id); selectIds.value = selection.map((item: any) => item.id);
} }
/** 打开通知公告弹窗 */ // 打开通知公告弹窗
function handleOpenDialog(id?: number) { function handleOpenDialog(id?: number) {
UserAPI.getOptions().then((data) => { UserAPI.getOptions().then((data) => {
userOptions.value = data; userOptions.value = data;
@@ -370,21 +315,23 @@ function handleOpenDialog(id?: number) {
} }
} }
function handlePublishNotice(id: number) { // 发布通知公告
function handlePublish(id: number) {
NoticeAPI.publish(id).then(() => { NoticeAPI.publish(id).then(() => {
ElMessage.success("发布成功"); ElMessage.success("发布成功");
handleQuery(); handleQuery();
}); });
} }
function revokeNotice(id: number) { // 撤回通知公告
function handleRevoke(id: number) {
NoticeAPI.revoke(id).then(() => { NoticeAPI.revoke(id).then(() => {
ElMessage.success("撤回成功"); ElMessage.success("撤回成功");
handleQuery(); handleQuery();
}); });
} }
/** 提交通知公告表单 */ // 通知公告表单提交
function handleSubmit() { function handleSubmit() {
dataFormRef.value.validate((valid: any) => { dataFormRef.value.validate((valid: any) => {
if (valid) { if (valid) {
@@ -411,17 +358,23 @@ function handleSubmit() {
}); });
} }
/** 关闭通知公告弹窗 */ // 重置表单
function handleCloseDialog() { function resetForm() {
dialog.visible = false;
dataFormRef.value.resetFields(); dataFormRef.value.resetFields();
dataFormRef.value.clearValidate(); dataFormRef.value.clearValidate();
formData.id = undefined; formData.id = undefined;
formData.targetType = 1;
} }
/** 删除通知公告 */ // 关闭通知公告弹窗
function handleCloseDialog() {
dialog.visible = false;
resetForm();
}
// 删除通知公告
function handleDelete(id?: number) { function handleDelete(id?: number) {
const deleteIds = [id || ids.value].join(","); const deleteIds = [id || selectIds.value].join(",");
if (!deleteIds) { if (!deleteIds) {
ElMessage.warning("请勾选删除项"); ElMessage.warning("请勾选删除项");
return; return;
@@ -447,8 +400,8 @@ function handleDelete(id?: number) {
); );
} }
/** 打开通知公告详情弹窗 */ // 打开通知公告详情弹窗
function openNoticeDetailDialog(id: number) { function handleOpenNoticeDetailDialog(id: number) {
noticeDetailRef.value.openNotice(id); noticeDetailRef.value.openNotice(id);
} }

View File

@@ -12,30 +12,16 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery"> <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<template #icon><Search /></template> <el-button icon="refresh" @click="handleResetQuery">重置</el-button>
搜索
</el-button>
<el-button @click="handleResetQuery">
<template #icon><Refresh /></template>
重置
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-card shadow="never"> <el-card shadow="never">
<div class="mb-10px"> <div class="mb-10px">
<el-button type="success" @click="handleOpenDialog()"> <el-button type="success" icon="plus" @click="handleOpenDialog()">新增</el-button>
<template #icon><Plus /></template> <el-button type="danger" :disabled="ids.length === 0" icon="delete" @click="handleDelete()">
新增
</el-button>
<el-button
type="danger"
:disabled="ids.length === 0"
@click="handleDelete()"
>
<template #icon><Delete /></template>
删除 删除
</el-button> </el-button>
</div> </div>
@@ -110,12 +96,7 @@
width="500px" width="500px"
@close="handleCloseDialog" @close="handleCloseDialog"
> >
<el-form <el-form ref="roleFormRef" :model="formData" :rules="rules" label-width="100px">
ref="roleFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="角色名称" prop="name"> <el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入角色名称" /> <el-input v-model="formData.name" placeholder="请输入角色名称" />
</el-form-item> </el-form-item>
@@ -165,12 +146,7 @@
size="500" size="500"
> >
<div class="flex-x-between"> <div class="flex-x-between">
<el-input <el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单权限名称">
v-model="permKeywords"
clearable
class="w-[150px]"
placeholder="菜单权限名称"
>
<template #prefix> <template #prefix>
<Search /> <Search />
</template> </template>
@@ -195,9 +171,7 @@
<template #content> <template #content>
如果只需勾选菜单权限不需要勾选子菜单或者按钮权限请关闭父子联动 如果只需勾选菜单权限不需要勾选子菜单或者按钮权限请关闭父子联动
</template> </template>
<el-icon <el-icon class="ml-1 color-[--el-color-primary] inline-block cursor-pointer">
class="ml-1 color-[--el-color-primary] inline-block cursor-pointer"
>
<QuestionFilled /> <QuestionFilled />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
@@ -220,9 +194,7 @@
</el-tree> </el-tree>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" @click="handleAssignPermSubmit"> <el-button type="primary" @click="handleAssignPermSubmit"> </el-button>
</el-button>
<el-button @click="assignPermDialogVisible = false"> </el-button> <el-button @click="assignPermDialogVisible = false"> </el-button>
</div> </div>
</template> </template>
@@ -236,11 +208,7 @@ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}); });
import RoleAPI, { import RoleAPI, { RolePageVO, RoleForm, RolePageQuery } from "@/api/system/role";
RolePageVO,
RoleForm,
RolePageQuery,
} from "@/api/system/role";
import MenuAPI from "@/api/system/menu"; import MenuAPI from "@/api/system/menu";
const queryFormRef = ref(ElForm); const queryFormRef = ref(ElForm);
@@ -414,9 +382,7 @@ async function handleOpenAssignPermDialog(row: RolePageVO) {
RoleAPI.getRoleMenuIds(roleId) RoleAPI.getRoleMenuIds(roleId)
.then((data) => { .then((data) => {
const checkedMenuIds = data; const checkedMenuIds = data;
checkedMenuIds.forEach((menuId) => checkedMenuIds.forEach((menuId) => permTreeRef.value!.setChecked(menuId, true, false));
permTreeRef.value!.setChecked(menuId, true, false)
);
}) })
.finally(() => { .finally(() => {
loading.value = false; loading.value = false;