refactor: ♻️ 字典重构,系统权限模块优化

This commit is contained in:
hxr
2024-06-24 08:21:46 +08:00
parent 239989f4aa
commit d5dfcb978e
43 changed files with 1243 additions and 1821 deletions

View File

@@ -5,9 +5,9 @@ VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'
# 线上接口地址
VITE_APP_API_URL = http://vapi.youlai.tech
# VITE_APP_API_URL = http://vapi.youlai.tech
# 开发接口地址
# VITE_APP_API_URL = http://localhost:8989
VITE_APP_API_URL = http://localhost:8989
# 是否启用 Mock 服务
VITE_MOCK_DEV_SERVER = false

View File

@@ -92,7 +92,7 @@ export default defineMock([
},
},
// 新增字典类型
// 新增字典
{
url: "dict/types",
method: ["POST"],
@@ -100,12 +100,12 @@ export default defineMock([
return {
code: "00000",
data: null,
msg: "新增字典类型" + body.name + "成功",
msg: "新增字典" + body.name + "成功",
};
},
},
// 获取字典类型表单数据
// 获取字典表单数据
{
url: "dict/types/:id/form",
method: ["GET"],
@@ -118,7 +118,7 @@ export default defineMock([
},
},
// 修改字典类型
// 修改字典
{
url: "dict/types/:id",
method: ["PUT"],
@@ -126,12 +126,12 @@ export default defineMock([
return {
code: "00000",
data: null,
msg: "修改字典类型" + body.name + "成功",
msg: "修改字典" + body.name + "成功",
};
},
},
// 删除字典类型
// 删除字典
{
url: "dict/types/:id",
method: ["DELETE"],
@@ -139,7 +139,7 @@ export default defineMock([
return {
code: "00000",
data: null,
msg: "删除字典类型" + params.id + "成功",
msg: "删除字典" + params.id + "成功",
};
},
},
@@ -178,7 +178,7 @@ export default defineMock([
return {
code: "00000",
data: null,
msg: "修改字典类型" + body.name + "成功",
msg: "修改字典" + body.name + "成功",
};
},
},
@@ -197,7 +197,7 @@ export default defineMock([
},
]);
// 字典类型映射表数据
// 字典映射表数据
const dictTypeMap: Record<string, any> = {
1: {
id: 1,

View File

@@ -720,7 +720,7 @@ export default defineMock([
{
id: 79,
parentId: 6,
name: "字典类型新增",
name: "字典新增",
type: "BUTTON",
path: "",
component: null,
@@ -734,7 +734,7 @@ export default defineMock([
{
id: 81,
parentId: 6,
name: "字典类型编辑",
name: "字典编辑",
type: "BUTTON",
path: "",
component: null,
@@ -748,7 +748,7 @@ export default defineMock([
{
id: 84,
parentId: 6,
name: "字典类型删除",
name: "字典删除",
type: "BUTTON",
path: "",
component: null,
@@ -1231,15 +1231,15 @@ export default defineMock([
children: [
{
value: 79,
label: "字典类型新增",
label: "字典新增",
},
{
value: 81,
label: "字典类型编辑",
label: "字典编辑",
},
{
value: 84,
label: "字典类型删除",
label: "字典删除",
},
{
value: 85,

72
src/api/auth.ts Normal file
View File

@@ -0,0 +1,72 @@
import request from "@/utils/request";
const AUTH_BASE_URL = "/api/v1/auth";
class AuthAPI {
/** 登录 接口*/
static login(data: LoginData) {
const formData = new FormData();
formData.append("username", data.username);
formData.append("password", data.password);
formData.append("captchaKey", data.captchaKey);
formData.append("captchaCode", data.captchaCode);
return request<any, LoginResult>({
url: `${AUTH_BASE_URL}/login`,
method: "post",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
}
/** 注销 接口*/
static logout() {
return request({
url: `${AUTH_BASE_URL}/logout`,
method: "delete",
});
}
/** 获取验证码 接口*/
static getCaptcha() {
return request<any, CaptchaResult>({
url: `${AUTH_BASE_URL}/captcha`,
method: "get",
});
}
}
export default AuthAPI;
/** 登录请求参数 */
export interface LoginData {
/** 用户名 */
username: string;
/** 密码 */
password: string;
/** 验证码缓存key */
captchaKey: string;
/** 验证码 */
captchaCode: string;
}
/** 登录响应 */
export interface LoginResult {
/** 访问token */
accessToken?: string;
/** 过期时间(单位:毫秒) */
expires?: number;
/** 刷新token */
refreshToken?: string;
/** token 类型 */
tokenType?: string;
}
/** 验证码响应 */
export interface CaptchaResult {
/** 验证码缓存key */
captchaKey: string;
/** 验证码图片Base64字符串 */
captchaBase64: string;
}

View File

@@ -1,54 +0,0 @@
import request from "@/utils/request";
import { CaptchaResult, LoginData, LoginResult } from "./model";
const AUTH_BASE_URL = "/api/v1/auth";
class AuthAPI {
/**
* 登录API
*
* @param data 登录数据
* @returns 登录结果
*/
static login(data: LoginData) {
const formData = new FormData();
formData.append("username", data.username);
formData.append("password", data.password);
formData.append("captchaKey", data.captchaKey || "");
formData.append("captchaCode", data.captchaCode || "");
return request<any, LoginResult>({
url: `${AUTH_BASE_URL}/login`,
method: "post",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
}
/**
* 注销API
*
* @returns 请求结果
*/
static logout() {
return request({
url: `${AUTH_BASE_URL}/logout`,
method: "delete",
});
}
/**
* 获取验证码
*
* @returns 验证码结果
*/
static getCaptcha() {
return request<any, CaptchaResult>({
url: `${AUTH_BASE_URL}/captcha`,
method: "get",
});
}
}
export default AuthAPI;

View File

@@ -1,59 +0,0 @@
/**
* 登录请求参数
*/
export interface LoginData {
/**
* 用户名
*/
username: string;
/**
* 密码
*/
password: string;
/**
* 验证码缓存key
*/
captchaKey?: string;
/**
* 验证码
*/
captchaCode?: string;
}
/**
* 登录响应
*/
export interface LoginResult {
/**
* 访问token
*/
accessToken?: string;
/**
* 过期时间(单位:毫秒)
*/
expires?: number;
/**
* 刷新token
*/
refreshToken?: string;
/**
* token 类型
*/
tokenType?: string;
}
/**
* 验证码响应
*/
export interface CaptchaResult {
/**
* 验证码缓存key
*/
captchaKey: string;
/**
* 验证码图片Base64字符串
*/
captchaBase64: string;
}

View File

@@ -1,11 +1,10 @@
import request from "@/utils/request";
import { DeptForm, DeptQuery, DeptVO } from "./model";
const DEPT_BASE_URL = "/api/v1/dept";
class DeptAPI {
/**
*
*
*
* @param queryParams
* @returns
@@ -18,11 +17,7 @@ class DeptAPI {
});
}
/**
*
*
* @returns
*/
/** 获取部门下拉列表 */
static getOptions() {
return request<any, OptionType[]>({
url: `${DEPT_BASE_URL}/options`,
@@ -87,3 +82,47 @@ class DeptAPI {
}
export default DeptAPI;
/** 部门查询参数 */
export interface DeptQuery {
/** 搜索关键字 */
keywords?: string;
/** 状态 */
status?: number;
}
/** 部门类型 */
export interface DeptVO {
/** 子部门 */
children?: DeptVO[];
/** 创建时间 */
createTime?: Date;
/** 部门ID */
id?: number;
/** 部门名称 */
name?: string;
/** 部门编号 */
code?: string;
/** 父部门ID */
parentId?: number;
/** 排序 */
sort?: number;
/** 状态(1:启用0:禁用) */
status?: number;
/** 修改时间 */
updateTime?: Date;
}
/** 部门表单类型 */
export interface DeptForm {
/** 部门ID(新增不填) */
id?: number;
/** 部门名称 */
name?: string;
/** 父部门ID */
parentId: number;
/** 排序 */
sort?: number;
/** 状态(1:启用0禁用) */
status?: number;
}

View File

@@ -1,71 +0,0 @@
/**
* 部门查询参数
*/
export interface DeptQuery {
keywords?: string;
status?: number;
}
/**
* 部门类型
*/
export interface DeptVO {
/**
* 子部门
*/
children?: DeptVO[];
/**
* 创建时间
*/
createTime?: Date;
/**
* 部门ID
*/
id?: number;
/**
* 部门名称
*/
name?: string;
/**
* 父部门ID
*/
parentId?: number;
/**
* 排序
*/
sort?: number;
/**
* 状态(1:启用0:禁用)
*/
status?: number;
/**
* 修改时间
*/
updateTime?: Date;
}
/**
* 部门表单类型
*/
export interface DeptForm {
/**
* 部门ID(新增不填)
*/
id?: number;
/**
* 部门名称
*/
name?: string;
/**
* 父部门ID
*/
parentId: number;
/**
* 排序
*/
sort?: number;
/**
* 状态(1:启用0禁用)
*/
status?: number;
}

183
src/api/dict.ts Normal file
View File

@@ -0,0 +1,183 @@
import request from "@/utils/request";
const DICT_BASE_URL = "/api/v1/dict";
class DictAPI {
/**
* 获取字典分页列表
*
* @param queryParams 查询参数
* @returns 字典分页结果
*/
static getPage(queryParams: DictPageQuery) {
return request<any, PageResult<DictPageVO[]>>({
url: `${DICT_BASE_URL}/page`,
method: "get",
params: queryParams,
});
}
/**
* 获取字典表单数据
*
* @param id 字典ID
* @returns 字典表单数据
*/
static getFormData(id: number) {
return request<any, ResponseData<DictForm>>({
url: `${DICT_BASE_URL}/${id}/form`,
method: "get",
});
}
/**
* 新增字典
*
* @param data 字典表单数据
* @returns 请求结果
*/
static add(data: DictForm) {
return request({
url: `${DICT_BASE_URL}`,
method: "post",
data: data,
});
}
/**
* 修改字典
*
* @param id 字典ID
* @param data 字典表单数据
* @returns 请求结果
*/
static update(id: number, data: DictForm) {
return request({
url: `${DICT_BASE_URL}/${id}`,
method: "put",
data: data,
});
}
/**
* 删除字典
*
* @param ids 字典ID多个以英文逗号(,)分隔
* @returns 请求结果
*/
static deleteByIds(ids: string) {
return request({
url: `${DICT_BASE_URL}/${ids}`,
method: "delete",
});
}
/**
* 获取字典的数据项
*
* @param typeCode 字典编码
* @returns 字典数据项
*/
static getOptions(code: string) {
return request<any, OptionType[]>({
url: `${DICT_BASE_URL}/${code}/options`,
method: "get",
});
}
}
export default DictAPI;
/**
* 字典查询参数
*/
export interface DictPageQuery extends PageQuery {
/**
* 关键字(字典名称/编码)
*/
keywords?: string;
}
/**
* 字典分页对象
*/
export interface DictPageVO {
/**
* 字典ID
*/
id: number;
/**
* 字典名称
*/
name: string;
/**
* 字典编码
*/
code: string;
/**
* 字典状态1-启用0-禁用)
*/
status: number;
/**
* 字典项列表
*/
dictItems: DictItem[];
}
/**
* 字典项
*/
export interface DictItem {
/**
* 字典项ID
*/
id?: number;
/**
* 字典项名称
*/
name?: string;
/**
* 字典项值
*/
value?: string;
/**
* 排序
*/
sort?: number;
/**
* 状态1-启用0-禁用)
*/
status?: number;
}
// TypeScript 类型声明
/**
* 字典
*/
export interface DictForm {
/**
* 字典ID
*/
id?: number;
/**
* 字典名称
*/
name?: string;
/**
* 字典编码
*/
code?: string;
/**
* 字典状态1-启用0-禁用)
*/
status?: number;
/**
* 备注
*/
remark?: string;
/**
* 字典数据项列表
*/
dictItems?: DictItem[];
}

View File

@@ -1,166 +0,0 @@
import request from "@/utils/request";
import {
DictTypeQuery,
DictTypePageResult,
DictTypeForm,
DictQuery,
DictForm,
DictPageResult,
} from "./model";
const DICT_BASE_URL = "/api/v1/dict";
class DictAPI {
/**
* 获取字典类型分页列表
*
* @param queryParams 查询参数
* @returns 字典类型分页结果
*/
static getDictTypePage(queryParams: DictTypeQuery) {
return request<any, DictTypePageResult>({
url: `${DICT_BASE_URL}/types/page`,
method: "get",
params: queryParams,
});
}
/**
* 获取字典类型表单数据
*
* @param id 字典类型ID
* @returns 字典类型表单数据
*/
static getDictTypeForm(id: number) {
return request<any, ResponseData<DictTypeForm>>({
url: `${DICT_BASE_URL}/types/${id}/form`,
method: "get",
});
}
/**
* 新增字典类型
*
* @param data 字典类型表单数据
* @returns 请求结果
*/
static addDictType(data: DictTypeForm) {
return request({
url: `${DICT_BASE_URL}/types`,
method: "post",
data: data,
});
}
/**
* 修改字典类型
*
* @param id 字典类型ID
* @param data 字典类型表单数据
* @returns 请求结果
*/
static updateDictType(id: number, data: DictTypeForm) {
return request({
url: `${DICT_BASE_URL}/types/${id}`,
method: "put",
data: data,
});
}
/**
* 删除字典类型
*
* @param ids 字典类型ID多个以英文逗号(,)分隔
* @returns 请求结果
*/
static deleteDictTypes(ids: string) {
return request({
url: `${DICT_BASE_URL}/types/${ids}`,
method: "delete",
});
}
/**
* 获取字典类型的数据项
*
* @param typeCode 字典类型编码
* @returns 字典类型的数据项
*/
static getDictOptions(typeCode: string) {
return request<any, OptionType[]>({
url: `${DICT_BASE_URL}/${typeCode}/options`,
method: "get",
});
}
/**
* 获取字典分页列表
*
* @param queryParams 查询参数
* @returns 字典分页结果
*/
static getDictPage(queryParams: DictQuery) {
return request<any, DictPageResult>({
url: `${DICT_BASE_URL}/page`,
method: "get",
params: queryParams,
});
}
/**
* 获取字典表单数据
*
* @param id 字典项ID
* @returns 字典表单数据
*/
static getDictFormData(id: number) {
return request<any, DictForm>({
url: `${DICT_BASE_URL}/${id}/form`,
method: "get",
});
}
/**
* 新增字典
*
* @param data 字典表单数据
* @returns 请求结果
*/
static addDict(data: DictForm) {
return request({
url: `${DICT_BASE_URL}`,
method: "post",
data: data,
});
}
/**
* 修改字典项
*
* @param id 字典项ID
* @param data 字典表单数据
* @returns 请求结果
*/
static updateDict(id: number, data: DictForm) {
return request({
url: `${DICT_BASE_URL}/${id}`,
method: "put",
data: data,
});
}
/**
* 删除字典
*
* @param ids 字典项ID多个以英文逗号(,)分隔
* @returns 请求结果
*/
static deleteDictByIds(ids: string) {
return request({
url: `${DICT_BASE_URL}/${ids}`,
method: "delete",
});
}
}
export default DictAPI;

View File

@@ -1,142 +0,0 @@
/**
* 字典类型查询参数
*/
export interface DictTypeQuery extends PageQuery {
/**
* 关键字(字典类型名称/编码)
*/
keywords?: string;
}
/**
* 字典类型分页对象
*/
export interface DictTypePageVO {
/**
* 字典类型ID
*/
id: number;
/**
* 类型编码
*/
code: string;
/**
* 类型名称
*/
name: string;
/**
* 状态(1:启用;0:禁用)
*/
status?: number;
/**
* 备注
*/
remark?: string;
}
/**
* 字典分页项类型声明
*/
export type DictTypePageResult = PageResult<DictTypePageVO[]>;
/**
* 字典表单类型声明
*/
export interface DictTypeForm {
/**
* 字典类型ID
*/
id?: number;
/**
* 类型名称
*/
name?: string;
/**
* 类型编码
*/
code?: string;
/**
* 类型状态1:启用;0:禁用
*/
status: number;
/**
* 备注
*/
remark?: string;
}
/**
* 字典查询参数
*/
export interface DictQuery extends PageQuery {
/**
* 字典项名称
*/
name?: string;
/**
* 字典类型编码
*/
typeCode?: string;
}
/**
* 字典分页对象
*/
export interface DictPageVO {
/**
* 字典ID
*/
id?: number;
/**
* 字典名称
*/
name?: string;
/**
* 状态(1:启用;0:禁用)
*/
status?: number;
/**
* 字典值
*/
value?: string;
}
/**
* 字典分页
*/
export type DictPageResult = PageResult<DictPageVO[]>;
/**
* 字典表单
*/
export interface DictForm {
/**
* 字典ID
*/
id?: number;
/**
* 字典名称
*/
name?: string;
/**
* 排序
*/
sort?: number;
/**
* 状态(1:启用;0:禁用)
*/
status?: number;
/**
* 类型编码
*/
typeCode?: string;
/**
* 值
*/
value?: string;
/**
* 备注
*/
remark?: string;
}

View File

@@ -1,5 +1,4 @@
import request from "@/utils/request";
import { FileInfo } from "./model";
class FileAPI {
/**
@@ -35,3 +34,13 @@ class FileAPI {
}
export default FileAPI;
/**
* API类型声明
*/
export interface FileInfo {
/** 文件名 */
name: string;
/** 文件路径 */
url: string;
}

View File

@@ -1,7 +0,0 @@
/**
* 文件API类型声明
*/
export interface FileInfo {
name: string;
url: string;
}

209
src/api/menu.ts Normal file
View File

@@ -0,0 +1,209 @@
import request from "@/utils/request";
// 菜单基础URL
const MENU_BASE_URL = "/api/v1/menus";
class MenuAPI {
/**
* 获取路由列表
*
* @returns 路由列表
*/
static getRoutes() {
return request<any, RouteVO[]>({
url: `${MENU_BASE_URL}/routes`,
method: "get",
});
}
/**
* 获取菜单树形列表
*
* @param queryParams 查询参数
* @returns 菜单树形列表
*/
static getList(queryParams: MenuQuery) {
return request<any, MenuVO[]>({
url: `${MENU_BASE_URL}`,
method: "get",
params: queryParams,
});
}
/**
* 获取菜单下拉数据源
*
* @returns 菜单下拉数据源
*/
static getOptions() {
return request<any, OptionType[]>({
url: `${MENU_BASE_URL}/options`,
method: "get",
});
}
/**
* 获取菜单表单数据
*
* @param id 菜单ID
* @returns 菜单表单数据
*/
static getFormData(id: number) {
return request<any, MenuForm>({
url: `${MENU_BASE_URL}/${id}/form`,
method: "get",
});
}
/**
* 添加菜单
*
* @param data 菜单表单数据
* @returns 请求结果
*/
static add(data: MenuForm) {
return request({
url: `${MENU_BASE_URL}`,
method: "post",
data: data,
});
}
/**
* 修改菜单
*
* @param id 菜单ID
* @param data 菜单表单数据
* @returns 请求结果
*/
static update(id: string, data: MenuForm) {
return request({
url: `${MENU_BASE_URL}/${id}`,
method: "put",
data: data,
});
}
/**
* 删除菜单
*
* @param id 菜单ID
* @returns 请求结果
*/
static deleteById(id: number) {
return request({
url: `${MENU_BASE_URL}/${id}`,
method: "delete",
});
}
}
export default MenuAPI;
import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
/** 菜单查询参数 */
export interface MenuQuery {
/** 搜索关键字 */
keywords?: string;
}
/** 菜单视图对象 */
export interface MenuVO {
/** 子菜单 */
children?: MenuVO[];
/** 组件路径 */
component?: string;
/** ICON */
icon?: string;
/** 菜单ID */
id?: number;
/** 菜单名称 */
name?: string;
/** 父菜单ID */
parentId?: number;
/** 按钮权限标识 */
perm?: string;
/** 跳转路径 */
redirect?: string;
/** 路由名称 */
routeName?: string;
/** 路由相对路径 */
routePath?: string;
/** 菜单排序(数字越小排名越靠前) */
sort?: number;
/** 菜单 */
type?: MenuTypeEnum;
/** 菜单是否可见(1:显示;0:隐藏) */
visible?: number;
}
/** 菜单表单对象 */
export interface MenuForm {
/** 菜单ID */
id?: string;
/** 父菜单ID */
parentId?: number;
/** 菜单名称 */
name?: string;
/** 菜单是否可见(1-是 0-否) */
visible: number;
/** ICON */
icon?: string;
/** 排序 */
sort?: number;
/** 路由名称 */
routeName?: string;
/** 路由路径 */
routePath?: string;
/** 组件路径 */
component?: string;
/** 跳转路由路径 */
redirect?: string;
/** 菜单 */
type?: MenuTypeEnum;
/** 权限标识 */
perm?: string;
/** 【菜单】是否开启页面缓存 */
keepAlive?: number;
/** 【目录】只有一个子路由是否始终显示 */
alwaysShow?: number;
/** 参数 */
params?: KeyValue[];
}
interface KeyValue {
key: string;
value: string;
}
/** RouteVO路由对象 */
export interface RouteVO {
/** 子路由列表 */
children: RouteVO[];
/** 组件路径 */
component?: string;
/** 路由属性 */
meta?: Meta;
/** 路由名称 */
name?: string;
/** 路由路径 */
path?: string;
/** 跳转链接 */
redirect?: string;
}
/** Meta路由属性 */
export interface Meta {
/** 【目录】只有一个子路由是否始终显示 */
alwaysShow?: boolean;
/** 是否隐藏(true-是 false-否) */
hidden?: boolean;
/** ICON */
icon?: string;
/** 【菜单】是否开启页面缓存 */
keepAlive?: boolean;
/** 拥有路由权限的角色编码 */
roles?: string[];
/** 路由title */
title?: string;
}

View File

@@ -1,101 +0,0 @@
import request from "@/utils/request";
import { MenuQuery, MenuVO, MenuForm, RouteVO } from "./model";
const MENU_BASE_URL = "/api/v1/menus";
class MenuAPI {
/**
* 获取路由列表
*
* @returns 路由列表
*/
static getRoutes() {
return request<any, RouteVO[]>({
url: `${MENU_BASE_URL}/routes`,
method: "get",
});
}
/**
* 获取菜单树形列表
*
* @param queryParams 查询参数
* @returns 菜单树形列表
*/
static getList(queryParams: MenuQuery) {
return request<any, MenuVO[]>({
url: `${MENU_BASE_URL}`,
method: "get",
params: queryParams,
});
}
/**
* 获取菜单下拉数据源
*
* @returns 菜单下拉数据源
*/
static getOptions() {
return request<any, OptionType[]>({
url: `${MENU_BASE_URL}/options`,
method: "get",
});
}
/**
* 获取菜单表单数据
*
* @param id 菜单ID
* @returns 菜单表单数据
*/
static getFormData(id: number) {
return request<any, MenuForm>({
url: `${MENU_BASE_URL}/${id}/form`,
method: "get",
});
}
/**
* 添加菜单
*
* @param data 菜单表单数据
* @returns 请求结果
*/
static add(data: MenuForm) {
return request({
url: `${MENU_BASE_URL}`,
method: "post",
data: data,
});
}
/**
* 修改菜单
*
* @param id 菜单ID
* @param data 菜单表单数据
* @returns 请求结果
*/
static update(id: string, data: MenuForm) {
return request({
url: `${MENU_BASE_URL}/${id}`,
method: "put",
data: data,
});
}
/**
* 删除菜单
*
* @param id 菜单ID
* @returns 请求结果
*/
static deleteById(id: number) {
return request({
url: `${MENU_BASE_URL}/${id}`,
method: "delete",
});
}
}
export default MenuAPI;

View File

@@ -1,188 +0,0 @@
import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
/**
* 菜单查询参数类型
*/
export interface MenuQuery {
keywords?: string;
}
/**
* 菜单视图对象类型
*/
export interface MenuVO {
/**
* 子菜单
*/
children?: MenuVO[];
/**
* 组件路径
*/
component?: string;
/**
* ICON
*/
icon?: string;
/**
* 菜单ID
*/
id?: number;
/**
* 菜单名称
*/
name?: string;
/**
* 父菜单ID
*/
parentId?: number;
/**
* 按钮权限标识
*/
perm?: string;
/**
* 跳转路径
*/
redirect?: string;
/**
* 路由名称
*/
routeName?: string;
/**
* 路由相对路径
*/
routePath?: string;
/**
* 菜单排序(数字越小排名越靠前)
*/
sort?: number;
/**
* 菜单类型
*/
type?: MenuTypeEnum;
/**
* 菜单是否可见(1:显示;0:隐藏)
*/
visible?: number;
}
/**
* 菜单表单对象类型
*/
export interface MenuForm {
/**
* 菜单ID
*/
id?: string;
/**
* 父菜单ID
*/
parentId?: number;
/**
* 菜单名称
*/
name?: string;
/**
* 菜单是否可见(1:是;0:否;)
*/
visible: number;
icon?: string;
/**
* 排序
*/
sort?: number;
/**
* 组件路径
*/
component?: string;
/**
* 路由路径
*/
path?: string;
/**
* 跳转路由路径
*/
redirect?: string;
/**
* 菜单类型
*/
type?: MenuTypeEnum;
/**
* 权限标识
*/
perm?: string;
/**
* 【菜单】是否开启页面缓存
*/
keepAlive?: number;
/**
* 【目录】只有一个子路由是否始终显示
*/
alwaysShow?: number;
params?: KeyValue[];
}
interface KeyValue {
key: string;
value: string;
}
/**
* RouteVO路由对象
*/
export interface RouteVO {
/**
* 子路由列表
*/
children: RouteVO[];
/**
* 组件路径
*/
component?: string;
meta?: Meta;
/**
* 路由名称
*/
name?: string;
/**
* 路由路径
*/
path?: string;
/**
* 跳转链接
*/
redirect?: string;
}
/**
* Meta路由属性类型
*/
export interface Meta {
/**
* 【目录】只有一个子路由是否始终显示
*/
alwaysShow?: boolean;
/**
* 是否隐藏(true-是 false-否)
*/
hidden?: boolean;
/**
* ICON
*/
icon?: string;
/**
* 【菜单】是否开启页面缓存
*/
keepAlive?: boolean;
/**
* 拥有路由权限的角色编码
*/
roles?: string[];
/**
* 路由title
*/
title?: string;
}

View File

@@ -1,34 +1,22 @@
import request from "@/utils/request";
import { RoleQuery, RolePageResult, RoleForm } from "./model";
const ROLE_BASE_URL = "/api/v1/roles";
class RoleAPI {
/**
*
*
* @param queryParams
* @returns
*/
static getPage(queryParams?: RoleQuery) {
return request<any, RolePageResult>({
/** 获取角色分页数据 */
static getPage(queryParams?: RolePageQuery) {
return request<any, PageResult<RolePageVO[]>>({
url: `${ROLE_BASE_URL}/page`,
method: "get",
params: queryParams,
});
}
/**
*
*
* @param queryParams
* @returns
*/
static getOptions(queryParams?: RoleQuery) {
/** 获取角色下拉数据源 */
static getOptions() {
return request<any, OptionType[]>({
url: `${ROLE_BASE_URL}/options`,
method: "get",
params: queryParams,
});
}
@@ -46,7 +34,7 @@ class RoleAPI {
}
/**
*
*
*
* @param roleId ID
* @param data ID集合
@@ -73,12 +61,7 @@ class RoleAPI {
});
}
/**
*
*
* @param data
* @returns
*/
/** 添加角色 */
static add(data: RoleForm) {
return request({
url: `${ROLE_BASE_URL}`,
@@ -92,7 +75,6 @@ class RoleAPI {
*
* @param id ID
* @param data
* @returns
*/
static update(id: number, data: RoleForm) {
return request({
@@ -106,7 +88,6 @@ class RoleAPI {
* (,)
*
* @param ids ID字符串(,)
* @returns
*/
static deleteByIds(ids: string) {
return request({
@@ -117,3 +98,43 @@ class RoleAPI {
}
export default RoleAPI;
/** 角色分页查询参数 */
export interface RolePageQuery extends PageQuery {
/** 搜索关键字 */
keywords?: string;
}
/** 角色分页对象 */
export interface RolePageVO {
/** 角色编码 */
code?: string;
/** 角色ID */
id?: number;
/** 角色名称 */
name?: string;
/** 排序 */
sort?: number;
/** 角色状态 */
status?: number;
/** 创建时间 */
createTime?: Date;
/** 修改时间 */
updateTime?: Date;
}
/** 角色表单对象 */
export interface RoleForm {
/** 角色ID */
id?: number;
/** 角色编码 */
code: string;
/** 数据权限 */
dataScope?: number;
/** 角色名称 */
name: string;
/** 排序 */
sort?: number;
/** 角色状态(1-正常0-停用) */
status?: number;
}

View File

@@ -1,78 +0,0 @@
/**
* 角色查询参数
*/
export interface RoleQuery extends PageQuery {
keywords?: string;
}
/**
* 角色分页对象
*/
export interface RolePageVO {
/**
* 角色编码
*/
code?: string;
/**
* 角色ID
*/
id?: number;
/**
* 角色名称
*/
name?: string;
/**
* 排序
*/
sort?: number;
/**
* 角色状态
*/
status?: number;
/**
* 创建时间
*/
createTime?: Date;
/**
* 修改时间
*/
updateTime?: Date;
}
/**
* 角色分页
*/
export type RolePageResult = PageResult<RolePageVO[]>;
/**
* 角色表单对象
*/
export interface RoleForm {
/**
* 角色ID
*/
id?: number;
/**
* 角色编码
*/
code: string;
/**
* 数据权限
*/
dataScope?: number;
/**
* 角色名称
*/
name: string;
/**
* 排序
*/
sort?: number;
/**
* 角色状态(1-正常0-停用)
*/
status?: number;
}

View File

@@ -1,13 +1,12 @@
import request from "@/utils/request";
import { UserForm, UserInfo, UserPageVO, UserQuery } from "./model";
const USER_BASE_URL = "/api/v1/users";
class UserAPI {
/**
*
*
*
* @returns
* @returns
*/
static getInfo() {
return request<any, UserInfo>({
@@ -20,9 +19,8 @@ class UserAPI {
*
*
* @param queryParams
* @returns
*/
static getPage(queryParams: UserQuery) {
static getPage(queryParams: UserPageQuery) {
return request<any, PageResult<UserPageVO[]>>({
url: `${USER_BASE_URL}/page`,
method: "get",
@@ -47,7 +45,6 @@ class UserAPI {
*
*
* @param data
* @returns
*/
static add(data: UserForm) {
return request({
@@ -62,7 +59,6 @@ class UserAPI {
*
* @param id ID
* @param data
* @returns
*/
static update(id: number, data: UserForm) {
return request({
@@ -77,7 +73,6 @@ class UserAPI {
*
* @param id ID
* @param password
* @returns
*/
static updatePassword(id: number, password: string) {
return request({
@@ -91,7 +86,6 @@ class UserAPI {
* (,)
*
* @param ids ID字符串(,)
* @returns
*/
static deleteByIds(ids: string) {
return request({
@@ -100,11 +94,7 @@ class UserAPI {
});
}
/**
*
*
* @returns
*/
/** 下载用户导入模板 */
static downloadTemplate() {
return request({
url: `${USER_BASE_URL}/template`,
@@ -117,9 +107,8 @@ class UserAPI {
*
*
* @param queryParams
* @returns
*/
static export(queryParams: UserQuery) {
static export(queryParams: UserPageQuery) {
return request({
url: `${USER_BASE_URL}/export`,
method: "get",
@@ -133,7 +122,6 @@ class UserAPI {
*
* @param deptId ID
* @param file
* @returns
*/
static import(deptId: number, file: File) {
const formData = new FormData();
@@ -151,3 +139,94 @@ class UserAPI {
}
export default UserAPI;
/** 登录用户信息 */
export interface UserInfo {
/** 用户ID */
userId?: number;
/** 用户名 */
username?: string;
/** 昵称 */
nickname?: string;
/** 头像URL */
avatar?: string;
/** 角色 */
roles: string[];
/** 权限 */
perms: string[];
}
/**
*
*/
export interface UserPageQuery extends PageQuery {
/** 搜索关键字 */
keywords?: string;
/** 用户状态 */
status?: number;
/** 部门ID */
deptId?: number;
/** 开始时间 */
startTime?: string;
/** 结束时间 */
endTime?: string;
}
/** 用户分页对象 */
export interface UserPageVO {
/** 用户头像URL */
avatar?: string;
/** 创建时间 */
createTime?: Date;
/** 部门名称 */
deptName?: string;
/** 用户邮箱 */
email?: string;
/** 性别 */
genderLabel?: string;
/** 用户ID */
id?: number;
/** 手机号 */
mobile?: string;
/** 用户昵称 */
nickname?: string;
/** 角色名称,多个使用英文逗号(,)分割 */
roleNames?: string;
/** 用户状态(1:启用;0:禁用) */
status?: number;
/** 用户名 */
username?: string;
}
/** 用户表单类型 */
export interface UserForm {
/** 用户头像 */
avatar?: string;
/** 部门ID */
deptId?: number;
/** 邮箱 */
email?: string;
/** 性别 */
gender?: number;
/** 用户ID */
id?: number;
/** 手机号 */
mobile?: string;
/** 昵称 */
nickname?: string;
/** 角色ID集合 */
roleIds?: number[];
/** 用户状态(1:正常;0:禁用) */
status?: number;
/** 用户名 */
username?: string;
}

View File

@@ -1,115 +0,0 @@
/**
* 登录用户信息
*/
export interface UserInfo {
userId?: number;
username?: string;
nickname?: string;
avatar?: string;
roles: string[];
perms: string[];
}
/**
* 用户查询对象类型
*/
export interface UserQuery extends PageQuery {
keywords?: string;
status?: number;
deptId?: number;
startTime?: string;
endTime?: string;
}
/**
* 用户分页对象
*/
export interface UserPageVO {
/**
* 用户头像地址
*/
avatar?: string;
/**
* 创建时间
*/
createTime?: Date;
/**
* 部门名称
*/
deptName?: string;
/**
* 用户邮箱
*/
email?: string;
/**
* 性别
*/
genderLabel?: string;
/**
* 用户ID
*/
id?: number;
/**
* 手机号
*/
mobile?: string;
/**
* 用户昵称
*/
nickname?: string;
/**
* 角色名称,多个使用英文逗号(,)分割
*/
roleNames?: string;
/**
* 用户状态(1:启用;0:禁用)
*/
status?: number;
/**
* 用户名
*/
username?: string;
}
/**
* 用户表单类型
*/
export interface UserForm {
/**
* 用户头像
*/
avatar?: string;
/**
* 部门ID
*/
deptId?: number;
/**
* 邮箱
*/
email?: string;
/**
* 性别
*/
gender?: number;
/**
* 用户ID
*/
id?: number;
mobile?: string;
/**
* 昵称
*/
nickname?: string;
/**
* 角色ID集合
*/
roleIds?: number[];
/**
* 用户状态(1:正常;0:禁用)
*/
status?: number;
/**
* 用户名
*/
username?: string;
}

View File

@@ -20,9 +20,9 @@ import DictAPI from "@/api/dict";
const props = defineProps({
/**
* 字典类型编码(eg: 性别-gender)
* 字典编码(eg: 性别-gender)
*/
typeCode: {
code: {
type: String,
required: true,
},
@@ -39,14 +39,17 @@ const props = defineProps({
},
});
const emits = defineEmits(["update:modelValue"]); // 父组件监听事件,同步子组件值的变化给父组件
const emits = defineEmits(["update:modelValue"]);
const options: Ref<OptionType[]> = ref([]); // 字典下拉数据源
const options: Ref<OptionType[]> = ref([]);
const selectedValue = ref<string | number | undefined>();
watch([options, () => props.modelValue], ([newOptions, newModelValue]) => {
if (newOptions.length === 0) return; // 下拉数据源加载未完成不回显
if (newOptions.length === 0) {
// 下拉数据源加载未完成不回显
return;
}
if (newModelValue == undefined) {
selectedValue.value = undefined;
return;
@@ -65,8 +68,8 @@ function handleChange(val?: string | number | undefined) {
}
onBeforeMount(() => {
// 根据字典类型编码(typeCode)获取字典
DictAPI.getDictOptions(props.typeCode).then((data) => {
// 根据字典编码获取字典项
DictAPI.getOptions(props.code).then((data) => {
options.value = data;
});
});

View File

@@ -2,7 +2,7 @@ import { RouteRecordRaw } from "vue-router";
import { constantRoutes } from "@/router";
import { store } from "@/store";
import MenuAPI from "@/api/menu";
import { RouteVO } from "@/api/menu/model";
import { RouteVO } from "@/api/menu/types";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layout/index.vue");

View File

@@ -3,8 +3,8 @@ import UserAPI from "@/api/user";
import { resetRouter } from "@/router";
import { store } from "@/store";
import { LoginData } from "@/api/auth/model";
import { UserInfo } from "@/api/user/model";
import { LoginData } from "@/api/auth";
import { UserInfo } from "@/api/user";
import { TOKEN_KEY } from "@/enums/CacheEnum";
export const useUserStore = defineStore("user", () => {

View File

@@ -13,6 +13,7 @@ declare module "vue" {
Breadcrumb: (typeof import("./../components/Breadcrumb/index.vue"))["default"];
CURD: (typeof import("./../components/CURD/index.vue"))["default"];
DeptTree: (typeof import("./../views/system/user/components/dept-tree.vue"))["default"];
UserImport: (typeof import("./../views/system/user/components/user-import.vue"))["default"];
Dictionary: (typeof import("./../components/Dictionary/index.vue"))["default"];
DictItem: (typeof import("./../views/system/dict/components/dict-item.vue"))["default"];
ElBacktop: (typeof import("element-plus/es"))["ElBacktop"];

View File

@@ -1,7 +1,6 @@
import DeptAPI from "@/api/dept";
import RoleAPI from "@/api/role";
import UserAPI from "@/api/user";
import type { UserForm } from "@/api/user/model";
import UserAPI, { UserForm } from "@/api/user";
import type { IModalConfig } from "@/components/CURD/types";
const modalConfig: IModalConfig<UserForm> = {

View File

@@ -1,9 +1,9 @@
import UserAPI from "@/api/user";
import RoleAPI from "@/api/role";
import type { UserQuery } from "@/api/user/model";
import type { UserPageQuery } from "@/api/user";
import type { IContentConfig } from "@/components/CURD/types";
const contentConfig: IContentConfig<UserQuery> = {
const contentConfig: IContentConfig<UserPageQuery> = {
pageName: "sys:user",
table: {
border: true,

View File

@@ -1,7 +1,7 @@
import DeptAPI from "@/api/dept";
import RoleAPI from "@/api/role";
import UserAPI from "@/api/user";
import type { UserForm } from "@/api/user/model";
import type { UserForm } from "@/api/user";
import type { IModalConfig } from "@/components/CURD/types";
import { DeviceEnum } from "@/enums/DeviceEnum";
import { useAppStore } from "@/store";

View File

@@ -49,7 +49,7 @@
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<dictionary v-model="scope.formData[scope.prop]" type-code="gender" />
<dictionary v-model="scope.formData[scope.prop]" code="gender" />
</template>
</page-modal>
@@ -60,7 +60,7 @@
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<dictionary v-model="scope.formData[scope.prop]" type-code="gender" />
<dictionary v-model="scope.formData[scope.prop]" code="gender" />
</template>
</page-modal>
</template>

View File

@@ -15,14 +15,14 @@ const numberValue = ref(1); // 性别(值为Number)
>
<el-form>
<el-form-item label="性别">
<dictionary v-model="stringValue" type-code="gender" />
<dictionary v-model="stringValue" code="gender" />
<el-link :underline="false" type="primary" class="ml-5"
>值为String: const value = ref("1");
</el-link>
</el-form-item>
<el-form-item label="性别">
<dictionary v-model="numberValue" type-code="gender" />
<dictionary v-model="numberValue" code="gender" />
<el-link :underline="false" type="success" class="ml-5"
>值为Number: const value = ref(1);
</el-link>

View File

@@ -116,7 +116,7 @@
<script setup lang="ts">
import { useSettingsStore, useUserStore } from "@/store";
import AuthAPI from "@/api/auth";
import { LoginData } from "@/api/auth/model";
import { LoginData } from "@/api/auth/types";
import type { FormInstance } from "element-plus";
import { LocationQuery, useRoute } from "vue-router";
import router from "@/router";

View File

@@ -38,7 +38,7 @@
<el-button
v-hasPerm="['sys:dept:add']"
type="success"
@click="openDialog(0, undefined)"
@click="handleOpenDialog(0, undefined)"
><i-ep-plus />新增</el-button
>
<el-button
@@ -60,6 +60,7 @@
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="name" label="部门名称" min-width="200" />
<el-table-column prop="code" label="部门编号" width="200" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
@@ -76,7 +77,7 @@
type="primary"
link
size="small"
@click.stop="openDialog(scope.row.id, undefined)"
@click.stop="handleOpenDialog(scope.row.id, undefined)"
><i-ep-plus />新增
</el-button>
<el-button
@@ -84,7 +85,7 @@
type="primary"
link
size="small"
@click.stop="openDialog(scope.row.parentId, scope.row.id)"
@click.stop="handleOpenDialog(scope.row.parentId, scope.row.id)"
><i-ep-edit />编辑
</el-button>
<el-button
@@ -105,7 +106,7 @@
v-model="dialog.visible"
:title="dialog.title"
width="600px"
@closed="closeDialog"
@closed="handleCloseDialog"
>
<el-form
ref="deptFormRef"
@@ -126,6 +127,9 @@
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="部门编号" prop="code">
<el-input v-model="formData.code" placeholder="请输入部门编号" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number
v-model="formData.sort"
@@ -145,7 +149,7 @@
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="closeDialog"> </el-button>
<el-button @click="handleCloseDialog"> </el-button>
</div>
</template>
</el-dialog>
@@ -158,8 +162,7 @@ defineOptions({
inheritAttrs: false,
});
import DeptAPI from "@/api/dept";
import { DeptVO, DeptForm, DeptQuery } from "@/api/dept/model";
import DeptAPI, { DeptVO, DeptForm, DeptQuery } from "@/api/dept";
const queryFormRef = ref(ElForm);
const deptFormRef = ref(ElForm);
@@ -183,12 +186,15 @@ const formData = reactive<DeptForm>({
});
const rules = reactive({
parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }],
parentId: [
{ required: true, message: "上级部门不能为空", trigger: "change" },
],
name: [{ required: true, message: "部门名称不能为空", trigger: "blur" }],
code: [{ required: true, message: "部门编号不能为空", trigger: "blur" }],
sort: [{ required: true, message: "显示排序不能为空", trigger: "blur" }],
});
/** 查询 */
/** 查询部门 */
function handleQuery() {
loading.value = true;
DeptAPI.getList(queryParams).then((data) => {
@@ -197,7 +203,7 @@ function handleQuery() {
});
}
/**重置查询 */
/** 重置查询 */
function handleResetQuery() {
queryFormRef.value.resetFields();
handleQuery();
@@ -208,9 +214,15 @@ function handleSelectionChange(selection: any) {
ids.value = selection.map((item: any) => item.id);
}
/** 获取部门下拉数据 */
async function loadDeptOptions() {
DeptAPI.getOptions().then((data) => {
/**
* 打开部门弹窗
*
* @param parentId 父部门ID
* @param deptId 部门ID
*/
async function handleOpenDialog(parentId?: number, deptId?: number) {
// 加载部门下拉数据
const data = await DeptAPI.getOptions();
deptOptions.value = [
{
value: 0,
@@ -218,17 +230,7 @@ async function loadDeptOptions() {
children: data,
},
];
});
}
/**
* 打开弹窗
*
* @param parentId 父部门ID
* @param deptId 部门ID
*/
async function openDialog(parentId?: number, deptId?: number) {
await loadDeptOptions();
dialog.visible = true;
if (deptId) {
dialog.title = "修改部门";
@@ -241,17 +243,17 @@ async function openDialog(parentId?: number, deptId?: number) {
}
}
/** 表单提交 */
/** 提交部门表单 */
function handleSubmit() {
deptFormRef.value.validate((valid: any) => {
if (valid) {
const deptId = formData.id;
loading.value = true;
const deptId = formData.id;
if (deptId) {
DeptAPI.update(deptId, formData)
.then(() => {
ElMessage.success("修改成功");
closeDialog();
handleCloseDialog();
handleQuery();
})
.finally(() => (loading.value = false));
@@ -259,7 +261,7 @@ function handleSubmit() {
DeptAPI.add(formData)
.then(() => {
ElMessage.success("新增成功");
closeDialog();
handleCloseDialog();
handleQuery();
})
.finally(() => (loading.value = false));
@@ -290,13 +292,9 @@ function handleDelete(deptId?: number) {
}
/** 关闭弹窗 */
function closeDialog() {
function handleCloseDialog() {
dialog.visible = false;
resetForm();
}
/** 重置表单 */
function resetForm() {
deptFormRef.value.resetFields();
deptFormRef.value.clearValidate();

View File

@@ -1,308 +0,0 @@
<!-- 字典数据 -->
<template>
<div class="app-container">
<div class="search-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="关键字" prop="name">
<el-input
v-model="queryParams.name"
placeholder="字典名称"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"
><i-ep-search />搜索</el-button
>
<el-button @click="handleResetQuery"> <i-ep-refresh />重置</el-button>
</el-form-item>
</el-form>
</div>
<el-card shadow="never">
<template #header>
<el-button
v-hasPerm="['sys:dict:add']"
type="success"
@click="openDialog()"
><i-ep-plus />新增</el-button
>
<el-button
v-hasPerm="['sys:dict:delete']"
type="danger"
:disabled="ids.length === 0"
@click="handleDelete()"
><i-ep-delete />删除</el-button
>
</template>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="dictList"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" />
<el-table-column label="字典名称" prop="name" />
<el-table-column label="字典值" prop="value" />
<el-table-column label="状态" align="center">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">启用</el-tag>
<el-tag v-else type="info">禁用</el-tag>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" align="center">
<template #default="scope">
<el-button
v-hasPerm="['sys:dict:edit']"
type="primary"
link
@click="openDialog(scope.row.id)"
><i-ep-edit />编辑</el-button
>
<el-button
v-hasPerm="['sys:dict:delete']"
type="primary"
link
@click.stop="handleDelete(scope.row.id)"
><i-ep-delete />删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
</el-card>
<!-- 表单弹窗 -->
<el-dialog
v-model="dialog.visible"
:title="dialog.title"
width="500px"
@close="closeDialog"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="字典名称">{{ typeName }}</el-form-item>
<el-form-item label="字典名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典值" prop="value">
<el-input v-model="formData.value" placeholder="字典值" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="formData.sort"
controls-position="right"
:min="0"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="closeDialog"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "DictData",
inheritAttrs: false,
});
import DictAPI from "@/api/dict";
import { DictPageVO, DictForm, DictQuery } from "@/api/dict/model";
const props = defineProps({
typeCode: {
type: String,
default: () => {
return "";
},
},
typeName: {
type: String,
default: () => {
return "";
},
},
});
watch(
() => props.typeCode,
(newVal: string) => {
queryParams.typeCode = newVal;
formData.typeCode = newVal;
handleResetQuery();
}
);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const loading = ref(false);
const ids = ref<number[]>([]);
const total = ref(0);
const queryParams = reactive<DictQuery>({
pageNum: 1,
pageSize: 10,
typeCode: props.typeCode,
});
const dictList = ref<DictPageVO[]>();
const dialog = reactive({
title: "",
visible: false,
});
const formData = reactive<DictForm>({
status: 1,
typeCode: props.typeCode,
sort: 1,
});
const rules = reactive({
name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
});
/** 查询 */
function handleQuery() {
if (queryParams.typeCode) {
loading.value = true;
DictAPI.getDictPage(queryParams)
.then((data) => {
dictList.value = data.list;
total.value = data.total;
})
.finally(() => (loading.value = false));
}
}
/** 重置查询 */
function handleResetQuery() {
queryFormRef.value.resetFields();
queryParams.pageNum = 1;
handleQuery();
}
/**
* 行checkbox change事件
*
* @param selection
*/
function handleSelectionChange(selection: any) {
ids.value = selection.map((item: any) => item.id);
}
/**
* 打开字典表单弹窗
*
* @param dictId 字典ID
*/
function openDialog(dictId?: number) {
dialog.visible = true;
if (dictId) {
dialog.title = "修改字典";
DictAPI.getDictFormData(dictId).then((data) => {
Object.assign(formData, data);
});
} else {
dialog.title = "新增字典";
}
}
/** 字典表单提交 */
function handleSubmit() {
dataFormRef.value.validate((isValid: boolean) => {
if (isValid) {
loading.value = false;
const dictId = formData.id;
if (dictId) {
DictAPI.updateDict(dictId, formData)
.then(() => {
ElMessage.success("修改成功");
closeDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
} else {
DictAPI.addDict(formData)
.then(() => {
ElMessage.success("新增成功");
closeDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
}
}
});
}
/** 关闭弹窗 */
function closeDialog() {
dialog.visible = false;
resetForm();
}
/** 重置表单 */
function resetForm() {
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
formData.id = undefined;
formData.status = 1;
formData.sort = 1;
formData.typeCode = props.typeCode;
}
/** 删除字典 */
function handleDelete(dictId?: number) {
const dictIds = [dictId || ids.value].join(",");
if (!dictIds) {
ElMessage.warning("请勾选删除项");
return;
}
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
DictAPI.deleteDictByIds(dictIds).then(() => {
ElMessage.success("删除成功");
handleResetQuery();
});
});
}
onMounted(() => {
handleQuery();
});
</script>

View File

@@ -1,5 +1,4 @@
<!--字典类型-->
<!-- 分类字典 -->
<template>
<div class="app-container">
<div class="search-container">
@@ -7,7 +6,7 @@
<el-form-item label="关键字" prop="name">
<el-input
v-model="queryParams.keywords"
placeholder="字典类型名称/编码"
placeholder="字典名称"
clearable
@keyup.enter="handleQuery"
/>
@@ -16,68 +15,67 @@
<el-button type="primary" @click="handleQuery()"
><i-ep-search />搜索</el-button
>
<el-button @click="handleResetQuery()"
<el-button @click="handleResetClick()"
><i-ep-refresh />重置</el-button
>
</el-form-item>
</el-form>
</div>
<el-card shadow="never" class="table-container">
<template #header>
<el-button
v-hasPerm="['sys:dict_type:add']"
type="success"
@click="openDialog()"
<el-card shadow="never">
<div class="mb-[10px]">
<el-button type="success" @click="handleAddClick()"
><i-ep-plus />新增</el-button
>
<el-button
type="danger"
:disabled="ids.length === 0"
@click="handleDelete()"
@click="handleDeleteClick()"
><i-ep-delete />删除</el-button
>
</template>
</div>
<el-table
v-loading="loading"
highlight-current-row
:data="dictTypeList"
:data="tableData"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="字典类型名称" prop="name" width="200" />
<el-table-column label="字典类型编码" prop="code" width="200" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">启用</el-tag>
<el-tag v-else type="info">禁用</el-tag>
<el-table-column type="expand" label="字典项列表" width="100">
<template #default="props">
<el-table :data="props.row.dictItems">
<el-table-column label="字典项键" prop="name" width="200" />
<el-table-column label="字典项值" prop="value" align="center" />
<el-table-column label="排序" prop="sort" align="center" />
</el-table>
</template>
</el-table-column>
<el-table-column label="字典名称" prop="name" />
<el-table-column label="字典编码" prop="code" />
<el-table-column label="状态" prop="status">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
{{ scope.row.status === 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" align="center" />
<el-table-column fixed="right" label="操作" align="center" width="220">
<template #default="scope">
<el-button
type="primary"
link
size="small"
@click.stop="openDictDialog(scope.row)"
><i-ep-Collection />字典数据</el-button
>
<el-button
v-hasPerm="['sys:dict_type:edit']"
type="primary"
link
size="small"
@click.stop="openDialog(scope.row.id)"
@click.stop="handleEditClick(scope.row.id, scope.row.name)"
><i-ep-edit />编辑</el-button
>
<el-button
v-hasPerm="['sys:dict_type:delete']"
type="primary"
type="danger"
link
size="small"
@click.stop="handleDelete(scope.row.id)"
@click.stop="handleDeleteClick(scope.row.id)"
><i-ep-delete />删除</el-button
>
</template>
@@ -93,71 +91,128 @@
/>
</el-card>
<el-dialog
<!--字典弹窗-->
<el-drawer
v-model="dialog.visible"
:title="dialog.title"
width="500px"
@close="closeDialog"
size="70%"
@close="handleCloseDialog"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="80px"
:rules="computedRules"
label-width="100px"
:inline="true"
>
<el-card shadow="never">
<el-form-item label="字典名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入字典编码" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-form-item label="状态">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0"></el-radio>
<el-radio :value="1">启用</el-radio>
<el-radio :value="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
placeholder="字典类型备注"
:autosize="{ minRows: 2, maxRows: 4 }"
/>
<el-form-item label="备注">
<el-input v-model="formData.remark" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="closeDialog"> </el-button>
</el-card>
<el-card shadow="never" class="mt-5">
<template #header>
<div class="flex-x-between">
<span>字典项</span>
<el-button
type="primary"
size="small"
@click.stop="handleAddAttrClick"
><i-ep-plus />新增字典</el-button
>
</div>
</template>
</el-dialog>
<!--字典数据弹窗-->
<el-dialog
v-model="dictDataDialog.visible"
:title="dictDataDialog.title"
width="1000px"
@close="closeDictDialog"
<el-table
v-loading="loading"
highlight--currentrow
:data="formData.dictItems"
@selection-change="handleSelectionChange"
>
<dict-item
v-model:typeCode="selectedDictType.typeCode"
v-model:typeName="selectedDictType.typeName"
<el-table-column label="字典项名称" width="200">
<template #default="scope">
<el-form-item :prop="'dictItems.' + scope.$index + '.name'">
<el-input v-model="scope.row.name" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="字典项值" width="200">
<template #default="scope">
<el-form-item :prop="'dictItems.' + scope.$index + '.value'">
<el-input v-model="scope.row.value" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="排序">
<template #default="scope">
<el-form-item :prop="'dictItems.' + scope.$index + '.sort'">
<el-input v-model="scope.row.sort" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="状态" prop="status">
<template #default="scope">
<el-form-item :prop="'dictItems.' + scope.$index + '.status'">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
/>
</el-dialog>
</el-form-item>
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
align="center"
width="120"
>
<template #default="scope">
<el-button
type="danger"
link
size="small"
@click.stop="handleDeleteAttrClick(scope.$index)"
><i-ep-delete />删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-card>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmitClick"> </el-button>
<el-button @click="handleCloseDialog"> </el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "DictType",
inheritAttrs: false,
name: "Dict",
inherititems: false,
});
import DictAPI from "@/api/dict";
import { DictTypePageVO, DictTypeQuery, DictTypeForm } from "@/api/dict/model";
import DictAPI, { DictPageQuery, DictPageVO, DictForm } from "@/api/dict";
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
@@ -166,33 +221,42 @@ const loading = ref(false);
const ids = ref<number[]>([]);
const total = ref(0);
const queryParams = reactive<DictTypeQuery>({
const queryParams = reactive<DictPageQuery>({
pageNum: 1,
pageSize: 10,
});
const dictTypeList = ref<DictTypePageVO[]>();
const tableData = ref<DictPageVO[]>();
// 字典弹窗
const dialog = reactive({
title: "",
visible: false,
});
const formData = reactive<DictTypeForm>({
status: 1,
const formData = reactive<DictForm>({});
const computedRules = computed(() => {
const rules: Partial<Record<string, any>> = {
name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
code: [{ required: true, message: "请输入字典编码", trigger: "blur" }],
};
if (formData.dictItems) {
formData.dictItems.forEach((attr, index) => {
rules[`dictItems.${index}.name`] = [
{ required: true, message: "请输入字典项名称", trigger: "blur" },
];
});
}
return rules;
});
const rules = reactive({
name: [{ required: true, message: "请输入字典类型名称", trigger: "blur" }],
code: [{ required: true, message: "请输入字典类型编码", trigger: "blur" }],
});
/** 查询 */
// 查询
function handleQuery() {
loading.value = true;
DictAPI.getDictTypePage(queryParams)
DictAPI.getPage(queryParams)
.then((data) => {
dictTypeList.value = data.list;
tableData.value = data.list;
total.value = data.total;
})
.finally(() => {
@@ -200,56 +264,57 @@ function handleQuery() {
});
}
/**
* 重置查询
*/
function handleResetQuery() {
// 重置查询
function handleResetClick() {
queryFormRef.value.resetFields();
queryParams.pageNum = 1;
handleQuery();
}
/** 行复选框选中 */
// 行选择
function handleSelectionChange(selection: any) {
ids.value = selection.map((item: any) => item.id);
}
/**
* 打开字典类型表单弹窗
*
* @param dicTypeId 字典类型ID
*/
function openDialog(dicTypeId?: number) {
// 新增字典
function handleAddClick() {
dialog.visible = true;
if (dicTypeId) {
dialog.title = "修改字典类型";
DictAPI.getDictTypeForm(dicTypeId).then((data) => {
dialog.title = "新增字典";
}
/**
* 编辑字典
*
* @param id 字典ID
*/
function handleEditClick(id: number, name: string) {
dialog.visible = true;
dialog.title = "【" + name + "】字典修改";
DictAPI.getFormData(id).then((data) => {
Object.assign(formData, data);
});
} else {
dialog.title = "新增字典类型";
}
}
/** 字典类型表单提交 */
function handleSubmit() {
// 提交字典表单
function handleSubmitClick() {
dataFormRef.value.validate((isValid: boolean) => {
console.log("isValid", isValid);
if (isValid) {
loading.value = false;
const dictTypeId = formData.id;
if (dictTypeId) {
DictAPI.updateDictType(dictTypeId, formData)
loading.value = true;
const id = formData.id;
if (id) {
DictAPI.update(id, formData)
.then(() => {
ElMessage.success("修改成功");
closeDialog();
handleCloseDialog();
handleQuery();
})
.finally(() => (loading.value = false));
} else {
DictAPI.addDictType(formData)
DictAPI.add(formData)
.then(() => {
ElMessage.success("新增成功");
closeDialog();
handleCloseDialog();
handleQuery();
})
.finally(() => (loading.value = false));
@@ -258,63 +323,57 @@ function handleSubmit() {
});
}
/** 关闭字典类型弹窗 */
function closeDialog() {
/** 关闭字典弹窗 */
function handleCloseDialog() {
dialog.visible = false;
resetForm();
}
/** 重置字典类型表单 */
function resetForm() {
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
formData.id = undefined;
formData.status = 1;
}
/** 删除字典类型 */
function handleDelete(dictTypeId?: number) {
const dictTypeIds = [dictTypeId || ids.value].join(",");
if (!dictTypeIds) {
/**
* 删除字典
*
* @param id 字典ID
*/
function handleDeleteClick(id?: number) {
const attrGroupIds = [id || ids.value].join(",");
if (!attrGroupIds) {
ElMessage.warning("请勾选删除项");
return;
}
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
DictAPI.deleteDictTypes(dictTypeIds).then(() => {
DictAPI.deleteByIds(attrGroupIds).then(() => {
ElMessage.success("删除成功");
handleResetQuery();
handleResetClick();
});
});
}
const dictDataDialog = reactive({
title: "",
visible: false,
});
const selectedDictType = reactive({ typeCode: "", typeName: "" }); // 当前选中的字典类型
/** 打开字典数据弹窗 */
function openDictDialog(row: DictTypePageVO) {
dictDataDialog.visible = true;
dictDataDialog.title = "【" + row.name + "】字典数据";
selectedDictType.typeCode = row.code;
selectedDictType.typeName = row.name;
// 新增字典
function handleAddAttrClick() {
formData.dictItems = formData.dictItems ?? [];
formData.dictItems.push({ sort: 1, status: 1 });
}
/** 关闭字典数据弹窗 */
function closeDictDialog() {
dictDataDialog.visible = false;
// 删除字典
function handleDeleteAttrClick(index: number) {
if (formData.dictItems && formData.dictItems.length > 0) {
formData.dictItems.splice(index, 1);
}
}
onMounted(() => {
handleQuery();
});
</script>
<style scoped lang="scss">
.el-form--inline .el-form-item {
margin-top: 18px;
}
</style>

View File

@@ -27,7 +27,7 @@
<el-button
v-hasPerm="['sys:menu:add']"
type="success"
@click="handleDialogOpen(0)"
@click="handleOpenDialog(0)"
>
<template #icon><i-ep-plus /></template>
新增</el-button
@@ -84,11 +84,18 @@
</template>
</el-table-column>
<el-table-column
label="路由名称"
align="left"
width="150"
prop="routeName"
/>
<el-table-column
label="路由路径"
align="left"
width="150"
prop="path"
prop="routePath"
/>
<el-table-column
@@ -122,7 +129,7 @@
type="primary"
link
size="small"
@click.stop="handleDialogOpen(scope.row.id)"
@click.stop="handleOpenDialog(scope.row.id)"
>
<i-ep-plus />新增
</el-button>
@@ -132,7 +139,7 @@
type="primary"
link
size="small"
@click.stop="handleDialogOpen(undefined, scope.row.id)"
@click.stop="handleOpenDialog(undefined, scope.row.id)"
>
<i-ep-edit />编辑
</el-button>
@@ -150,19 +157,17 @@
</el-table>
</el-card>
<el-dialog
<el-drawer
v-model="dialog.visible"
:title="dialog.title"
destroy-on-close
append-to-body
width="1000px"
@close="closeDialog"
@close="handleCloseDialog"
size="50%"
>
<el-form
ref="menuFormRef"
:model="formData"
:rules="rules"
label-width="160px"
label-width="100px"
>
<el-form-item label="父级菜单" prop="parentId">
<el-tree-select
@@ -196,7 +201,10 @@
label="外链地址"
prop="path"
>
<el-input v-model="formData.path" placeholder="请输入外链完整路径" />
<el-input
v-model="formData.routePath"
placeholder="请输入外链完整路径"
/>
</el-form-item>
<el-form-item
@@ -204,8 +212,29 @@
formData.type == MenuTypeEnum.CATALOG ||
formData.type == MenuTypeEnum.MENU
"
label=""
prop="path"
prop="routeName"
>
<template #label>
<div>
路由名称
<el-tooltip placement="bottom" effect="light">
<template #content>
如果需要开启缓存,需保证页面 defineOptions 中的 name
与此处一致,建议使用驼峰。
</template>
<i-ep-QuestionFilled class="inline-block" />
</el-tooltip>
</div>
</template>
<el-input v-model="formData.routeName" placeholder="User" />
</el-form-item>
<el-form-item
v-if="
formData.type == MenuTypeEnum.CATALOG ||
formData.type == MenuTypeEnum.MENU
"
prop="routePath"
>
<template #label>
<div>
@@ -222,10 +251,10 @@
</template>
<el-input
v-if="formData.type == MenuTypeEnum.CATALOG"
v-model="formData.path"
v-model="formData.routePath"
placeholder="system"
/>
<el-input v-else v-model="formData.path" placeholder="user" />
<el-input v-else v-model="formData.routePath" placeholder="user" />
</el-form-item>
<el-form-item
@@ -405,10 +434,10 @@
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="closeDialog">取 消</el-button>
<el-button @click="handleCloseDialog">取 消</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -418,8 +447,7 @@ defineOptions({
inheritAttrs: false,
});
import MenuAPI from "@/api/menu";
import { MenuQuery, MenuForm, MenuVO } from "@/api/menu/model";
import MenuAPI, { MenuQuery, MenuForm, MenuVO } from "@/api/menu";
import { MenuTypeEnum } from "@/enums/MenuTypeEnum";
const queryFormRef = ref(ElForm);
@@ -458,7 +486,7 @@ const rules = reactive({
parentId: [{ required: true, message: "请选择顶级菜单", trigger: "blur" }],
name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }],
path: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
routePath: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
component: [{ required: true, message: "请输入组件路径", trigger: "blur" }],
visible: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
});
@@ -496,7 +524,7 @@ function handleRowClick(row: MenuVO) {
* @param parentId 父菜单ID
* @param menuId 菜单ID
*/
function handleDialogOpen(parentId?: number, menuId?: number) {
function handleOpenDialog(parentId?: number, menuId?: number) {
MenuAPI.getOptions()
.then((data) => {
menuOptions.value = [{ value: 0, label: "顶级菜单", children: data }];
@@ -541,13 +569,13 @@ function submitForm() {
if (menuId) {
MenuAPI.update(menuId, formData.value).then(() => {
ElMessage.success("修改成功");
closeDialog();
handleCloseDialog();
handleQuery();
});
} else {
MenuAPI.add(formData.value).then(() => {
ElMessage.success("新增成功");
closeDialog();
handleCloseDialog();
handleQuery();
});
}
@@ -577,7 +605,7 @@ function handleDelete(menuId: number) {
}
// 关闭弹窗
function closeDialog() {
function handleCloseDialog() {
dialog.visible = false;
menuFormRef.value.resetFields();
menuFormRef.value.clearValidate();

View File

@@ -195,7 +195,6 @@
:data="menuPermOptions"
:filter-node-method="handlePermFilter"
:default-expand-all="true"
:check-on-click-node="true"
:check-strictly="!parentChildLinked"
class="mt-5"
>
@@ -222,11 +221,9 @@ defineOptions({
inheritAttrs: false,
});
import RoleAPI from "@/api/role";
import RoleAPI, { RolePageVO, RoleForm, RolePageQuery } from "@/api/role";
import MenuAPI from "@/api/menu";
import { RolePageVO, RoleForm, RoleQuery } from "@/api/role/model";
const queryFormRef = ref(ElForm);
const roleFormRef = ref(ElForm);
const permTreeRef = ref<InstanceType<typeof ElTree>>();
@@ -235,7 +232,7 @@ const loading = ref(false);
const ids = ref<number[]>([]);
const total = ref(0);
const queryParams = reactive<RoleQuery>({
const queryParams = reactive<RolePageQuery>({
pageNum: 1,
pageSize: 10,
});
@@ -315,7 +312,7 @@ function handleOpenDialog(roleId?: number) {
}
}
// 角色保存提交
/** 提交角色表单 */
function handleSubmit() {
roleFormRef.value.validate((valid: any) => {
if (valid) {
@@ -342,7 +339,7 @@ function handleSubmit() {
});
}
// 关闭表单弹窗
/** 关闭表单弹窗 */
function handleCloseDialog() {
dialog.visible = false;
@@ -354,7 +351,7 @@ function handleCloseDialog() {
formData.status = 1;
}
// 删除角色
/** 删除角色 */
function handleDelete(roleId?: number) {
const roleIds = [roleId || ids.value].join(",");
if (!roleIds) {
@@ -377,7 +374,7 @@ function handleDelete(roleId?: number) {
});
}
// 打开分配权限
/** 打开分配菜单权限弹窗 */
async function handleOpenAssignPermDialog(row: RolePageVO) {
const roleId = row.id;
if (roleId) {
@@ -404,7 +401,7 @@ async function handleOpenAssignPermDialog(row: RolePageVO) {
}
}
// 分配权限提交
/** 分配菜单权限提交 */
function handleAssignPermSubmit() {
const roleId = checkedRole.value.id;
if (roleId) {
@@ -425,7 +422,7 @@ function handleAssignPermSubmit() {
}
}
// 切换菜单树展开状态
/** 展开/收缩 菜单权限树 */
function togglePermTree() {
isExpanded.value = !isExpanded.value;
if (permTreeRef.value) {
@@ -438,11 +435,12 @@ function togglePermTree() {
});
}
}
// 菜单权限筛选
watch(permKeywords, (val) => {
permTreeRef.value!.filter(val);
});
// 权限筛选
/** 权限筛选 */
function handlePermFilter(
value: string,
data: {
@@ -453,6 +451,7 @@ function handlePermFilter(
return data.label.includes(value);
}
/** 父子菜单节点是否联动 change*/
function handleparentChildLinkedChange(val: any) {
parentChildLinked.value = val;
}

View File

@@ -0,0 +1,145 @@
<template>
<el-dialog
v-model="dialogVisible"
:align-center="true"
title="导入数据"
width="600px"
@close="handleClose"
>
<el-scrollbar max-height="60vh">
<el-form
ref="importFormRef"
label-width="auto"
style="padding-right: var(--el-dialog-padding-primary)"
:model="importFormData"
:rules="importFormRules"
>
<el-form-item label="文件名" prop="files">
<el-upload
class="w-full"
ref="uploadRef"
v-model:file-list="importFormData.files"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:drag="true"
:limit="1"
:auto-upload="false"
:on-exceed="handleFileExceed"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
*.xlsx / *.xls
<el-link
type="primary"
icon="download"
:underline="false"
@click="handleDownloadTemplate"
>
下载模板
</el-link>
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button
type="primary"
:disabled="importFormData.files.length === 0"
@click="handleSubmit"
>
</el-button>
<el-button @click="handleClose"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { type UploadUserFile } from "element-plus";
import UserAPI from "@/api/user";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "import-success"]);
const dialogVisible = computed({
get: () => props.visible,
set: (val) => {
emit("update:visible", val);
},
});
const importFormRef = ref(null);
const uploadRef = ref(null);
const importFormData = reactive<{
files: UploadUserFile[];
}>({
files: [],
});
const importFormRules = {
files: [{ required: true, message: "文件不能为空", trigger: "blur" }],
};
const handleClose = () => {
dialogVisible.value = false;
};
const handleSubmit = async () => {
if (!importFormData.files.length) {
ElMessage.warning("请选择文件");
return;
}
try {
await UserAPI.import(1, importFormData.files[0].raw as File);
ElMessage.success("上传成功");
emit("import-success");
handleClose();
} catch (error) {
ElMessage.error("上传失败");
}
};
const handleFileExceed = () => {
ElMessage.warning("只能上传一个文件");
};
const handleDownloadTemplate = () => {
UserAPI.downloadTemplate().then((response: any) => {
const fileData = response.data;
const fileName = decodeURI(
response.headers["content-disposition"].split(";")[1].split("=")[1]
);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
});
};
</script>

View File

@@ -59,12 +59,12 @@
<el-card shadow="never" class="table-container">
<template #header>
<div class="flex justify-between">
<div class="flex-x-between">
<div>
<el-button
v-hasPerm="['sys:user:add']"
type="success"
@click="openDialog('user-form')"
@click="handleOpenDialog()"
><i-ep-plus />新增</el-button
>
<el-button
@@ -76,19 +76,10 @@
>
</div>
<div>
<el-dropdown split-button>
导入
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="downloadTemplate">
<i-ep-download />下载模板</el-dropdown-item
<el-button class="ml-3" @click="handleOpenImportDialog"
><template #icon><i-ep-upload /></template>导入</el-button
>
<el-dropdown-item @click="openDialog('user-import')">
<i-ep-top />导入数据</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button class="ml-3" @click="handleExport"
><template #icon><i-ep-download /></template>导出</el-button
>
@@ -162,7 +153,7 @@
type="primary"
size="small"
link
@click="resetPassword(scope.row)"
@click="hancleResetPassword(scope.row)"
><i-ep-refresh-left />重置密码</el-button
>
<el-button
@@ -170,12 +161,12 @@
type="primary"
link
size="small"
@click="openDialog('user-form', scope.row.id)"
@click="handleOpenDialog(scope.row.id)"
><i-ep-edit />编辑</el-button
>
<el-button
v-hasPerm="['sys:user:delete']"
type="primary"
type="danger"
link
size="small"
@click="handleDelete(scope.row.id)"
@@ -196,17 +187,15 @@
</el-col>
</el-row>
<!-- 弹窗 -->
<el-dialog
<!-- 用户表单弹窗 -->
<el-drawer
v-model="dialog.visible"
:title="dialog.title"
:width="dialog.width"
append-to-body
@close="closeDialog"
@close="handleCloseDialog"
>
<!-- 用户新增/编辑表单 -->
<el-form
v-if="dialog.type === 'user-form'"
ref="userFormRef"
:model="formData"
:rules="rules"
@@ -228,7 +217,7 @@
<el-tree-select
v-model="formData.deptId"
placeholder="请选择所属部门"
:data="deptList"
:data="deptOptions"
filterable
check-strictly
:render-after-expand="false"
@@ -236,13 +225,13 @@
</el-form-item>
<el-form-item label="性别" prop="gender">
<dictionary v-model="formData.gender" type-code="gender" />
<dictionary v-model="formData.gender" code="gender" />
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="formData.roleIds" multiple placeholder="请选择">
<el-option
v-for="item in roleList"
v-for="item in roleOptions"
:key="item.value"
:label="item.label"
:value="item.value"
@@ -274,55 +263,19 @@
</el-form-item>
</el-form>
<!-- 用户导入表单 -->
<el-form
v-else-if="dialog.type === 'user-import'"
:model="importData"
label-width="100px"
>
<el-form-item label="部门">
<el-tree-select
v-model="importData.deptId"
placeholder="请选择部门"
:data="deptList"
filterable
check-strictly
/>
</el-form-item>
<el-form-item label="Excel文件">
<el-upload
ref="uploadRef"
action=""
drag
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:limit="1"
:auto-upload="false"
:file-list="importData.fileList"
:on-change="handleFileChange"
:on-exceed="handleFileExceed"
>
<el-icon class="el-icon--upload">
<i-ep-upload-filled />
</el-icon>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
<template #tip>
<div>xls/xlsx files</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="closeDialog"> </el-button>
<el-button @click="handleCloseDialog"> </el-button>
</div>
</template>
</el-dialog>
</el-drawer>
<!-- 用户导入弹窗 -->
<user-import
v-model:visible="importDialogVisible"
@import-success="handleOpenImportDialogSuccess"
/>
</div>
</template>
@@ -332,30 +285,28 @@ defineOptions({
inheritAttrs: false,
});
import UserAPI from "@/api/user";
import UserAPI, { UserForm, UserPageQuery, UserPageVO } from "@/api/user";
import DeptAPI from "@/api/dept";
import RoleAPI from "@/api/role";
import { UserForm, UserQuery, UserPageVO } from "@/api/user/model";
import type { UploadInstance } from "element-plus";
import { genFileId } from "element-plus";
const queryFormRef = ref(ElForm);
const userFormRef = ref(ElForm);
const queryFormRef = ref(ElForm); // 查询表单
const userFormRef = ref(ElForm); // 用户表单
const uploadRef = ref<UploadInstance>(); // 上传组件
const loading = ref(false); // 加载状态
const removeIds = ref([]); // 删除用户ID集合 用于批量删除
const queryParams = reactive<UserQuery>({
const loading = ref(false);
const removeIds = ref([]);
const total = ref(0);
const pageData = ref<UserPageVO[]>();
/** 部门下拉选项 */
const deptOptions = ref<OptionType[]>();
/** 角色下拉选项 */
const roleOptions = ref<OptionType[]>();
/** 用户查询参数 */
const queryParams = reactive<UserPageQuery>({
pageNum: 1,
pageSize: 10,
});
const dateTimeRange = ref("");
const total = ref(0); // 数据总数
const pageData = ref<UserPageVO[]>(); // 用户分页数据
const deptList = ref<OptionType[]>(); // 部门下拉数据源
const roleList = ref<OptionType[]>(); // 角色下拉数据源
const dateTimeRange = ref("");
watch(dateTimeRange, (newVal) => {
if (newVal) {
queryParams.startTime = newVal[0];
@@ -363,27 +314,21 @@ watch(dateTimeRange, (newVal) => {
}
});
// 弹窗对象
/** 用户弹窗对象 */
const dialog = reactive({
visible: false,
type: "user-form",
width: 800,
title: "",
});
/** 导入弹窗显示状态 */
const importDialogVisible = ref(false);
// 用户表单数据
const formData = reactive<UserForm>({
status: 1,
});
// 用户导入数据
const importData = reactive({
deptId: undefined,
file: undefined,
fileList: [],
});
// 校验规则
/** 用户表单校验规则 */
const rules = reactive({
username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
nickname: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
@@ -436,7 +381,7 @@ function handleSelectionChange(selection: any) {
}
/** 重置密码 */
function resetPassword(row: { [key: string]: any }) {
function hancleResetPassword(row: { [key: string]: any }) {
ElMessageBox.prompt(
"请输入用户「" + row.username + "」的新密码",
"重置密码",
@@ -456,34 +401,18 @@ function resetPassword(row: { [key: string]: any }) {
});
}
/** 加载角色下拉数据源 */
async function loadRoleOptions() {
RoleAPI.getOptions().then((data) => {
roleList.value = data;
});
}
/** 加载部门下拉数据源 */
async function loadDeptOptions() {
DeptAPI.getOptions().then((data) => {
deptList.value = data;
});
}
/**
* 打开弹窗
*
* @param type 弹窗类型 用户表单user-form | 用户导入user-import
* @param id 用户ID
*/
async function openDialog(type: string, id?: number) {
async function handleOpenDialog(id?: number) {
dialog.visible = true;
dialog.type = type;
// 加载角色下拉数据源
roleOptions.value = await RoleAPI.getOptions();
// 加载部门下拉数据源
deptOptions.value = await DeptAPI.getOptions();
if (dialog.type === "user-form") {
// 用户表单弹窗
await loadDeptOptions();
await loadRoleOptions();
if (id) {
dialog.title = "修改用户";
UserAPI.getFormData(id).then((data) => {
@@ -492,36 +421,20 @@ async function openDialog(type: string, id?: number) {
} else {
dialog.title = "新增用户";
}
} else if (dialog.type === "user-import") {
// 用户导入弹窗
dialog.title = "导入用户";
dialog.width = 600;
loadDeptOptions();
}
}
/**
* 关闭弹窗
*
* @param type 弹窗类型 用户表单user-form | 用户导入user-import
*/
function closeDialog() {
/** 关闭弹窗 */
function handleCloseDialog() {
dialog.visible = false;
if (dialog.type === "user-form") {
userFormRef.value.resetFields();
userFormRef.value.clearValidate();
formData.id = undefined;
formData.status = 1;
} else if (dialog.type === "user-import") {
importData.file = undefined;
importData.fileList = [];
}
}
/** 表单提交 */
const handleSubmit = useThrottleFn(() => {
if (dialog.type === "user-form") {
userFormRef.value.validate((valid: any) => {
if (valid) {
const userId = formData.id;
@@ -530,7 +443,7 @@ const handleSubmit = useThrottleFn(() => {
UserAPI.update(userId, formData)
.then(() => {
ElMessage.success("修改用户成功");
closeDialog();
handleCloseDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
@@ -538,28 +451,13 @@ const handleSubmit = useThrottleFn(() => {
UserAPI.add(formData)
.then(() => {
ElMessage.success("新增用户成功");
closeDialog();
handleCloseDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
}
}
});
} else if (dialog.type === "user-import") {
if (!importData?.deptId) {
ElMessage.warning("请选择部门");
return false;
}
if (!importData?.file) {
ElMessage.warning("上传Excel文件不能为空");
return false;
}
UserAPI.import(importData?.deptId, importData?.file).then((data) => {
ElMessage.success("导入用户成功");
closeDialog();
handleResetQuery();
});
}
}, 3000);
/** 删除用户 */
@@ -581,44 +479,14 @@ function handleDelete(id?: number) {
});
});
}
/** 下载导入模板 */
function downloadTemplate() {
UserAPI.downloadTemplate().then((response: any) => {
const fileData = response.data;
const fileName = decodeURI(
response.headers["content-disposition"].split(";")[1].split("=")[1]
);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
});
/** 打开导入弹窗 */
function handleOpenImportDialog() {
importDialogVisible.value = true;
}
/** Excel文件 Change */
function handleFileChange(file: any) {
importData.file = file.raw;
}
/** Excel文件 Exceed */
function handleFileExceed(files: any) {
uploadRef.value!.clearFiles();
const file = files[0];
file.uid = genFileId();
uploadRef.value!.handleStart(file);
importData.file = file;
/** 导入用户成功 */
function handleOpenImportDialogSuccess() {
handleQuery();
}
/** 导出用户 */

View File

@@ -25,9 +25,8 @@
"jsxFragmentFactory": "Fragment"
},
"include": [
"src/**/*.ts",
"src/**/*.vue",
"src/typings/**/*.d.ts",
"src/types/**/*.d.ts",
"mock/**/*.ts",
"vite.config.ts"
],