mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-24 23:47:17 +08:00
perf: 系统管理
This commit is contained in:
parent
7443ac3ac4
commit
9bac6333fb
@ -38,6 +38,7 @@ menus:
|
||||
hsRole: Role Manage
|
||||
hsSystemMenu: Menu Manage
|
||||
hsDept: Dept Manage
|
||||
hsJob: Job Manage
|
||||
hssysMonitor: System Monitor
|
||||
hsOnlineUser: Online User
|
||||
hsLoginLog: Login Log
|
||||
|
@ -35,9 +35,10 @@ menus:
|
||||
hsAbout: 关于
|
||||
hssysManagement: 系统管理
|
||||
hsUser: 用户管理
|
||||
hsRole: 角色管理
|
||||
hsRole: 权限管理
|
||||
hsSystemMenu: 菜单管理
|
||||
hsDept: 部门管理
|
||||
hsJob: 角色管理
|
||||
hssysMonitor: 系统监控
|
||||
hsOnlineUser: 在线用户
|
||||
hsLoginLog: 登录日志
|
||||
|
@ -51,6 +51,15 @@ const systemManagementRouter = {
|
||||
title: "menus.hsDept",
|
||||
roles: ["admin"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/system/job/index",
|
||||
name: "SystemJob",
|
||||
meta: {
|
||||
icon: "ri:account-circle-fill",
|
||||
title: "menus.hsDept",
|
||||
roles: ["admin"]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
73
src/api/system/dept.ts
Normal file
73
src/api/system/dept.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { PageQuery, type ApiAbstract } from "@/utils/http/ApiAbstract";
|
||||
import { baseUrlApi } from "../utils";
|
||||
|
||||
export class Dept {
|
||||
createBy: string;
|
||||
createTime: Date;
|
||||
deptSort: number;
|
||||
enabled: boolean;
|
||||
hasChildren: boolean;
|
||||
id: number;
|
||||
pid: number;
|
||||
status: number;
|
||||
label: string;
|
||||
leaf: boolean;
|
||||
name: string;
|
||||
subCount: number;
|
||||
updateBy: string;
|
||||
updateTime: Date;
|
||||
menus: any[];
|
||||
}
|
||||
export class DeptQueryCriteria extends PageQuery {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
pid: number;
|
||||
pidIsNull: boolean;
|
||||
createTime: Array<Date>;
|
||||
}
|
||||
|
||||
export const getDepts = (params: DeptQueryCriteria | any) => {
|
||||
return http.request<ApiAbstract<Dept>>("get", baseUrlApi("dept"), {
|
||||
params
|
||||
});
|
||||
};
|
||||
export const getDeptTree = (params?: DeptQueryCriteria | any) => {
|
||||
return http.request<ApiAbstract<Dept>>("get", baseUrlApi("dept/treeAll"), {
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeptSuperior = (ids: Number[]) => {
|
||||
const data = ids.length || ids.length === 0 ? ids : Array.of(ids);
|
||||
return http.request<ApiAbstract<Dept>>("post", baseUrlApi("dept/superior"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const add = (data: Partial<Dept>) => {
|
||||
return http.request<ApiAbstract<Dept>>("post", baseUrlApi("dept"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const del = (ids: number[] | any) => {
|
||||
return http.request("delete", baseUrlApi("dept"), {
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
export const edit = (data: Partial<Dept>) => {
|
||||
return http.request<ApiAbstract<Dept>>("put", baseUrlApi("dept"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const download = (data: Partial<DeptQueryCriteria>) => {
|
||||
return http.request<Blob>(
|
||||
"get",
|
||||
baseUrlApi("dept/download"),
|
||||
{
|
||||
data
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
};
|
64
src/api/system/job.ts
Normal file
64
src/api/system/job.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { http } from "@/utils/http";
|
||||
import {
|
||||
type ApiAbstract,
|
||||
type Page,
|
||||
PageQuery,
|
||||
VersionEntity
|
||||
} from "@/utils/http/ApiAbstract";
|
||||
import { baseUrlApi } from "../utils";
|
||||
|
||||
export class Job extends VersionEntity {
|
||||
id: number;
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 岗位排序
|
||||
*/
|
||||
jobSort: number;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
enabled: boolean;
|
||||
}
|
||||
export class JobQueryCriteria extends PageQuery {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
createTime: Date[];
|
||||
}
|
||||
|
||||
export const get = (params?: number | any) => {
|
||||
return http.request<ApiAbstract<Page<Job>>>("get", baseUrlApi("job"), {
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
export const add = (data: Partial<Job>) => {
|
||||
return http.request("post", baseUrlApi("job"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const del = (ids: number[] | any) => {
|
||||
return http.request("delete", baseUrlApi("job"), {
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
export const edit = (data: Partial<Job>) => {
|
||||
return http.request("put", baseUrlApi("job"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const download = (data: Partial<JobQueryCriteria>) => {
|
||||
return http.request<Blob>(
|
||||
"get",
|
||||
baseUrlApi("job/download"),
|
||||
{
|
||||
data
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
};
|
78
src/api/system/menu.ts
Normal file
78
src/api/system/menu.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { PageQuery, type ApiAbstract } from "@/utils/http/ApiAbstract";
|
||||
import { baseUrlApi } from "../utils";
|
||||
|
||||
export class Menu {
|
||||
id: number;
|
||||
|
||||
children: Menu[];
|
||||
|
||||
type: number;
|
||||
|
||||
permission: string;
|
||||
|
||||
title: string;
|
||||
|
||||
menuSort: number;
|
||||
|
||||
path: string;
|
||||
|
||||
component: string;
|
||||
|
||||
pid: number;
|
||||
|
||||
subCount: number;
|
||||
|
||||
iframe: boolean;
|
||||
|
||||
cache: boolean;
|
||||
|
||||
hidden: boolean;
|
||||
|
||||
componentName: string;
|
||||
|
||||
icon: string;
|
||||
}
|
||||
export class MenuQueryCriteria extends PageQuery {
|
||||
blurry: string;
|
||||
pid: number;
|
||||
createTime: Date[];
|
||||
}
|
||||
export const menuTree = (ids: number[]) => {
|
||||
return http.request<ApiAbstract<Menu>>("post", baseUrlApi("menus/tree"), {
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
|
||||
export const get = (params: number | any) => {
|
||||
return http.request<ApiAbstract<Menu>>("get", baseUrlApi("menus"), {
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
export const add = (data: Partial<Menu>) => {
|
||||
return http.request("post", baseUrlApi("menus"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const del = (ids: number[] | any) => {
|
||||
return http.request("delete", baseUrlApi("menus"), {
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
export const edit = (data: Partial<Menu>) => {
|
||||
return http.request<ApiAbstract<Menu>>("put", baseUrlApi("menus"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const download = (data: Partial<MenuQueryCriteria>) => {
|
||||
return http.request<Blob>(
|
||||
"get",
|
||||
baseUrlApi("menus/download"),
|
||||
{
|
||||
data
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
};
|
90
src/api/system/role.ts
Normal file
90
src/api/system/role.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { type ApiAbstract, PageQuery } from "@/utils/http/ApiAbstract";
|
||||
import { baseUrlApi } from "../utils";
|
||||
import type { Dept } from "./dept";
|
||||
|
||||
export class Role {
|
||||
id: number;
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
users: any;
|
||||
/**
|
||||
* 菜单
|
||||
*/
|
||||
menus: any;
|
||||
/**
|
||||
* 部门
|
||||
*/
|
||||
depts: Dept[] | any[];
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 数据权限,全部 、 本级 、 自定义
|
||||
*/
|
||||
dataScope: string;
|
||||
/**
|
||||
* 级别,数值越小,级别越大
|
||||
*/
|
||||
level: number;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
description: string;
|
||||
}
|
||||
export class RoleQueryCriteria extends PageQuery {
|
||||
blurry: string;
|
||||
createTime: Date[];
|
||||
}
|
||||
export const getAll = (data: Partial<RoleQueryCriteria>) => {
|
||||
return http.request<Role[]>("get", baseUrlApi("roles/all"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const get = (params?: number | any) => {
|
||||
return http.request<ApiAbstract<Role>>("get", baseUrlApi("roles"), {
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
export const getLevel = () => {
|
||||
return http.request("get", baseUrlApi("roles/level"));
|
||||
};
|
||||
|
||||
export const editMenu = data => {
|
||||
return http.request("put", baseUrlApi("roles/menu"), { data });
|
||||
};
|
||||
export const add = (data: Partial<Role>) => {
|
||||
return http.request("post", baseUrlApi("roles"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const del = (ids: number[] | any) => {
|
||||
return http.request("delete", baseUrlApi("roles"), {
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
export const edit = (data: Partial<Role>) => {
|
||||
return http.request<ApiAbstract<Role>>("put", baseUrlApi("roles"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const menus = (data: Partial<Dept>) => {
|
||||
return http.request("put", baseUrlApi("roles/menu"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const download = (data: Partial<RoleQueryCriteria>) => {
|
||||
return http.request<Blob>(
|
||||
"get",
|
||||
baseUrlApi("roles/download"),
|
||||
{
|
||||
data
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
};
|
158
src/api/system/user.ts
Normal file
158
src/api/system/user.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { http } from "@/utils/http";
|
||||
import {
|
||||
type ApiAbstract,
|
||||
PageQuery,
|
||||
VersionEntity
|
||||
} from "@/utils/http/ApiAbstract";
|
||||
import { baseUrlApi } from "../utils";
|
||||
import type { Role } from "./role";
|
||||
import type { Job } from "./job";
|
||||
import type { Dept } from "./dept";
|
||||
import { encrypt } from "@/utils/rsaEncrypt";
|
||||
|
||||
export class User extends VersionEntity {
|
||||
id: number;
|
||||
/**
|
||||
* 用户角色
|
||||
*/
|
||||
roles: Role[];
|
||||
/**
|
||||
* 用户岗位
|
||||
*/
|
||||
jobs: Job[];
|
||||
/**
|
||||
* 用户部门
|
||||
*/
|
||||
dept: Dept;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName: string;
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* 电话号码
|
||||
*/
|
||||
phone: string;
|
||||
/**
|
||||
* 用户性别
|
||||
*/
|
||||
gender: string;
|
||||
/**
|
||||
* 头像真实名称
|
||||
*/
|
||||
avatarName: string;
|
||||
/**
|
||||
* 头像存储的路径
|
||||
*/
|
||||
avatarPath: string;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
password: string;
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
enabled: string;
|
||||
/**
|
||||
* 是否为admin账号
|
||||
*/
|
||||
isAdmin: boolean;
|
||||
/**
|
||||
* 最后修改密码的时间
|
||||
*/
|
||||
pwdResetTime: Date;
|
||||
}
|
||||
export class UserQueryCriteria extends PageQuery {
|
||||
name: string;
|
||||
deptId: number;
|
||||
deptIds: number[];
|
||||
enabled: boolean;
|
||||
createTime: Date[];
|
||||
}
|
||||
|
||||
export const get = (params: number | any) => {
|
||||
return http.request<ApiAbstract<User>>("get", baseUrlApi("users"), {
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
export const add = (data: Partial<User>) => {
|
||||
return http.request("post", baseUrlApi("users"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
export const del = (ids: number[] | any) => {
|
||||
return http.request("delete", baseUrlApi("users"), {
|
||||
data: ids
|
||||
});
|
||||
};
|
||||
export const edit = (data: Partial<User>) => {
|
||||
return http.request("put", baseUrlApi("users"), {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const download = (data: Partial<UserQueryCriteria>) => {
|
||||
return http.request<Blob>(
|
||||
"get",
|
||||
baseUrlApi("users/download"),
|
||||
{
|
||||
data
|
||||
},
|
||||
{ responseType: "blob" }
|
||||
);
|
||||
};
|
||||
|
||||
export const updateAvatarByid = ({ id, avatar }) => {
|
||||
const formData = new FormData();
|
||||
formData.append("avatar", avatar, "avatar.jpg");
|
||||
return http.request("post", baseUrlApi("/users/updateAvatar/" + id), {
|
||||
data: formData,
|
||||
// 请求超时时间
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
filename: "avatar.png"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export function updatePass({ oldPass, newPass }) {
|
||||
const data = {
|
||||
oldPass: encrypt(oldPass),
|
||||
newPass: encrypt(newPass)
|
||||
};
|
||||
return http.request("post", baseUrlApi("users/updatePass"), {
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function resetEmail(data) {
|
||||
return http.request("post", baseUrlApi("/code/resetEmail?email=" + data));
|
||||
}
|
||||
|
||||
export function updateEmail(form) {
|
||||
const data = {
|
||||
password: encrypt(form.pass),
|
||||
email: form.email
|
||||
};
|
||||
return http.request("post", baseUrlApi("users/updateEmail/" + form.code), {
|
||||
data
|
||||
});
|
||||
}
|
||||
export function editUser(data) {
|
||||
return http.request("put", baseUrlApi("users/center"), {
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function getLog() {
|
||||
return http.request("get", baseUrlApi("logs/user"));
|
||||
}
|
53
src/views/components/date-picker.vue
Normal file
53
src/views/components/date-picker.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<script setup lang="tsx">
|
||||
import { ref } from "vue";
|
||||
const props = defineProps({
|
||||
createTime: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
const createTime = ref(props.createTime);
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
text: "最近1周",
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||
return [start, end];
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "最近1个月",
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||
return [start, end];
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "最近3个月",
|
||||
value: () => {
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||
return [start, end];
|
||||
}
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-date-picker
|
||||
v-model="createTime"
|
||||
type="datetimerange"
|
||||
:shortcuts="shortcuts"
|
||||
range-separator="-"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD h:m:s"
|
||||
/>
|
||||
</template>
|
@ -58,7 +58,7 @@ const { locale, translationCh, translationEn } = useTranslationLang();
|
||||
|
||||
const ruleForm = reactive({
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
password: "123456",
|
||||
verifyCode: ""
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { formRules } from "./utils/rule";
|
||||
import { FormProps } from "./utils/types";
|
||||
@ -8,13 +8,17 @@ import { usePublicHooks } from "../hooks";
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
higherDeptOptions: [],
|
||||
higherDeptOptions2: {},
|
||||
parentId: 0,
|
||||
id: 0,
|
||||
pid: 0,
|
||||
deptSort: 0,
|
||||
enabled: false,
|
||||
name: "",
|
||||
principal: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
sort: 0,
|
||||
status: 1,
|
||||
remark: ""
|
||||
})
|
||||
});
|
||||
@ -26,7 +30,9 @@ const newFormInline = ref(props.formInline);
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 在这里编写页面加载后要执行的代码
|
||||
});
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
@ -41,22 +47,16 @@ defineExpose({ getRef });
|
||||
<re-col>
|
||||
<el-form-item label="上级部门">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parentId"
|
||||
v-model="newFormInline.pid"
|
||||
class="w-full"
|
||||
:options="newFormInline.higherDeptOptions"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}"
|
||||
:props="newFormInline.higherDeptOptions2"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择上级部门"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ data.name }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
<span>{{ data.label }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.subCount }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
@ -103,8 +103,7 @@ defineExpose({ getRef });
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="排序">
|
||||
<el-input-number
|
||||
v-model="newFormInline.sort"
|
||||
class="!w-full"
|
||||
v-model="newFormInline.deptSort"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
@ -114,10 +113,10 @@ defineExpose({ getRef });
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="部门状态">
|
||||
<el-switch
|
||||
v-model="newFormInline.status"
|
||||
v-model="newFormInline.enabled"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
:style="switchStyle"
|
||||
|
@ -3,28 +3,94 @@ import { ref } from "vue";
|
||||
import { useDept } from "./utils/hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import datePicker from "@/views/components/date-picker.vue";
|
||||
import * as Dept from "@/api/system/dept";
|
||||
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import { message } from "@/utils/message";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemDept"
|
||||
name: "Dept"
|
||||
});
|
||||
|
||||
const handleSelectionChange = val => {
|
||||
multipleSelection.value = val;
|
||||
if (val != null && val.length > 0) {
|
||||
value2.value = false;
|
||||
} else {
|
||||
value2.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteAll() {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>删除所选的</strong><strong style='color:var(--el-color-primary)'>${multipleSelection.value.length}</strong>个部门吗?`,
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
Dept.del(multipleSelection.value.map(dept => dept.id));
|
||||
message("已删除所选的部门", {
|
||||
type: "success"
|
||||
});
|
||||
onSearch();
|
||||
})
|
||||
.catch(() => {
|
||||
onSearch();
|
||||
});
|
||||
}
|
||||
const load = (
|
||||
row: Partial<Dept.Dept>,
|
||||
treeNode: unknown,
|
||||
resolve: (date: Partial<Dept.Dept>[]) => void
|
||||
) => {
|
||||
let queryDept = { pid: row?.id };
|
||||
if (!row?.id) {
|
||||
queryDept = null;
|
||||
}
|
||||
Dept.getDepts(queryDept).then(contentData => {
|
||||
// 调用' resolve '回调以返回子节点数据并指示加载完成。
|
||||
resolve(contentData.data.content);
|
||||
});
|
||||
};
|
||||
const exportClick = async () => {
|
||||
const response: Blob = await Dept.download(null);
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(response); // 创建媒体流 url ,详细了解可自己查 URL.createObjectURL(推荐 MDN )
|
||||
|
||||
a.href = url;
|
||||
a.style.display = "none";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.parentNode.removeChild(a);
|
||||
window.URL.revokeObjectURL(url); // 删除创建的媒体流 url 对象
|
||||
message("导出成功", {
|
||||
type: "success"
|
||||
});
|
||||
};
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const value2 = ref(true);
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
multipleSelection,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
handleDelete
|
||||
} = useDept();
|
||||
</script>
|
||||
|
||||
@ -41,24 +107,27 @@ const {
|
||||
v-model="form.name"
|
||||
placeholder="请输入部门名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
class="!w-[200px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
v-model="form.enabled"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="0" />
|
||||
<el-option label="启用" :value="true" />
|
||||
<el-option label="停用" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="createTime">
|
||||
<datePicker v-model="form.createTime" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:icon="useRenderIcon('search')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
@ -71,12 +140,12 @@ const {
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="部门管理(仅演示,操作后不生效)"
|
||||
title="部门列表"
|
||||
:columns="columns"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<template #add>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@ -85,11 +154,30 @@ const {
|
||||
新增部门
|
||||
</el-button>
|
||||
</template>
|
||||
<template #delete>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="value2"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
@click="deleteAll()"
|
||||
>
|
||||
删除部门
|
||||
</el-button>
|
||||
</template>
|
||||
<template #export>
|
||||
<el-button
|
||||
type="success"
|
||||
:icon="useRenderIcon('solar:upload-bold')"
|
||||
@click="exportClick()"
|
||||
>
|
||||
导出数据
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
:adaptiveConfig="{ offsetBottom: 32 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
@ -97,12 +185,14 @@ const {
|
||||
default-expand-all
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:data="dataList"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
lazy
|
||||
:load="load"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
@ -112,19 +202,9 @@ const {
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
@click="openDialog('编辑', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
>
|
||||
新增
|
||||
编辑
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除部门名称为${row.name}的这条数据`"
|
||||
@ -150,14 +230,6 @@ const {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
|
@ -1,26 +1,41 @@
|
||||
import dayjs from "dayjs";
|
||||
import editForm from "../form.vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import { getDeptList } from "@/api/system";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import * as Dept from "@/api/system/dept";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import { reactive, ref, onMounted, h } from "vue";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import { ElMessageBox, type CascaderProps } from "element-plus";
|
||||
|
||||
export function useDept() {
|
||||
const form = reactive({
|
||||
name: "",
|
||||
status: null
|
||||
enabled: null,
|
||||
createTime: ""
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const dataList = ref([]);
|
||||
const dataList = reactive([]);
|
||||
const loading = ref(true);
|
||||
const { tagStyle } = usePublicHooks();
|
||||
const multipleSelection = ref([]);
|
||||
const switchLoadMap = ref({});
|
||||
const { switchStyle } = usePublicHooks();
|
||||
|
||||
/** 表格索引 */
|
||||
const indexMethod = (index: number) => {
|
||||
return index + 1;
|
||||
};
|
||||
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
type: "selection"
|
||||
},
|
||||
{
|
||||
type: "index",
|
||||
index: indexMethod
|
||||
},
|
||||
{
|
||||
label: "部门名称",
|
||||
prop: "name",
|
||||
@ -29,17 +44,26 @@ export function useDept() {
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "sort",
|
||||
prop: "deptSort",
|
||||
minWidth: 70
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
prop: "enabled",
|
||||
minWidth: 100,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} style={tagStyle.value(row.status)}>
|
||||
{row.status === 1 ? "启用" : "停用"}
|
||||
</el-tag>
|
||||
cellRenderer: scope => (
|
||||
<el-switch
|
||||
v-model={scope.row.enabled}
|
||||
size={scope.props.size === "small" ? "small" : "default"}
|
||||
loading={switchLoadMap.value[scope.row.index]?.loading}
|
||||
style={switchStyle.value}
|
||||
inline-prompt
|
||||
active-value={true}
|
||||
inactive-value={false}
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
onChange={() => onChange(scope as any)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
@ -57,15 +81,11 @@ export function useDept() {
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 210,
|
||||
width: 160,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
function handleSelectionChange(val) {
|
||||
console.log("handleSelectionChange", val);
|
||||
}
|
||||
|
||||
function resetForm(formEl) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
@ -74,17 +94,24 @@ export function useDept() {
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getDeptList(); // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id
|
||||
let newData = data;
|
||||
const queryType = new Dept.DeptQueryCriteria();
|
||||
|
||||
if (!isAllEmpty(form.name)) {
|
||||
// 前端搜索部门名称
|
||||
newData = newData.filter(item => item.name.includes(form.name));
|
||||
queryType.name = form.name;
|
||||
}
|
||||
if (!isAllEmpty(form.status)) {
|
||||
// 前端搜索状态
|
||||
newData = newData.filter(item => item.status === form.status);
|
||||
if (!isAllEmpty(form.enabled)) {
|
||||
queryType.enabled = form.enabled;
|
||||
}
|
||||
dataList.value = handleTree(newData); // 处理成树结构
|
||||
if (!isAllEmpty(form.createTime)) {
|
||||
queryType.createTime = form.createTime;
|
||||
}
|
||||
const depts = (await Dept.getDepts(queryType)).data; // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id
|
||||
let newData = depts.content;
|
||||
//dataList.value = handleTree2(newData); // 处理成树结构
|
||||
dataList.splice(0, dataList.length); // 清空数组
|
||||
newData.forEach(x => {
|
||||
dataList.push(x);
|
||||
});
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
@ -95,26 +122,51 @@ export function useDept() {
|
||||
if (!treeList || !treeList.length) return;
|
||||
const newTreeList = [];
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
treeList[i].disabled = treeList[i].status === 0 ? true : false;
|
||||
treeList[i].disabled = treeList[i].enabled;
|
||||
formatHigherDeptOptions(treeList[i].children);
|
||||
newTreeList.push(treeList[i]);
|
||||
}
|
||||
return newTreeList;
|
||||
}
|
||||
|
||||
const higherDeptOptions2: CascaderProps = {
|
||||
lazy: true,
|
||||
checkStrictly: true,
|
||||
lazyLoad(node, resolve) {
|
||||
setTimeout(() => {
|
||||
let queryDept = { pid: node?.data?.value };
|
||||
if (!node.data) {
|
||||
queryDept = null;
|
||||
}
|
||||
Dept.getDepts(queryDept).then(contentData => {
|
||||
const nodes = contentData.data.content.map(item => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
subCount: item.subCount,
|
||||
leaf: item.subCount === 0
|
||||
}));
|
||||
// 调用' resolve '回调以返回子节点数据并指示加载完成。
|
||||
resolve(nodes);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
function openDialog(title = "新增", row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}部门`,
|
||||
props: {
|
||||
formInline: {
|
||||
higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)),
|
||||
higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList)),
|
||||
higherDeptOptions2: higherDeptOptions2,
|
||||
parentId: row?.parentId ?? 0,
|
||||
id: row?.id ?? 0,
|
||||
pid: row?.pid ?? 0,
|
||||
name: row?.name ?? "",
|
||||
deptSort: row?.deptSort ?? 0,
|
||||
principal: row?.principal ?? "",
|
||||
phone: row?.phone ?? "",
|
||||
email: row?.email ?? "",
|
||||
sort: row?.sort ?? 0,
|
||||
status: row?.status ?? 1,
|
||||
enabled: row?.enabled ?? false,
|
||||
remark: row?.remark ?? ""
|
||||
}
|
||||
},
|
||||
@ -135,13 +187,27 @@ export function useDept() {
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
Dept.add({
|
||||
name: curData.name,
|
||||
pid: curData.pid === 0 ? null : curData.pid[0],
|
||||
deptSort: curData.deptSort,
|
||||
enabled: curData.enabled
|
||||
});
|
||||
chores();
|
||||
} else if (title === "修改") {
|
||||
Dept.edit({
|
||||
id: curData.id,
|
||||
name: curData.name,
|
||||
pid: curData.pid === 0 ? null : curData.pid,
|
||||
deptSort: curData.deptSort,
|
||||
enabled: curData.enabled
|
||||
});
|
||||
// 实际开发先调用编辑接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
@ -151,10 +217,60 @@ export function useDept() {
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了部门名称为${row.name}的这条数据`, { type: "success" });
|
||||
Dept.del([row.id]).then(() => {
|
||||
message(`您删除了部门名称为${row.name}的这条数据`, { type: "success" });
|
||||
});
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function onChange({ row, index }) {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>${
|
||||
!row.enabled ? "停用" : "启用"
|
||||
}</strong><strong style='color:var(--el-color-primary)'>${
|
||||
row.name
|
||||
}</strong>用户吗?`,
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: false
|
||||
}
|
||||
);
|
||||
Dept.edit({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
pid: row.pid === 0 ? null : row.pid,
|
||||
deptSort: row.deptSort,
|
||||
enabled: row.enabled
|
||||
});
|
||||
message("已成功修改部门状态", {
|
||||
type: "success"
|
||||
});
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
row.enabled ? (row.enabled = false) : (row.enabled = true);
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
@ -164,14 +280,14 @@ export function useDept() {
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
multipleSelection,
|
||||
/** 搜索 */
|
||||
onSearch,
|
||||
/** 重置 */
|
||||
resetForm,
|
||||
/** 新增、修改部门 */
|
||||
/** 新增、编辑部门 */
|
||||
openDialog,
|
||||
/** 删除部门 */
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
handleDelete
|
||||
};
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
import type { CascaderProps } from "element-plus";
|
||||
interface FormItemProps {
|
||||
higherDeptOptions: Record<string, unknown>[];
|
||||
higherDeptOptions2: CascaderProps;
|
||||
parentId: number;
|
||||
id: number;
|
||||
pid: number;
|
||||
deptSort: number;
|
||||
name: string;
|
||||
principal: string;
|
||||
phone: string | number;
|
||||
email: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
enabled: boolean;
|
||||
remark: string;
|
||||
}
|
||||
interface FormProps {
|
||||
|
@ -13,8 +13,8 @@ export function usePublicHooks() {
|
||||
});
|
||||
|
||||
const tagStyle = computed(() => {
|
||||
return (status: number) => {
|
||||
return status === 1
|
||||
return (enabled: boolean) => {
|
||||
return enabled
|
||||
? {
|
||||
"--el-tag-text-color": isDark.value ? "#6abe39" : "#389e0d",
|
||||
"--el-tag-bg-color": isDark.value ? "#172412" : "#f6ffed",
|
||||
|
80
src/views/system/job/form.vue
Normal file
80
src/views/system/job/form.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { formRules } from "./utils/rule";
|
||||
import { FormProps } from "./utils/types";
|
||||
import { usePublicHooks } from "../hooks";
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
higherDeptOptions: [],
|
||||
jobSort: 0,
|
||||
id: 0,
|
||||
deptSort: 0,
|
||||
enabled: false,
|
||||
name: "",
|
||||
principal: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
version: 0,
|
||||
sort: 0,
|
||||
remark: ""
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
onMounted(() => {
|
||||
// 在这里编写页面加载后要执行的代码
|
||||
});
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="newFormInline"
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-row :gutter="30">
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="岗位名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入岗位名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="排序">
|
||||
<el-input-number
|
||||
v-model="newFormInline.jobSort"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="岗位状态">
|
||||
<el-switch
|
||||
v-model="newFormInline.enabled"
|
||||
inline-prompt
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
:style="switchStyle"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
229
src/views/system/job/index.vue
Normal file
229
src/views/system/job/index.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useDept } from "./utils/hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import datePicker from "@/views/components/date-picker.vue";
|
||||
import * as Job from "@/api/system/job";
|
||||
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import { message } from "@/utils/message";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
defineOptions({
|
||||
name: "Dept"
|
||||
});
|
||||
|
||||
const handleSelectionChange = val => {
|
||||
multipleSelection.value = val;
|
||||
if (val != null && val.length > 0) {
|
||||
value2.value = false;
|
||||
} else {
|
||||
value2.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteAll() {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>删除所选的</strong><strong style='color:var(--el-color-primary)'>${multipleSelection.value.length}</strong>个岗位吗?`,
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
Job.del(multipleSelection.value.map(dept => dept.id)).then(() => {
|
||||
message("已删除所选的岗位", {
|
||||
type: "success"
|
||||
});
|
||||
onSearch();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
onSearch();
|
||||
});
|
||||
}
|
||||
const exportClick = async () => {
|
||||
const response: Blob = await Job.download(null);
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(response); // 创建媒体流 url ,详细了解可自己查 URL.createObjectURL(推荐 MDN )
|
||||
|
||||
a.href = url;
|
||||
a.style.display = "none";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.parentNode.removeChild(a);
|
||||
window.URL.revokeObjectURL(url); // 删除创建的媒体流 url 对象
|
||||
message("导出成功", {
|
||||
type: "success"
|
||||
});
|
||||
};
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const value2 = ref(true);
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
multipleSelection,
|
||||
pagination,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
} = useDept();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="岗位名称:" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入岗位名称"
|
||||
clearable
|
||||
class="!w-[200px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.enabled"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
>
|
||||
<el-option label="启用" :value="true" />
|
||||
<el-option label="停用" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="createTime">
|
||||
<datePicker v-model="form.createTime" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('search')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="岗位列表"
|
||||
:columns="columns"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #add>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增岗位
|
||||
</el-button>
|
||||
</template>
|
||||
<template #delete>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="value2"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
@click="deleteAll()"
|
||||
>
|
||||
删除岗位
|
||||
</el-button>
|
||||
</template>
|
||||
<template #export>
|
||||
<el-button
|
||||
type="success"
|
||||
:icon="useRenderIcon('solar:upload-bold')"
|
||||
@click="exportClick()"
|
||||
>
|
||||
导出数据
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 32 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
default-expand-all
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:columns="dynamicColumns"
|
||||
:data="dataList"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('编辑', row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除岗位名称为${row.name}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
271
src/views/system/job/utils/hook.tsx
Normal file
271
src/views/system/job/utils/hook.tsx
Normal file
@ -0,0 +1,271 @@
|
||||
import dayjs from "dayjs";
|
||||
import editForm from "../form.vue";
|
||||
import { message } from "@/utils/message";
|
||||
import * as Job from "@/api/system/job";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import { reactive, ref, onMounted, h } from "vue";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
export function useDept() {
|
||||
const form = reactive({
|
||||
name: "",
|
||||
enabled: null,
|
||||
createTime: ""
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const dataList = reactive([]);
|
||||
const loading = ref(true);
|
||||
const multipleSelection = ref([]);
|
||||
const switchLoadMap = ref({});
|
||||
const { switchStyle } = usePublicHooks();
|
||||
|
||||
/** 分页配置 */
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 10,
|
||||
pageSize: 2,
|
||||
pageSizes: [10, 20, 50],
|
||||
currentPage: 1,
|
||||
align: "left",
|
||||
background: true
|
||||
});
|
||||
/** 表格索引 */
|
||||
const indexMethod = (index: number) => {
|
||||
return index + 1;
|
||||
};
|
||||
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
type: "selection"
|
||||
},
|
||||
{
|
||||
type: "index",
|
||||
index: indexMethod
|
||||
},
|
||||
{
|
||||
label: "岗位名称",
|
||||
prop: "name",
|
||||
width: 180,
|
||||
align: "left"
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "jobSort",
|
||||
minWidth: 70
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "enabled",
|
||||
minWidth: 100,
|
||||
cellRenderer: scope => (
|
||||
<el-switch
|
||||
v-model={scope.row.enabled}
|
||||
size={scope.props.size === "small" ? "small" : "default"}
|
||||
loading={switchLoadMap.value[scope.row.index]?.loading}
|
||||
style={switchStyle.value}
|
||||
inline-prompt
|
||||
active-value={true}
|
||||
inactive-value={false}
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
onChange={() => onChange(scope as any)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
minWidth: 200,
|
||||
prop: "createTime",
|
||||
formatter: ({ createTime }) =>
|
||||
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 160,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
function resetForm(formEl) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const queryType = new Job.JobQueryCriteria();
|
||||
if (!isAllEmpty(form.name)) {
|
||||
queryType.name = form.name;
|
||||
}
|
||||
if (!isAllEmpty(form.enabled)) {
|
||||
queryType.enabled = form.enabled;
|
||||
}
|
||||
if (!isAllEmpty(form.createTime)) {
|
||||
queryType.createTime = form.createTime;
|
||||
}
|
||||
queryType.page = pagination.currentPage - 1;
|
||||
queryType.size = pagination.pageSize;
|
||||
const depts = (await Job.get(queryType)).data;
|
||||
pagination.total = depts.totalElements;
|
||||
let newData = depts.content;
|
||||
dataList.splice(0, dataList.length); // 清空数组
|
||||
newData.forEach(x => {
|
||||
dataList.push(x);
|
||||
});
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function openDialog(title = "新增", row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}岗位`,
|
||||
props: {
|
||||
formInline: {
|
||||
higherDeptOptions: cloneDeep(dataList),
|
||||
id: row?.id ?? 0,
|
||||
name: row?.name ?? "",
|
||||
jobSort: row?.jobSort ?? 0,
|
||||
principal: row?.principal ?? "",
|
||||
phone: row?.phone ?? "",
|
||||
email: row?.email ?? "",
|
||||
sort: row?.sort ?? 0,
|
||||
version: row?.version ?? 0,
|
||||
enabled: row?.enabled ?? false,
|
||||
remark: row?.remark ?? ""
|
||||
}
|
||||
},
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
function chores() {
|
||||
message(`您${title}了岗位名称为${curData.name}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
Job.add({
|
||||
name: curData.name,
|
||||
enabled: curData.enabled,
|
||||
version: curData.version,
|
||||
jobSort: curData.jobSort
|
||||
}).finally(() => chores());
|
||||
} else if (title === "编辑") {
|
||||
Job.edit({
|
||||
id: curData.id,
|
||||
name: curData.name,
|
||||
enabled: curData.enabled,
|
||||
version: curData.version,
|
||||
jobSort: curData.jobSort
|
||||
}).finally(() => chores());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
Job.del([row.id]).then(() => {
|
||||
message(`您删除了岗位名称为${row.name}的这条数据`, { type: "success" });
|
||||
});
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function onChange({ row, index }) {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>${
|
||||
!row.enabled ? "停用" : "启用"
|
||||
}</strong><strong style='color:var(--el-color-primary)'>${
|
||||
row.name
|
||||
}</strong>用户吗?`,
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: false
|
||||
}
|
||||
);
|
||||
Job.edit({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
enabled: row.enabled,
|
||||
version: row.version,
|
||||
jobSort: row.jobSort
|
||||
});
|
||||
message("已成功修改岗位状态", {
|
||||
type: "success"
|
||||
});
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
row.enabled ? (row.enabled = false) : (row.enabled = true);
|
||||
});
|
||||
}
|
||||
function handleSizeChange(val: number) {
|
||||
pagination.pageSize = val;
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
pagination.currentPage = val;
|
||||
onSearch();
|
||||
}
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
multipleSelection,
|
||||
pagination,
|
||||
/** 搜索 */
|
||||
onSearch,
|
||||
/** 重置 */
|
||||
resetForm,
|
||||
/** 新增、编辑岗位 */
|
||||
openDialog,
|
||||
/** 删除岗位 */
|
||||
handleDelete,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
};
|
||||
}
|
8
src/views/system/job/utils/rule.ts
Normal file
8
src/views/system/job/utils/rule.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
name: [{ required: true, message: "岗位名称为必填项", trigger: "blur" }],
|
||||
jobSort: [{ required: true, message: "岗位排序为必填项", trigger: "blur" }]
|
||||
});
|
19
src/views/system/job/utils/types.ts
Normal file
19
src/views/system/job/utils/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
interface FormItemProps {
|
||||
higherDeptOptions: Record<string, unknown>[];
|
||||
parentId?: number;
|
||||
id: number;
|
||||
jobSort: number;
|
||||
name: string;
|
||||
principal: string;
|
||||
phone: string | number;
|
||||
email: string;
|
||||
sort: number;
|
||||
version: number;
|
||||
enabled: boolean;
|
||||
remark: string;
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps };
|
@ -1,26 +0,0 @@
|
||||
<!-- 初版,持续完善中 -->
|
||||
|
||||
## 字段含义
|
||||
|
||||
| 字段 | 说明 |
|
||||
| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `menuType` | 菜单类型(`0`代表菜单、`1`代表`iframe`、`2`代表外链、`3`代表按钮) |
|
||||
| `parentId` | |
|
||||
| `title` | 菜单名称(兼容国际化、非国际化,如果用国际化的写法就必须在根目录的`locales`文件夹下对应添加) |
|
||||
| `name` | 路由名称(必须唯一并且和当前路由`component`字段对应的页面里用`defineOptions`包起来的`name`保持一致) |
|
||||
| `path` | 路由路径 |
|
||||
| `component` | 组件路径(传`component`组件路径,那么`path`可以随便写,如果不传,`component`组件路径会跟`path`保持一致) |
|
||||
| `rank` | 菜单排序(平台规定只有`home`路由的`rank`才能为`0`,所以后端在返回`rank`的时候需要从非`0`开始 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E8%8F%9C%E5%8D%95%E6%8E%92%E5%BA%8F-rank)) |
|
||||
| `redirect` | 路由重定向 |
|
||||
| `icon` | 菜单图标 |
|
||||
| `extraIcon` | 右侧图标 |
|
||||
| `enterTransition` | 进场动画(页面加载动画) |
|
||||
| `leaveTransition` | 离场动画(页面加载动画) |
|
||||
| `activePath` | 菜单激活(将某个菜单激活,主要用于通过`query`或`params`传参的路由,当它们通过配置`showLink: false`后不在菜单中显示,就不会有任何菜单高亮,而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`) |
|
||||
| `auths` | 权限标识(按钮级别权限设置) |
|
||||
| `frameSrc` | 链接地址(需要内嵌的`iframe`链接地址) |
|
||||
| `frameLoading` | 加载动画(内嵌的`iframe`页面是否开启首次加载动画) |
|
||||
| `keepAlive` | 缓存页面(是否缓存该路由页面,开启后会保存该页面的整体状态,刷新后会清空状态) |
|
||||
| `hiddenTag` | 标签页(当前菜单名称或自定义信息禁止添加到标签页) |
|
||||
| `showLink` | 菜单(是否显示该菜单) |
|
||||
| `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) |
|
@ -1,326 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { formRules } from "./utils/rule";
|
||||
import { FormProps } from "./utils/types";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { IconSelect } from "@/components/ReIcon";
|
||||
import Segmented from "@/components/ReSegmented";
|
||||
import ReAnimateSelector from "@/components/ReAnimateSelector";
|
||||
import {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
frameLoadingOptions
|
||||
} from "./utils/enums";
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
menuType: 0,
|
||||
higherMenuOptions: [],
|
||||
parentId: 0,
|
||||
title: "",
|
||||
name: "",
|
||||
path: "",
|
||||
component: "",
|
||||
rank: 99,
|
||||
redirect: " ",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="newFormInline"
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-row :gutter="30">
|
||||
<re-col>
|
||||
<el-form-item label="菜单类型">
|
||||
<Segmented
|
||||
v-model="newFormInline.menuType"
|
||||
:options="menuTypeOptions"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col>
|
||||
<el-form-item label="上级菜单">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parentId"
|
||||
class="w-full"
|
||||
:options="newFormInline.higherMenuOptions"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'title',
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择上级菜单"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ transformI18n(data.title) }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="菜单名称" prop="title">
|
||||
<el-input
|
||||
v-model="newFormInline.title"
|
||||
clearable
|
||||
placeholder="请输入菜单名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="路由名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入路由名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="路由路径" prop="path">
|
||||
<el-input
|
||||
v-model="newFormInline.path"
|
||||
clearable
|
||||
placeholder="请输入路由路径"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 0"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="组件路径">
|
||||
<el-input
|
||||
v-model="newFormInline.component"
|
||||
clearable
|
||||
placeholder="请输入组件路径"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="菜单排序">
|
||||
<el-input-number
|
||||
v-model="newFormInline.rank"
|
||||
class="!w-full"
|
||||
:min="1"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 0"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="路由重定向">
|
||||
<el-input
|
||||
v-model="newFormInline.redirect"
|
||||
clearable
|
||||
placeholder="请输入默认跳转地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="菜单图标">
|
||||
<IconSelect v-model="newFormInline.icon" class="w-full" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="右侧图标">
|
||||
<el-input
|
||||
v-model="newFormInline.extraIcon"
|
||||
clearable
|
||||
placeholder="菜单名称右侧的额外图标"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="进场动画">
|
||||
<ReAnimateSelector
|
||||
v-model="newFormInline.enterTransition"
|
||||
placeholder="请选择页面进场加载动画"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="离场动画">
|
||||
<ReAnimateSelector
|
||||
v-model="newFormInline.leaveTransition"
|
||||
placeholder="请选择页面离场加载动画"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 0"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="菜单激活">
|
||||
<el-input
|
||||
v-model="newFormInline.activePath"
|
||||
clearable
|
||||
placeholder="请输入需要激活的菜单"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType === 3" :value="12" :xs="24" :sm="24">
|
||||
<!-- 按钮级别权限设置 -->
|
||||
<el-form-item label="权限标识" prop="auths">
|
||||
<el-input
|
||||
v-model="newFormInline.auths"
|
||||
clearable
|
||||
placeholder="请输入权限标识"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType === 1"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<!-- iframe -->
|
||||
<el-form-item label="链接地址">
|
||||
<el-input
|
||||
v-model="newFormInline.frameSrc"
|
||||
clearable
|
||||
placeholder="请输入 iframe 链接地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType === 1" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="加载动画">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.frameLoading ? 0 : 1"
|
||||
:options="frameLoadingOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.frameLoading = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="缓存页面">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.keepAlive ? 0 : 1"
|
||||
:options="keepAliveOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.keepAlive = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="标签页">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.hiddenTag ? 1 : 0"
|
||||
:options="hiddenTagOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.hiddenTag = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="菜单">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.showLink ? 0 : 1"
|
||||
:options="showLinkOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.showLink = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="8"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<el-form-item label="父级菜单">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.showParent ? 0 : 1"
|
||||
:options="showParentOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.showParent = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
@ -1,157 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useMenu } from "./utils/hook";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemMenu"
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
} = useMenu();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="菜单名称:" prop="title">
|
||||
<el-input
|
||||
v-model="form.title"
|
||||
placeholder="请输入菜单名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="菜单管理(初版,持续完善中)"
|
||||
:columns="columns"
|
||||
:isExpandAll="false"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增菜单
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="row.menuType !== 3"
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除菜单名称为${transformI18n(row.title)}的这条数据${row?.children?.length > 0 ? '。注意下级菜单也会一并删除,请谨慎操作' : ''}`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,94 +0,0 @@
|
||||
import type { OptionsType } from "@/components/ReSegmented";
|
||||
|
||||
const menuTypeOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "菜单",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: "iframe",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "外链",
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: "按钮",
|
||||
value: 3
|
||||
}
|
||||
];
|
||||
|
||||
const showLinkOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "显示",
|
||||
tip: "会在菜单中显示",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
tip: "不会在菜单中显示",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const keepAliveOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "缓存",
|
||||
tip: "会保存该页面的整体状态,刷新后会清空状态",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "不缓存",
|
||||
tip: "不会保存该页面的整体状态",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const hiddenTagOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "允许",
|
||||
tip: "当前菜单名称或自定义信息允许添加到标签页",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: "禁止",
|
||||
tip: "当前菜单名称或自定义信息禁止添加到标签页",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
|
||||
const showParentOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "显示",
|
||||
tip: "会显示父级菜单",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
tip: "不会显示父级菜单",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const frameLoadingOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "开启",
|
||||
tip: "有首次加载动画",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "关闭",
|
||||
tip: "无首次加载动画",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
export {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
frameLoadingOptions
|
||||
};
|
@ -1,223 +0,0 @@
|
||||
import editForm from "../form.vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import { getMenuList } from "@/api/system";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import { reactive, ref, onMounted, h } from "vue";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
export function useMenu() {
|
||||
const form = reactive({
|
||||
title: ""
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const dataList = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
const getMenuType = (type, text = false) => {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return text ? "菜单" : "primary";
|
||||
case 1:
|
||||
return text ? "iframe" : "warning";
|
||||
case 2:
|
||||
return text ? "外链" : "danger";
|
||||
case 3:
|
||||
return text ? "按钮" : "info";
|
||||
}
|
||||
};
|
||||
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "菜单名称",
|
||||
prop: "title",
|
||||
align: "left",
|
||||
cellRenderer: ({ row }) => (
|
||||
<>
|
||||
<span class="inline-block mr-1">
|
||||
{h(useRenderIcon(row.icon), {
|
||||
style: { paddingTop: "1px" }
|
||||
})}
|
||||
</span>
|
||||
<span>{transformI18n(row.title)}</span>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "菜单类型",
|
||||
prop: "menuType",
|
||||
width: 100,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag
|
||||
size={props.size}
|
||||
type={getMenuType(row.menuType)}
|
||||
effect="plain"
|
||||
>
|
||||
{getMenuType(row.menuType, true)}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "路由路径",
|
||||
prop: "path"
|
||||
},
|
||||
{
|
||||
label: "组件路径",
|
||||
prop: "component",
|
||||
formatter: ({ path, component }) =>
|
||||
isAllEmpty(component) ? path : component
|
||||
},
|
||||
{
|
||||
label: "权限标识",
|
||||
prop: "auths"
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "rank",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
prop: "showLink",
|
||||
formatter: ({ showLink }) => (showLink ? "否" : "是"),
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 210,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
function handleSelectionChange(val) {
|
||||
console.log("handleSelectionChange", val);
|
||||
}
|
||||
|
||||
function resetForm(formEl) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getMenuList(); // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id
|
||||
let newData = data;
|
||||
if (!isAllEmpty(form.title)) {
|
||||
// 前端搜索菜单名称
|
||||
newData = newData.filter(item =>
|
||||
transformI18n(item.title).includes(form.title)
|
||||
);
|
||||
}
|
||||
dataList.value = handleTree(newData); // 处理成树结构
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function formatHigherMenuOptions(treeList) {
|
||||
if (!treeList || !treeList.length) return;
|
||||
const newTreeList = [];
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
treeList[i].title = transformI18n(treeList[i].title);
|
||||
formatHigherMenuOptions(treeList[i].children);
|
||||
newTreeList.push(treeList[i]);
|
||||
}
|
||||
return newTreeList;
|
||||
}
|
||||
|
||||
function openDialog(title = "新增", row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}菜单`,
|
||||
props: {
|
||||
formInline: {
|
||||
menuType: row?.menuType ?? 0,
|
||||
higherMenuOptions: formatHigherMenuOptions(cloneDeep(dataList.value)),
|
||||
parentId: row?.parentId ?? 0,
|
||||
title: row?.title ?? "",
|
||||
name: row?.name ?? "",
|
||||
path: row?.path ?? "",
|
||||
component: row?.component ?? "",
|
||||
rank: row?.rank ?? 99,
|
||||
redirect: row?.redirect ?? "",
|
||||
icon: row?.icon ?? "",
|
||||
extraIcon: row?.extraIcon ?? "",
|
||||
enterTransition: row?.enterTransition ?? "",
|
||||
leaveTransition: row?.leaveTransition ?? "",
|
||||
activePath: row?.activePath ?? "",
|
||||
auths: row?.auths ?? "",
|
||||
frameSrc: row?.frameSrc ?? "",
|
||||
frameLoading: row?.frameLoading ?? true,
|
||||
keepAlive: row?.keepAlive ?? false,
|
||||
hiddenTag: row?.hiddenTag ?? false,
|
||||
showLink: row?.showLink ?? true,
|
||||
showParent: row?.showParent ?? false
|
||||
}
|
||||
},
|
||||
width: "45%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
function chores() {
|
||||
message(
|
||||
`您${title}了菜单名称为${transformI18n(curData.title)}的这条数据`,
|
||||
{
|
||||
type: "success"
|
||||
}
|
||||
);
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了菜单名称为${transformI18n(row.title)}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
onSearch();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
/** 搜索 */
|
||||
onSearch,
|
||||
/** 重置 */
|
||||
resetForm,
|
||||
/** 新增、修改菜单 */
|
||||
openDialog,
|
||||
/** 删除菜单 */
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
};
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
title: [{ required: true, message: "菜单名称为必填项", trigger: "blur" }],
|
||||
name: [{ required: true, message: "路由名称为必填项", trigger: "blur" }],
|
||||
path: [{ required: true, message: "路由路径为必填项", trigger: "blur" }],
|
||||
auths: [{ required: true, message: "权限标识为必填项", trigger: "blur" }]
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
interface FormItemProps {
|
||||
/** 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮)*/
|
||||
menuType: number;
|
||||
higherMenuOptions: Record<string, unknown>[];
|
||||
parentId: number;
|
||||
title: string;
|
||||
name: string;
|
||||
path: string;
|
||||
component: string;
|
||||
rank: number;
|
||||
redirect: string;
|
||||
icon: string;
|
||||
extraIcon: string;
|
||||
enterTransition: string;
|
||||
leaveTransition: string;
|
||||
activePath: string;
|
||||
auths: string;
|
||||
frameSrc: string;
|
||||
frameLoading: boolean;
|
||||
keepAlive: boolean;
|
||||
hiddenTag: boolean;
|
||||
showLink: boolean;
|
||||
showParent: boolean;
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps };
|
@ -5,15 +5,23 @@ import { FormProps } from "./utils/types";
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
id: 0,
|
||||
name: "",
|
||||
code: "",
|
||||
remark: ""
|
||||
description: "",
|
||||
dataScope: "全部",
|
||||
level: 0,
|
||||
depts: [],
|
||||
deptIds: []
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
const props1 = {
|
||||
multiple: true,
|
||||
value: "id",
|
||||
label: "name",
|
||||
checkStrictly: true
|
||||
};
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
@ -28,28 +36,68 @@ defineExpose({ getRef });
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-form-item label="角色名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入角色名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="角色标识" prop="code">
|
||||
<el-input
|
||||
v-model="newFormInline.code"
|
||||
clearable
|
||||
placeholder="请输入角色标识"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="newFormInline.remark"
|
||||
placeholder="请输入备注信息"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item label="角色名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入角色名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="角色级别" prop="level">
|
||||
<el-input-number
|
||||
v-model="newFormInline.level"
|
||||
clearable
|
||||
:min="0"
|
||||
placeholder="请输入角色级别"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="数据范围" prop="dataScope">
|
||||
<el-select
|
||||
v-model="newFormInline.dataScope"
|
||||
filterable
|
||||
placeholder="请选择数据范围"
|
||||
>
|
||||
<el-option key="全部" value="全部" label="全部" default />
|
||||
<el-option key="本级" value="本级" label="本级" />
|
||||
<el-option key="自定义" value="自定义" label="自定义" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-show="newFormInline.dataScope === '自定义'">
|
||||
<el-form-item label="数据权限" prop="deptIds">
|
||||
<el-cascader
|
||||
v-model="newFormInline.deptIds"
|
||||
placeholder="请选择部门"
|
||||
clearable
|
||||
filterable
|
||||
:props="props1"
|
||||
:show-all-levels="false"
|
||||
:options="newFormInline.depts"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ data.name }}</span>
|
||||
<span v-if="!node.isLeaf && data.children?.length > 0">
|
||||
({{ data.children?.length }})
|
||||
</span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="newFormInline.description"
|
||||
placeholder="请输入备注信息"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
@ -1,180 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useRole } from "./utils/hook";
|
||||
import tree from "./tree.vue";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import * as Role from "@/api/system/role";
|
||||
import { message } from "@/utils/message";
|
||||
import datePicker from "@/views/components/date-picker.vue";
|
||||
|
||||
// import Database from "@iconify-icons/ri/database-2-line";
|
||||
// import More from "@iconify-icons/ep/more-filled";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import Edit from "@iconify-icons/ep/edit";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import Download from "@iconify-icons/ep/download";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemRole"
|
||||
name: "Role"
|
||||
});
|
||||
|
||||
const exportClick = async () => {
|
||||
const response: Blob = await Role.download(null);
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(response); // 创建媒体流 url ,详细了解可自己查 URL.createObjectURL(推荐 MDN )
|
||||
|
||||
a.href = url;
|
||||
a.style.display = "none";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.parentNode.removeChild(a);
|
||||
window.URL.revokeObjectURL(url); // 删除创建的媒体流 url 对象
|
||||
message("导出成功", {
|
||||
type: "success"
|
||||
});
|
||||
};
|
||||
const treeRef = ref();
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
pagination,
|
||||
disabledDelete,
|
||||
disabledEdit,
|
||||
disabledEditFrom,
|
||||
treeData,
|
||||
treeLoading,
|
||||
currentRow,
|
||||
deptId,
|
||||
// buttonClass,
|
||||
onTreeSelect,
|
||||
// buttonClass,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete,
|
||||
// handleDatabase,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange
|
||||
} = useRole();
|
||||
handleCurrentChange1
|
||||
} = useRole(tableRef, treeRef);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<!-- 搜索状态栏 -->
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="角色名称:" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入角色名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
>
|
||||
<el-option label="已启用" value="1" />
|
||||
<el-option label="已停用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
:icon="useRenderIcon(Refresh)"
|
||||
@click="resetForm(formRef)"
|
||||
>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 表格 -->
|
||||
<PureTableBar
|
||||
title="角色管理(仅演示,操作后不生效)"
|
||||
:columns="columns"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<!-- 表头按钮 -->
|
||||
<template #buttons>
|
||||
<el-button
|
||||
type="success"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(Edit)"
|
||||
:disabled="disabledEdit"
|
||||
@click="openDialog('修改', disabledEditFrom)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
:disabled="disabledDelete"
|
||||
@click="openDialog()"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
:icon="useRenderIcon(Download)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 表格内容 -->
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
align-whole="center"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
stripe
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
row-key="id"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<!-- 表格数据操作按钮 -->
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Edit)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除角色名称为${row.name}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="danger"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
<div class="flex justify-between">
|
||||
<div class="w-[calc(90%-180px)]">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="角色名称:" prop="blurry">
|
||||
<el-input
|
||||
v-model="form.blurry"
|
||||
placeholder="请输入角色名称"
|
||||
clearable
|
||||
class="!w-[200px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="createTime">
|
||||
<datePicker v-model="form.createTime" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('search')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar title="角色列表" :columns="columns" @refresh="onSearch">
|
||||
<template #add>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增角色
|
||||
</el-button>
|
||||
</template>
|
||||
<template #export>
|
||||
<el-button
|
||||
type="success"
|
||||
:icon="useRenderIcon('solar:upload-bold')"
|
||||
@click="exportClick()"
|
||||
>
|
||||
导出数据
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
align-whole="center"
|
||||
showOverflowTooltip
|
||||
adaptive
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
highlight-current-row
|
||||
@selection-change="handleCurrentChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
@current-change="handleCurrentChange1"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('编辑', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除角色名称为${row.name}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
<tree
|
||||
v-model:currentRow="currentRow"
|
||||
v-model:deptId="deptId"
|
||||
class="min-w-[300px] mr-2"
|
||||
:treeData="treeData"
|
||||
:treeLoading="treeLoading"
|
||||
@tree-select="onTreeSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -183,10 +186,6 @@ const {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
|
213
src/views/system/role/tree.vue
Normal file
213
src/views/system/role/tree.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineModel, getCurrentInstance } from "vue";
|
||||
|
||||
import Dept from "@iconify-icons/ri/git-branch-line";
|
||||
// import Reset from "@iconify-icons/ri/restart-line";
|
||||
import More2Fill from "@iconify-icons/ri/more-2-fill";
|
||||
import OfficeBuilding from "@iconify-icons/ep/office-building";
|
||||
import LocationCompany from "@iconify-icons/ep/add-location";
|
||||
import { TreeKey } from "element-plus/es/components/tree/src/tree.type.mjs";
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
label: string;
|
||||
highlight?: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
treeLoading: Boolean,
|
||||
treeData: Array
|
||||
});
|
||||
const currentRow = defineModel<TreeKey[]>("currentRow");
|
||||
const deptId = defineModel<Number>("deptId");
|
||||
|
||||
const emit = defineEmits(["tree-select"]);
|
||||
|
||||
const treeRef = ref();
|
||||
const treeRef2 = ref();
|
||||
const isExpand = ref(true);
|
||||
const searchValue = ref("");
|
||||
const highlightMap = ref({});
|
||||
const { proxy } = getCurrentInstance();
|
||||
const defaultProps = {
|
||||
children: "children",
|
||||
label: "label"
|
||||
};
|
||||
const buttonClass = computed(() => {
|
||||
return [
|
||||
"!h-[20px]",
|
||||
"reset-margin",
|
||||
"!text-gray-500",
|
||||
"dark:!text-white",
|
||||
"dark:hover:!text-primary"
|
||||
];
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return data.label.includes(value);
|
||||
};
|
||||
|
||||
function toggleRowExpansionAll(status) {
|
||||
isExpand.value = status;
|
||||
const nodes = (proxy.$refs["treeRef"] as any).store._getAllNodes();
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
nodes[i].expanded = status;
|
||||
}
|
||||
}
|
||||
/** 重置部门树状态(选中状态、搜索框值、树初始化) */
|
||||
function onTreeReset() {
|
||||
highlightMap.value = {};
|
||||
searchValue.value = "";
|
||||
toggleRowExpansionAll(true);
|
||||
}
|
||||
|
||||
function testClick() {
|
||||
emit("tree-select", { id: deptId, menuIds: treeRef.value.getCheckedKeys() });
|
||||
}
|
||||
|
||||
watch(searchValue, val => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
defineExpose({ onTreeReset });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-loading="props.treeLoading"
|
||||
class="h-full bg-bg_color overflow-auto"
|
||||
:style="{ minHeight: `calc(100vh - 133px)` }"
|
||||
>
|
||||
<div class="flex items-center h-[34px]">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="16"
|
||||
><div class="grid-content ep-bg-purple" />
|
||||
菜单分配</el-col
|
||||
>
|
||||
<el-col :span="6"
|
||||
><div class="grid-content ep-bg-purple" />
|
||||
<el-button type="primary" @click="testClick">✔保存</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center h-[34px]">
|
||||
<el-input
|
||||
v-model="searchValue"
|
||||
class="ml-2"
|
||||
size="small"
|
||||
placeholder="请输入部门名称"
|
||||
clearable
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon v-show="searchValue.length === 0" class="el-input__icon">
|
||||
<IconifyIconOffline icon="search" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-dropdown :hide-on-click="false">
|
||||
<IconifyIconOffline
|
||||
class="w-[28px] cursor-pointer"
|
||||
width="18px"
|
||||
:icon="More2Fill"
|
||||
/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
@click="toggleRowExpansionAll(isExpand ? false : true)"
|
||||
>
|
||||
{{ isExpand ? "折叠全部" : "展开全部" }}
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
@click="onTreeReset"
|
||||
>
|
||||
全选
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
@click="onTreeReset"
|
||||
>
|
||||
取消全选
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-divider />
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
size="small"
|
||||
show-checkbox
|
||||
:data="treeData"
|
||||
:props="defaultProps"
|
||||
:default-checked-keys="currentRow"
|
||||
:current-node-key="treeRef2"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span
|
||||
:class="[
|
||||
'pl-1',
|
||||
'pr-1',
|
||||
'rounded',
|
||||
'flex',
|
||||
'items-center',
|
||||
'select-none',
|
||||
'hover:text-primary',
|
||||
searchValue.trim().length > 0 &&
|
||||
node.label.includes(searchValue) &&
|
||||
'text-red-500',
|
||||
highlightMap[node.id]?.highlight ? 'dark:text-primary' : ''
|
||||
]"
|
||||
:style="{
|
||||
color: highlightMap[node.id]?.highlight
|
||||
? 'var(--el-color-primary)'
|
||||
: '',
|
||||
background: highlightMap[node.id]?.highlight
|
||||
? 'var(--el-color-primary-light-7)'
|
||||
: 'transparent'
|
||||
}"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
:icon="
|
||||
data.type === 1
|
||||
? OfficeBuilding
|
||||
: data.type === 2
|
||||
? LocationCompany
|
||||
: Dept
|
||||
"
|
||||
/>
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-divider) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tree) {
|
||||
--el-tree-node-hover-bg-color: transparent;
|
||||
}
|
||||
</style>
|
@ -1,68 +1,61 @@
|
||||
import dayjs from "dayjs";
|
||||
import editForm from "../form.vue";
|
||||
import { message } from "@/utils/message";
|
||||
import { getRoleList } from "@/api/system";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import * as Role from "@/api/system/role";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import { reactive, ref, onMounted, h, toRaw } from "vue";
|
||||
import { reactive, ref, onMounted, h, toRaw, type Ref } from "vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import * as Dept from "@/api/system/dept";
|
||||
import * as Menu from "@/api/system/menu";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
import type { ApiAbstract } from "@/utils/http/ApiAbstract";
|
||||
//import { cloneDeep } from "@pureadmin/utils";
|
||||
|
||||
export function useRole() {
|
||||
export function useRole(tableRef?: Ref, treeRef?: Ref) {
|
||||
const deptList = ref();
|
||||
console.log(tableRef, treeRef);
|
||||
const form = reactive({
|
||||
name: "",
|
||||
code: "",
|
||||
status: ""
|
||||
blurry: "",
|
||||
createTime: "",
|
||||
size: 10,
|
||||
page: 0
|
||||
});
|
||||
const formRef = ref();
|
||||
const dataList = ref([]);
|
||||
const loading = ref(true);
|
||||
const switchLoadMap = ref({});
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const treeData = ref([]);
|
||||
const treeLoading = ref(true);
|
||||
const currentRow = ref([]);
|
||||
const deptId = ref<Number>();
|
||||
const nenus = ref<ApiAbstract<Menu.Menu>>();
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
background: true
|
||||
});
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "角色编号",
|
||||
prop: "id",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "角色名称",
|
||||
prop: "name",
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
label: "角色标识",
|
||||
prop: "code",
|
||||
label: "数据权限",
|
||||
prop: "dataScope",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
minWidth: 130,
|
||||
cellRenderer: scope => (
|
||||
<el-switch
|
||||
size={scope.props.size === "small" ? "small" : "default"}
|
||||
loading={switchLoadMap.value[scope.index]?.loading}
|
||||
v-model={scope.row.status}
|
||||
active-value={1}
|
||||
inactive-value={0}
|
||||
active-text="已启用"
|
||||
inactive-text="已停用"
|
||||
inline-prompt
|
||||
style={switchStyle.value}
|
||||
onChange={() => onChange(scope as any)}
|
||||
/>
|
||||
)
|
||||
label: "角色级别",
|
||||
prop: "level",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: "备注",
|
||||
prop: "remark",
|
||||
label: "描述",
|
||||
prop: "description",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
@ -89,72 +82,37 @@ export function useRole() {
|
||||
// ];
|
||||
// });
|
||||
|
||||
function onChange({ row, index }) {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>${
|
||||
row.status === 0 ? "停用" : "启用"
|
||||
}</strong><strong style='color:var(--el-color-primary)'>${
|
||||
row.name
|
||||
}</strong>吗?`,
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: false
|
||||
}
|
||||
);
|
||||
message(`已${row.status === 0 ? "停用" : "启用"}${row.name}`, {
|
||||
type: "success"
|
||||
});
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
row.status === 0 ? (row.status = 1) : (row.status = 0);
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" });
|
||||
onSearch();
|
||||
Role.del([row.id]).finally(() => onSearch());
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
console.log(`${val} items per page`);
|
||||
form.size = val - 1;
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
console.log(`current page: ${val}`);
|
||||
form.page = val - 1;
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleSelectionChange(val) {
|
||||
console.log("handleSelectionChange", val);
|
||||
function handleCurrentChange1(value: Dept.Dept) {
|
||||
deptId.value = value?.id;
|
||||
const { data } = nenus.value;
|
||||
treeData.value = cloneDeep(handleTree(data, "id", "pid"));
|
||||
currentRow.value = value?.menus?.map(item => item.id) ?? [-1];
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
getDeptTree();
|
||||
loading.value = true;
|
||||
const { data } = await getRoleList(toRaw(form));
|
||||
dataList.value = data.list;
|
||||
pagination.total = data.total;
|
||||
pagination.pageSize = data.pageSize;
|
||||
pagination.currentPage = data.currentPage;
|
||||
const { data } = await Role.get(
|
||||
Object.entries(toRaw(form))
|
||||
.filter(([_, value]) => value !== null && value !== "")
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
);
|
||||
dataList.value = data.content;
|
||||
pagination.total = data.totalElements;
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
@ -172,9 +130,14 @@ export function useRole() {
|
||||
title: `${title}角色`,
|
||||
props: {
|
||||
formInline: {
|
||||
id: row?.id ?? 0,
|
||||
name: row?.name ?? "",
|
||||
code: row?.code ?? "",
|
||||
remark: row?.remark ?? ""
|
||||
description: row?.description ?? "",
|
||||
level: row?.level ?? 0,
|
||||
dataScope: row?.dataScope ?? "全部",
|
||||
dataIds: row?.deptIds ?? [],
|
||||
deptIds: row?.depts.map(person => person.id) ?? [],
|
||||
depts: cloneDeep(deptList.value)
|
||||
}
|
||||
},
|
||||
width: "40%",
|
||||
@ -194,14 +157,24 @@ export function useRole() {
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
const obj = {
|
||||
...curData,
|
||||
depts: curData.deptIds.map(person => ({
|
||||
id: Object.values(person)[Object.values(person).length - 1]
|
||||
}))
|
||||
}; // 复制对象
|
||||
delete obj.id; // 删除指定字段
|
||||
Role.add(obj).finally(() => chores());
|
||||
} else if (title === "编辑") {
|
||||
const roleOne = {
|
||||
...curData,
|
||||
depts: curData.deptIds.map(person => ({
|
||||
id: Object.values(person)[Object.values(person).length - 1]
|
||||
}))
|
||||
};
|
||||
Role.edit(roleOne).finally(() => chores());
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -209,15 +182,35 @@ export function useRole() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 菜单权限 */
|
||||
function handleMenu() {
|
||||
message("等菜单管理页面开发后完善");
|
||||
function getDeptTree() {
|
||||
Dept.getDeptTree([]).then(data => {
|
||||
if (data.status) {
|
||||
deptList.value = handleTree(data.data, "id", "pid");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 数据权限 可自行开发 */
|
||||
// function handleDatabase() {}
|
||||
|
||||
onMounted(() => {
|
||||
function onTreeSelect({ id, menuIds }) {
|
||||
if (
|
||||
id.value !== null &&
|
||||
id.value !== undefined &&
|
||||
id.value === deptId.value
|
||||
) {
|
||||
Role.menus({
|
||||
id: id.value,
|
||||
menus: menuIds.map(person => ({
|
||||
id: person
|
||||
}))
|
||||
}).then(() => {
|
||||
onSearch();
|
||||
});
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
// 归属部门
|
||||
nenus.value = await Menu.menuTree([]);
|
||||
const { data } = nenus.value;
|
||||
treeData.value = cloneDeep(handleTree(data, "id", "pid"));
|
||||
treeLoading.value = false;
|
||||
onSearch();
|
||||
});
|
||||
|
||||
@ -226,18 +219,21 @@ export function useRole() {
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
deptList,
|
||||
pagination,
|
||||
treeData,
|
||||
treeLoading,
|
||||
currentRow,
|
||||
deptId,
|
||||
// buttonClass,
|
||||
switchStyle,
|
||||
onChange,
|
||||
onTreeSelect,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleMenu,
|
||||
handleDelete,
|
||||
// handleDatabase,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange
|
||||
handleCurrentChange1
|
||||
};
|
||||
}
|
||||
|
@ -4,5 +4,19 @@ import type { FormRules } from "element-plus";
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }],
|
||||
code: [{ required: true, message: "角色标识为必填项", trigger: "blur" }]
|
||||
code: [{ required: true, message: "角色标识为必填项", trigger: "blur" }],
|
||||
deptIds: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "" || value.lenght < 1) {
|
||||
callback(new Error("手机号为必填项"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
// trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -1,12 +1,18 @@
|
||||
// 虽然字段很少 但是抽离出来 后续有扩展字段需求就很方便了
|
||||
|
||||
interface FormItemProps {
|
||||
id: number;
|
||||
/** 角色名称 */
|
||||
name: string;
|
||||
/** 角色编号 */
|
||||
code: string;
|
||||
level: number;
|
||||
/** 数据范围 */
|
||||
dataScope: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
description: string;
|
||||
/** 备注 */
|
||||
depts: any[];
|
||||
deptIds: number[];
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
|
@ -9,15 +9,20 @@ const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
title: "新增",
|
||||
higherDeptOptions: [],
|
||||
id: null,
|
||||
parentId: 0,
|
||||
nickname: "",
|
||||
nickName: "",
|
||||
username: "",
|
||||
password: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
sex: "",
|
||||
status: 1,
|
||||
remark: ""
|
||||
gender: "男",
|
||||
enabled: false,
|
||||
remark: "",
|
||||
roleOptionsId: [],
|
||||
roleOptions: [],
|
||||
jobOptionsId: [],
|
||||
jobOptions: []
|
||||
})
|
||||
});
|
||||
|
||||
@ -51,9 +56,9 @@ defineExpose({ getRef });
|
||||
>
|
||||
<el-row :gutter="30">
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-form-item label="用户昵称" prop="nickName">
|
||||
<el-input
|
||||
v-model="newFormInline.nickname"
|
||||
v-model="newFormInline.nickName"
|
||||
clearable
|
||||
placeholder="请输入用户昵称"
|
||||
/>
|
||||
@ -104,24 +109,20 @@ defineExpose({ getRef });
|
||||
</re-col>
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="用户性别">
|
||||
<el-select
|
||||
v-model="newFormInline.sex"
|
||||
placeholder="请选择用户性别"
|
||||
class="w-full"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
<el-radio-group v-model="newFormInline.gender">
|
||||
<el-radio
|
||||
v-for="(item, index) in sexOptions"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
border
|
||||
/>
|
||||
</el-select>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="归属部门">
|
||||
<el-form-item label="归属部门" prop="parentId">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parentId"
|
||||
class="w-full"
|
||||
@ -143,25 +144,59 @@ defineExpose({ getRef });
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col
|
||||
v-if="newFormInline.title === '新增'"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="用户状态">
|
||||
<el-switch
|
||||
v-model="newFormInline.status"
|
||||
v-model="newFormInline.enabled"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
active-text="激活"
|
||||
inactive-text="锁定"
|
||||
:style="switchStyle"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="角色" prop="roleOptionsId">
|
||||
<el-select
|
||||
v-model="newFormInline.roleOptionsId"
|
||||
placeholder="请选择"
|
||||
class="w-full"
|
||||
clearable
|
||||
multiple
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in newFormInline.roleOptions"
|
||||
:key="index"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="岗位" prop="jobOptionsId">
|
||||
<el-select
|
||||
v-model="newFormInline.jobOptionsId"
|
||||
placeholder="请选择"
|
||||
class="w-full"
|
||||
clearable
|
||||
multiple
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in newFormInline.jobOptions"
|
||||
:key="index"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
|
@ -6,7 +6,7 @@ import { RoleFormProps } from "../utils/types";
|
||||
const props = withDefaults(defineProps<RoleFormProps>(), {
|
||||
formInline: () => ({
|
||||
username: "",
|
||||
nickname: "",
|
||||
nickName: "",
|
||||
roleOptions: [],
|
||||
ids: []
|
||||
})
|
||||
@ -18,14 +18,14 @@ const newFormInline = ref(props.formInline);
|
||||
<template>
|
||||
<el-form :model="newFormInline">
|
||||
<el-row :gutter="30">
|
||||
<!-- <re-col>
|
||||
<el-form-item label="用户名称" prop="username">
|
||||
<el-input disabled v-model="newFormInline.username" />
|
||||
</el-form-item>
|
||||
</re-col> -->
|
||||
<re-col>
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-input v-model="newFormInline.nickname" disabled />
|
||||
<el-form-item label="用户名称" prop="username">
|
||||
<el-input v-model="newFormInline.username" disabled />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col>
|
||||
<el-form-item label="用户昵称" prop="nickName">
|
||||
<el-input v-model="newFormInline.nickName" disabled />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col>
|
||||
|
@ -4,6 +4,7 @@ import tree from "./tree.vue";
|
||||
import { useUser } from "./utils/hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import datePicker from "@/views/components/date-picker.vue";
|
||||
|
||||
import Upload from "@iconify-icons/ri/upload-line";
|
||||
import Role from "@iconify-icons/ri/admin-line";
|
||||
@ -15,7 +16,7 @@ import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemUser"
|
||||
name: "User"
|
||||
});
|
||||
|
||||
const treeRef = ref();
|
||||
@ -68,34 +69,29 @@ const {
|
||||
<el-form-item label="用户名称:" prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请输入用户名称"
|
||||
placeholder="请输入用户名或邮箱"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
class="!w-[160px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码:" prop="phone">
|
||||
<el-input
|
||||
v-model="form.phone"
|
||||
placeholder="请输入手机号码"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
<el-form-item label="" prop="createTime">
|
||||
<datePicker v-model="form.createTime" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
class="!w-[160px]"
|
||||
>
|
||||
<el-option label="已开启" value="1" />
|
||||
<el-option label="已关闭" value="0" />
|
||||
<el-option label="激活" value="1" />
|
||||
<el-option label="锁定" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:icon="useRenderIcon('search')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
@ -107,12 +103,8 @@ const {
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="用户管理(仅演示,操作后不生效)"
|
||||
:columns="columns"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<PureTableBar title="用户管理" :columns="columns" @refresh="onSearch">
|
||||
<template #add>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@ -121,6 +113,25 @@ const {
|
||||
新增用户
|
||||
</el-button>
|
||||
</template>
|
||||
<template #export>
|
||||
<el-button
|
||||
type="success"
|
||||
:icon="useRenderIcon('solar:upload-bold')"
|
||||
@click="openDialog()"
|
||||
>
|
||||
导出数据
|
||||
</el-button>
|
||||
</template>
|
||||
<template #reset>
|
||||
<el-button
|
||||
type="danger"
|
||||
disabled
|
||||
:icon="useRenderIcon(Refresh)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
重置密码
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<div
|
||||
v-if="selectedNum > 0"
|
||||
@ -150,7 +161,6 @@ const {
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
@ -174,7 +184,7 @@ const {
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
@click="openDialog('编辑', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
@ -261,10 +271,6 @@ const {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
|
241
src/views/system/user/info.vue
Normal file
241
src/views/system/user/info.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue";
|
||||
import type { TabsPaneContext } from "element-plus";
|
||||
import { useUser } from "./utils/info";
|
||||
import { isPhone } from "@pureadmin/utils";
|
||||
import type { FormInstance } from "element-plus";
|
||||
|
||||
import Check from "@iconify-icons/ep/avatar";
|
||||
import SignIn from "@iconify-icons/ri/login-box-line";
|
||||
import NodeTree from "@iconify-icons/ri/node-tree";
|
||||
import Phone from "@iconify-icons/ep/iphone";
|
||||
import Mail from "@iconify-icons/ri/mail-fill";
|
||||
import Secure from "@iconify-icons/ri/secure-payment-fill";
|
||||
|
||||
const activeName = ref("first");
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log("tab", tab.paneName);
|
||||
console.log("event", event);
|
||||
};
|
||||
|
||||
const treeRef = ref();
|
||||
const tableRef = ref();
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const {
|
||||
userInfo,
|
||||
handleUpload,
|
||||
handleReset,
|
||||
handleResetEmail,
|
||||
submitEditUser
|
||||
} = useUser(tableRef, treeRef);
|
||||
|
||||
const user = reactive(userInfo.value.user);
|
||||
defineOptions({
|
||||
name: "UserInfo"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="justify-between">
|
||||
<el-row :gutter="30">
|
||||
<el-col :xs="8" :sm="6" :md="4" :lg="6" :xl="5">
|
||||
<div class="grid-content">
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>个人信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="el-upload">
|
||||
<el-avatar
|
||||
:size="80"
|
||||
src="https://empty"
|
||||
@click="handleUpload(userInfo.user)"
|
||||
>
|
||||
<img :src="'/avatar/' + userInfo.user.avatarName" />
|
||||
</el-avatar>
|
||||
</div>
|
||||
|
||||
<ul class="user-info">
|
||||
<li>
|
||||
<div style="height: 100%">
|
||||
<IconifyIconOffline class="check-zh" :icon="SignIn" />
|
||||
登录账号
|
||||
<div class="user-right">{{ userInfo.user.username }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<IconifyIconOffline class="check-zh" :icon="Check" />
|
||||
用户昵称
|
||||
<div class="user-right">{{ userInfo.user.nickName }}</div>
|
||||
</li>
|
||||
<li>
|
||||
<IconifyIconOffline class="check-zh" :icon="NodeTree" />
|
||||
所属部门
|
||||
<div class="user-right">{{ userInfo.user.dept.name }}</div>
|
||||
</li>
|
||||
<li>
|
||||
<IconifyIconOffline class="check-zh" :icon="Phone" />
|
||||
手机号码
|
||||
<div class="user-right">{{ userInfo.user.phone }}</div>
|
||||
</li>
|
||||
<li>
|
||||
<IconifyIconOffline class="check-zh" :icon="Mail" />
|
||||
用户邮箱
|
||||
<div class="user-right">{{ userInfo.user.email }}</div>
|
||||
</li>
|
||||
<li>
|
||||
<IconifyIconOffline class="check-zh" :icon="Secure" />
|
||||
安全设置
|
||||
<div class="user-right">
|
||||
<a @click="handleReset">修改密码 </a>
|
||||
|
||||
<a @click="handleResetEmail"> 修改邮箱</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="8" :sm="6" :md="8" :lg="18" :xl="11"
|
||||
><div class="grid-content">
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
class="demo-tabs grid-content"
|
||||
@tab-click="handleClick"
|
||||
>
|
||||
<el-tab-pane label="用户资料" name="first">
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="user"
|
||||
style="margin-top: 10px"
|
||||
size="small"
|
||||
label-width="65px"
|
||||
>
|
||||
<el-form-item
|
||||
label="昵称"
|
||||
prop="nickName"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '昵称为必填项',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 3,
|
||||
max: 12,
|
||||
message: 'Length should be 4 to 12',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
>
|
||||
<el-input
|
||||
v-model="user.nickName"
|
||||
clearable
|
||||
style="width: 35%"
|
||||
/>
|
||||
<span style=" margin-left: 10px;color: #e6a23c"
|
||||
>⚠️用户昵称不作为登录使用</span
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="手机号"
|
||||
prop="phone"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '手机号为必填项',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('手机号为必填项'));
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error('请输入正确的手机号码格式'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
>
|
||||
<el-input v-model="user.phone" clearable style="width: 35%" />
|
||||
<span style=" margin-left: 10px;color: #e6a23c"
|
||||
>⚠️手机号码不能重复</span
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-radio-group v-model="user.gender" style="width: 178px">
|
||||
<el-radio label="男" value="男">男</el-radio>
|
||||
<el-radio label="女" value="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitEditUser(ruleFormRef, user)"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志" name="second">操作日志</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div></el-col
|
||||
>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.el-col {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
min-height: 36px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.demo-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
padding: 11px 0;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid #f0f3f4;
|
||||
}
|
||||
|
||||
.user-right {
|
||||
float: right;
|
||||
|
||||
a {
|
||||
color: #317ef3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.check-zh {
|
||||
display: unset;
|
||||
}
|
||||
</style>
|
@ -1 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"/></svg>
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4h9Z"/></svg>
|
Before Width: | Height: | Size: 161 B After Width: | Height: | Size: 163 B |
@ -1 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2H2v20h2v-9h14.17l-5.5 5.5 1.41 1.42L22 12l-7.92-7.92-1.41 1.42 5.5 5.5H4z"/></svg>
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2H2v20h2v-9h14.17l-5.5 5.5l1.41 1.42L22 12l-7.92-7.92l-1.41 1.42l5.5 5.5H4V2Z"/></svg>
|
Before Width: | Height: | Size: 163 B After Width: | Height: | Size: 166 B |
@ -96,7 +96,7 @@ defineExpose({ onTreeReset });
|
||||
<div
|
||||
v-loading="props.treeLoading"
|
||||
class="h-full bg-bg_color overflow-auto"
|
||||
:style="{ minHeight: `calc(100vh - 145px)` }"
|
||||
:style="{ minHeight: `calc(100vh - 133px)` }"
|
||||
>
|
||||
<div class="flex items-center h-[34px]">
|
||||
<el-input
|
||||
@ -110,7 +110,7 @@ defineExpose({ onTreeReset });
|
||||
<el-icon class="el-input__icon">
|
||||
<IconifyIconOffline
|
||||
v-show="searchValue.length === 0"
|
||||
icon="ri:search-line"
|
||||
icon="search"
|
||||
/>
|
||||
</el-icon>
|
||||
</template>
|
||||
|
@ -10,7 +10,6 @@ const props = defineProps({
|
||||
const emit = defineEmits(["cropper"]);
|
||||
|
||||
const infos = ref();
|
||||
const popoverRef = ref();
|
||||
const refCropper = ref();
|
||||
const showPopover = ref(false);
|
||||
const cropperImg = ref<string>("");
|
||||
@ -20,22 +19,11 @@ function onCropper({ base64, blob, info }) {
|
||||
cropperImg.value = base64;
|
||||
emit("cropper", { base64, blob, info });
|
||||
}
|
||||
|
||||
function hidePopover() {
|
||||
popoverRef.value.hide();
|
||||
}
|
||||
|
||||
defineExpose({ hidePopover });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="!showPopover" element-loading-background="transparent">
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
:visible="showPopover"
|
||||
placement="right"
|
||||
width="18vw"
|
||||
>
|
||||
<el-popover :visible="showPopover" placement="right-end" width="18vw">
|
||||
<template #reference>
|
||||
<div class="w-[18vw]">
|
||||
<ReCropper
|
||||
|
@ -10,13 +10,17 @@ import { usePublicHooks } from "../../hooks";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import type { FormItemProps, RoleFormItemProps } from "../utils/types";
|
||||
import { hideTextAtIndex, getKeyList, isAllEmpty } from "@pureadmin/utils";
|
||||
import {
|
||||
getRoleIds,
|
||||
getDeptList,
|
||||
getUserList,
|
||||
getAllRoleList
|
||||
} from "@/api/system";
|
||||
hideTextAtIndex,
|
||||
getKeyList,
|
||||
isAllEmpty,
|
||||
cloneDeep
|
||||
} from "@pureadmin/utils";
|
||||
import { baseUrlAvatar } from "@/api/utils";
|
||||
import * as User from "@/api/system/user";
|
||||
import * as Dept from "@/api/system/dept";
|
||||
import * as Job from "@/api/system/job";
|
||||
import * as Role from "@/api/system/role";
|
||||
import {
|
||||
ElForm,
|
||||
ElInput,
|
||||
@ -24,22 +28,14 @@ import {
|
||||
ElProgress,
|
||||
ElMessageBox
|
||||
} from "element-plus";
|
||||
import {
|
||||
type Ref,
|
||||
h,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
computed,
|
||||
reactive,
|
||||
onMounted
|
||||
} from "vue";
|
||||
import { type Ref, h, ref, watch, computed, reactive, onMounted } from "vue";
|
||||
|
||||
export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
const form = reactive({
|
||||
// 左侧部门树的id
|
||||
deptId: "",
|
||||
username: "",
|
||||
createTime: "",
|
||||
phone: "",
|
||||
status: ""
|
||||
});
|
||||
@ -55,10 +51,13 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
const treeData = ref([]);
|
||||
const treeLoading = ref(true);
|
||||
const selectedNum = ref(0);
|
||||
/** 分页配置 */
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 15, 20],
|
||||
currentPage: 1,
|
||||
align: "left",
|
||||
background: true
|
||||
});
|
||||
const columns: TableColumnList = [
|
||||
@ -75,15 +74,21 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
},
|
||||
{
|
||||
label: "用户头像",
|
||||
prop: "avatar",
|
||||
prop: "avatarName",
|
||||
cellRenderer: ({ row }) => (
|
||||
<el-image
|
||||
fit="cover"
|
||||
preview-teleported={true}
|
||||
src={row.avatar}
|
||||
preview-src-list={Array.of(row.avatar)}
|
||||
src={baseUrlAvatar(row.avatarName)}
|
||||
preview-src-list={Array.of(baseUrlAvatar(row.avatarName))}
|
||||
class="w-[24px] h-[24px] rounded-full align-middle"
|
||||
/>
|
||||
>
|
||||
{{
|
||||
error: () => (
|
||||
<el-image src="https://element-plus.org/images/element-plus-logo.svg" />
|
||||
)
|
||||
}}
|
||||
</el-image>
|
||||
),
|
||||
width: 90
|
||||
},
|
||||
@ -94,20 +99,20 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
},
|
||||
{
|
||||
label: "用户昵称",
|
||||
prop: "nickname",
|
||||
prop: "nickName",
|
||||
minWidth: 130
|
||||
},
|
||||
{
|
||||
label: "性别",
|
||||
prop: "sex",
|
||||
prop: "gender",
|
||||
minWidth: 90,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag
|
||||
size={props.size}
|
||||
type={row.sex === 1 ? "danger" : null}
|
||||
type={row.sex === 1 ? "danger" : "success"}
|
||||
effect="plain"
|
||||
>
|
||||
{row.sex === 1 ? "女" : "男"}
|
||||
{row.gender}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
@ -122,19 +127,24 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
minWidth: 90,
|
||||
formatter: ({ phone }) => hideTextAtIndex(phone, { start: 3, end: 6 })
|
||||
},
|
||||
{
|
||||
label: "邮箱",
|
||||
prop: "email",
|
||||
minWidth: 90
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
prop: "enabled",
|
||||
minWidth: 90,
|
||||
cellRenderer: scope => (
|
||||
<el-switch
|
||||
size={scope.props.size === "small" ? "small" : "default"}
|
||||
loading={switchLoadMap.value[scope.index]?.loading}
|
||||
v-model={scope.row.status}
|
||||
active-value={1}
|
||||
inactive-value={0}
|
||||
active-text="已启用"
|
||||
inactive-text="已停用"
|
||||
v-model={scope.row.enabled}
|
||||
active-value={true}
|
||||
inactive-value={false}
|
||||
active-text="激活"
|
||||
inactive-text="锁定"
|
||||
inline-prompt
|
||||
style={switchStyle.value}
|
||||
onChange={() => onChange(scope as any)}
|
||||
@ -164,6 +174,7 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
"dark:hover:!text-primary"
|
||||
];
|
||||
});
|
||||
|
||||
// 重置的新密码
|
||||
const pwdForm = reactive({
|
||||
newPwd: ""
|
||||
@ -178,11 +189,12 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
// 当前密码强度(0-4)
|
||||
const curScore = ref();
|
||||
const roleOptions = ref([]);
|
||||
const jobOptions = ref([]);
|
||||
|
||||
function onChange({ row, index }) {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>${
|
||||
row.status === 0 ? "停用" : "启用"
|
||||
row.enabled ? "激活" : "锁定"
|
||||
}</strong><strong style='color:var(--el-color-primary)'>${
|
||||
row.username
|
||||
}</strong>用户吗?`,
|
||||
@ -203,7 +215,7 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
User.edit(row).finally(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
@ -214,7 +226,8 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
message("已成功修改用户状态", {
|
||||
type: "success"
|
||||
});
|
||||
}, 300);
|
||||
onSearch();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
row.status === 0 ? (row.status = 1) : (row.status = 0);
|
||||
@ -226,8 +239,10 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了用户编号为${row.id}的这条数据`, { type: "success" });
|
||||
onSearch();
|
||||
User.del([row.id]).then(() => {
|
||||
message(`您删除了用户编号为${row.id}的这条数据!`, { type: "success" });
|
||||
onSearch();
|
||||
});
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
@ -256,25 +271,42 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
function onbatchDel() {
|
||||
// 返回当前选中的行
|
||||
const curSelected = tableRef.value.getTableRef().getSelectionRows();
|
||||
// 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除
|
||||
message(`已删除用户编号为 ${getKeyList(curSelected, "id")} 的数据`, {
|
||||
type: "success"
|
||||
User.del(getKeyList(curSelected, "id")).then(() => {
|
||||
// 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除
|
||||
message(`已删除用户编号为 ${getKeyList(curSelected, "id")} 的数据`, {
|
||||
type: "success"
|
||||
});
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
onSearch();
|
||||
});
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
onSearch();
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getUserList(toRaw(form));
|
||||
dataList.value = data.list;
|
||||
pagination.total = data.total;
|
||||
pagination.pageSize = data.pageSize;
|
||||
pagination.currentPage = data.currentPage;
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
const queryType = new User.UserQueryCriteria();
|
||||
if (!isAllEmpty(form.username)) {
|
||||
queryType.name = form.username;
|
||||
}
|
||||
if (
|
||||
form.deptId !== null &&
|
||||
form.deptId !== "0" &&
|
||||
form.deptId !== "" &&
|
||||
form.deptId !== " "
|
||||
) {
|
||||
queryType.deptId = Number(form.deptId);
|
||||
}
|
||||
queryType.page = pagination.currentPage - 1;
|
||||
queryType.size = pagination.pageSize;
|
||||
User.get(queryType)
|
||||
.then(data => {
|
||||
data.data.content.forEach(userFor => {
|
||||
userFor["roleOptionsId"] = userFor.roles.map(x => x.id);
|
||||
userFor["jobOptionsId"] = userFor.jobs.map(x => x.id);
|
||||
});
|
||||
dataList.value = data.data.content;
|
||||
pagination.total = data.data.totalElements;
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
}
|
||||
|
||||
const resetForm = formEl => {
|
||||
@ -309,15 +341,20 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
formInline: {
|
||||
title,
|
||||
higherDeptOptions: formatHigherDeptOptions(higherDeptOptions.value),
|
||||
id: row?.id,
|
||||
parentId: row?.dept.id ?? 0,
|
||||
nickname: row?.nickname ?? "",
|
||||
username: row?.username ?? "",
|
||||
nickName: row?.nickName ?? "",
|
||||
username: row?.nickName ?? "",
|
||||
password: row?.password ?? "",
|
||||
phone: row?.phone ?? "",
|
||||
email: row?.email ?? "",
|
||||
sex: row?.sex ?? "",
|
||||
status: row?.status ?? 1,
|
||||
remark: row?.remark ?? ""
|
||||
gender: row?.gender ?? "男",
|
||||
enabled: row?.enabled ?? 1,
|
||||
remark: row?.remark ?? "",
|
||||
jobOptionsId: row?.jobOptionsId ?? [],
|
||||
roleOptionsId: row?.roleOptionsId ?? [],
|
||||
roleOptions: roleOptions?.value ?? [],
|
||||
jobOptions: jobOptions?.value ?? []
|
||||
}
|
||||
},
|
||||
width: "46%",
|
||||
@ -329,7 +366,7 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
function chores() {
|
||||
message(`您${title}了用户名称为${curData.username}的这条数据`, {
|
||||
message(`您${title}了用户名称为${curData.nickName}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
@ -337,14 +374,30 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
const userClone = cloneDeep(curData);
|
||||
userClone["dept"] = { id: userClone.parentId };
|
||||
userClone["roles"] = userClone.roleOptionsId.map(x => ({
|
||||
id: x
|
||||
}));
|
||||
userClone["jobs"] = userClone.jobOptionsId.map(x => ({
|
||||
id: x
|
||||
}));
|
||||
delete userClone.title;
|
||||
delete userClone.higherDeptOptions;
|
||||
delete userClone.parentId;
|
||||
delete userClone.roleOptions;
|
||||
delete userClone.jobOptions;
|
||||
delete userClone.roleOptionsId;
|
||||
delete userClone.jobOptionsId;
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
User.add(userClone);
|
||||
chores();
|
||||
console.log("curData", userClone);
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
User.edit(userClone).finally(() => {
|
||||
chores();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -352,7 +405,6 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
});
|
||||
}
|
||||
|
||||
const cropRef = ref();
|
||||
/** 上传头像 */
|
||||
function handleUpload(row) {
|
||||
addDialog({
|
||||
@ -362,17 +414,15 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () =>
|
||||
h(croppingUpload, {
|
||||
ref: cropRef,
|
||||
imgSrc: row.avatar,
|
||||
imgSrc: baseUrlAvatar(row.avatarName),
|
||||
onCropper: info => (avatarInfo.value = info)
|
||||
}),
|
||||
beforeSure: done => {
|
||||
console.log("裁剪后的图片信息:", avatarInfo.value);
|
||||
User.updateAvatarByid({ id: row.id, avatar: avatarInfo.value.blob });
|
||||
// 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
},
|
||||
closeCallBack: () => cropRef.value.hidePopover()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -445,7 +495,6 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
message(`已成功重置 ${row.username} 用户的密码`, {
|
||||
type: "success"
|
||||
});
|
||||
console.log(pwdForm.newPwd);
|
||||
// 根据实际业务使用pwdForm.newPwd和row里的某些字段去调用重置用户密码接口即可
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
@ -457,14 +506,15 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
|
||||
/** 分配角色 */
|
||||
async function handleRole(row) {
|
||||
const roleIds = row.roles.map(x => x.id);
|
||||
// 选中的角色列表
|
||||
const ids = (await getRoleIds({ userId: row.id })).data ?? [];
|
||||
const ids = roleIds ?? [];
|
||||
addDialog({
|
||||
title: `分配 ${row.username} 用户的角色`,
|
||||
props: {
|
||||
formInline: {
|
||||
username: row?.username ?? "",
|
||||
nickname: row?.nickname ?? "",
|
||||
nickName: row?.nickName ?? "",
|
||||
roleOptions: roleOptions.value ?? [],
|
||||
ids
|
||||
}
|
||||
@ -476,9 +526,25 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
contentRenderer: () => h(roleForm),
|
||||
beforeSure: (done, { options }) => {
|
||||
const curData = options.props.formInline as RoleFormItemProps;
|
||||
console.log("curIds", curData.ids);
|
||||
// 根据实际业务使用curData.ids和row里的某些字段去调用修改角色接口即可
|
||||
done(); // 关闭弹框
|
||||
const userClone = cloneDeep(row);
|
||||
userClone["dept"] = { id: userClone.parentId };
|
||||
userClone["roles"] = curData.ids.map(x => ({
|
||||
id: x
|
||||
}));
|
||||
userClone["jobs"] = userClone.jobOptionsId.map(x => ({
|
||||
id: x
|
||||
}));
|
||||
delete userClone.title;
|
||||
delete userClone.higherDeptOptions;
|
||||
delete userClone.parentId;
|
||||
delete userClone.roleOptions;
|
||||
delete userClone.jobOptions;
|
||||
delete userClone.roleOptionsId;
|
||||
delete userClone.jobOptionsId;
|
||||
User.edit(userClone).finally(() => {
|
||||
done();
|
||||
onSearch();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -488,13 +554,15 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
onSearch();
|
||||
|
||||
// 归属部门
|
||||
const { data } = await getDeptList();
|
||||
higherDeptOptions.value = handleTree(data);
|
||||
treeData.value = handleTree(data);
|
||||
// const { data } = await getDeptList();
|
||||
const { data } = await Dept.getDeptTree({ enabled: true });
|
||||
higherDeptOptions.value = handleTree(data, "id", "pid");
|
||||
treeData.value = handleTree(data, "id", "pid");
|
||||
treeLoading.value = false;
|
||||
|
||||
// 角色列表
|
||||
roleOptions.value = (await getAllRoleList()).data;
|
||||
roleOptions.value = (await Role.get()).data.content;
|
||||
jobOptions.value = (await Job.get()).data.content;
|
||||
});
|
||||
|
||||
return {
|
||||
|
321
src/views/system/user/utils/info.tsx
Normal file
321
src/views/system/user/utils/info.tsx
Normal file
@ -0,0 +1,321 @@
|
||||
import { type Ref, h, ref, watch, reactive, onMounted, computed } from "vue";
|
||||
import croppingUpload from "../upload.vue";
|
||||
import { baseUrlAvatar } from "@/api/utils";
|
||||
import { zxcvbn } from "@zxcvbn-ts/core";
|
||||
import { isAllEmpty, isNull, isEmail } from "@pureadmin/utils";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import Cookies from "js-cookie";
|
||||
import * as User from "@/api/system/user";
|
||||
import {
|
||||
ElForm,
|
||||
ElInput,
|
||||
ElFormItem,
|
||||
ElProgress,
|
||||
ElButton
|
||||
} from "element-plus";
|
||||
import { message } from "@/utils/message";
|
||||
import { formRulesPwd, formRulesEmail } from "./rule";
|
||||
import type { FormInstance } from "element-plus";
|
||||
|
||||
export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
console.log(tableRef, treeRef);
|
||||
// 上传头像信息
|
||||
const avatarInfo = ref();
|
||||
const ruleFormRef = ref();
|
||||
// 重置的新密码
|
||||
const pwdForm = reactive({
|
||||
newPwd: "",
|
||||
oldPwd: "",
|
||||
newPwdCop: ""
|
||||
});
|
||||
const pwdProgress = [
|
||||
{ color: "#e74242", text: "非常弱" },
|
||||
{ color: "#EFBD47", text: "弱" },
|
||||
{ color: "#ffa500", text: "一般" },
|
||||
{ color: "#1bbf1b", text: "强" },
|
||||
{ color: "#008000", text: "非常强" }
|
||||
];
|
||||
const ruleEmailFormRef = ref();
|
||||
|
||||
// 重置的新邮箱
|
||||
const emailForm = reactive({
|
||||
pwd: "",
|
||||
code: "",
|
||||
email: ""
|
||||
});
|
||||
// 当前密码强度(0-4)
|
||||
const curScore = ref();
|
||||
const form = reactive({
|
||||
// 左侧部门树的id
|
||||
deptId: "",
|
||||
username: "",
|
||||
createTime: "",
|
||||
phone: "",
|
||||
status: ""
|
||||
});
|
||||
/** 用户信息 */
|
||||
const userInfo = computed(() => {
|
||||
return JSON.parse(Cookies.get("userInfo"));
|
||||
});
|
||||
/** 获取邮箱验证码 */
|
||||
const getEmailCode = email => {
|
||||
if (isNull(email)) {
|
||||
message("email必填", {
|
||||
type: "error"
|
||||
});
|
||||
} else if (!isEmail(email)) {
|
||||
message("email格式错误", {
|
||||
type: "error"
|
||||
});
|
||||
} else {
|
||||
console.log("email", email);
|
||||
User.resetEmail(email)
|
||||
.then(() => {
|
||||
message("更换邮箱成功", {
|
||||
type: "success"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
message(`${error}`, {
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
onMounted(async () => {
|
||||
console.log(userInfo);
|
||||
});
|
||||
|
||||
/** 上传头像 */
|
||||
function handleUpload(row) {
|
||||
addDialog({
|
||||
title: "裁剪、上传头像",
|
||||
width: "50%",
|
||||
draggable: false,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () =>
|
||||
h(croppingUpload, {
|
||||
imgSrc: baseUrlAvatar(row.avatarName),
|
||||
onCropper: info => (avatarInfo.value = info)
|
||||
}),
|
||||
beforeSure: done => {
|
||||
console.log("avatarInfo.value", avatarInfo.value);
|
||||
User.updateAvatarByid({ id: row.id, avatar: avatarInfo.value.blob });
|
||||
// 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可
|
||||
done(); // 关闭弹框
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
pwdForm,
|
||||
({ newPwd }) =>
|
||||
(curScore.value = isAllEmpty(newPwd) ? -1 : zxcvbn(newPwd).score)
|
||||
);
|
||||
/** 重置密码 */
|
||||
function handleReset() {
|
||||
addDialog({
|
||||
title: `重置 ${userInfo.value.user.nickName} 用户的密码`,
|
||||
width: "30%",
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => (
|
||||
<>
|
||||
<ElForm
|
||||
ref={ruleFormRef}
|
||||
model={pwdForm}
|
||||
{...{ rules: formRulesPwd }}
|
||||
>
|
||||
<ElFormItem prop="oldPwd" label="请输入旧密码:">
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={pwdForm.oldPwd}
|
||||
placeholder="请输入旧密码"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="newPwd" label="请输入新密码:">
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={pwdForm.newPwd}
|
||||
placeholder="请输入新密码"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<div class="mt-4 flex">
|
||||
{pwdProgress.map(({ color, text }, idx) => (
|
||||
<div
|
||||
class="w-[19vw]"
|
||||
style={{ marginLeft: idx !== 0 ? "4px" : 0 }}
|
||||
>
|
||||
<ElProgress
|
||||
striped
|
||||
striped-flow
|
||||
duration={curScore.value === idx ? 6 : 0}
|
||||
percentage={curScore.value >= idx ? 100 : 0}
|
||||
color={color}
|
||||
stroke-width={10}
|
||||
show-text={false}
|
||||
/>
|
||||
<p
|
||||
class="text-center"
|
||||
style={{ color: curScore.value === idx ? color : "" }}
|
||||
>
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ElFormItem prop="newPwdCop" label="请确认新密码:">
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={pwdForm.newPwdCop}
|
||||
placeholder="请确认新密码"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</>
|
||||
),
|
||||
closeCallBack: () =>
|
||||
Object.assign(pwdForm, {
|
||||
newPwd: "",
|
||||
oldPwd: "",
|
||||
newPwdCop: ""
|
||||
}),
|
||||
beforeSure: done => {
|
||||
ruleFormRef.value.validate(valid => {
|
||||
if (valid) {
|
||||
if (pwdForm.newPwd !== pwdForm.newPwdCop) {
|
||||
message(`两次密码不想等`, {
|
||||
type: "error"
|
||||
});
|
||||
} else {
|
||||
// 表单规则校验通过
|
||||
message(`已成功重置 ${userInfo.value.user.nickName} 用户的密码`, {
|
||||
type: "success"
|
||||
});
|
||||
// 根据实际业务使用pwdForm.newPwd和row里的某些字段去调用重置用户密码接口即可
|
||||
done(); // 关闭弹框
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
/** 更换邮箱 */
|
||||
function handleResetEmail() {
|
||||
addDialog({
|
||||
title: `更换 ${userInfo.value.user.nickName} 用户的邮箱`,
|
||||
width: "30%",
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => (
|
||||
<>
|
||||
<ElForm
|
||||
ref={ruleEmailFormRef}
|
||||
model={emailForm}
|
||||
{...{ rules: formRulesEmail }}
|
||||
>
|
||||
<ElFormItem prop="email" label="新邮箱:">
|
||||
<ElInput
|
||||
clearable
|
||||
type="email"
|
||||
v-model={emailForm.email}
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="code" label="验证码:">
|
||||
<ElInput
|
||||
clearable
|
||||
type="text"
|
||||
v-model={emailForm.code}
|
||||
placeholder="请输入验证码"
|
||||
v-slots={{
|
||||
append: () => (
|
||||
<ElButton onClick={() => getEmailCode(emailForm.email)}>
|
||||
获取验证码
|
||||
</ElButton>
|
||||
)
|
||||
}}
|
||||
></ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="pwd" label="当前密码:">
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={emailForm.pwd}
|
||||
placeholder="请输入当前密码"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</>
|
||||
),
|
||||
closeCallBack: () =>
|
||||
Object.assign(emailForm, {
|
||||
pwd: "",
|
||||
code: "",
|
||||
email: ""
|
||||
}),
|
||||
beforeSure: done => {
|
||||
ruleEmailFormRef.value.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("emailForm", emailForm);
|
||||
User.updateEmail({
|
||||
code: emailForm.code,
|
||||
pass: emailForm.pwd,
|
||||
email: emailForm.email
|
||||
})
|
||||
.then(() => {
|
||||
// 表单规则校验通过
|
||||
message(
|
||||
`已成功更换 ${userInfo.value.user.nickName} 用户的邮箱`,
|
||||
{
|
||||
type: "success"
|
||||
}
|
||||
);
|
||||
// 根据实际业务使用pwdForm.newPwd和row里的某些字段去调用重置用户密码接口即可
|
||||
done(); // 关闭弹框
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error", error.response);
|
||||
console.log("error", error.response.data.message);
|
||||
message(`验证码错误:${error.response.data.message}`, {
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
//更新用户谢谢
|
||||
const submitEditUser = async (formEl: FormInstance | undefined, userI) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
console.log("userI!", userI);
|
||||
User.editUser({
|
||||
id: userI.id,
|
||||
nickName: userI.nickName,
|
||||
gender: userI.gender,
|
||||
phone: userI.phone
|
||||
});
|
||||
} else {
|
||||
console.log("error submit!", fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
return {
|
||||
form,
|
||||
userInfo,
|
||||
handleUpload,
|
||||
handleReset,
|
||||
handleResetEmail,
|
||||
submitEditUser
|
||||
};
|
||||
}
|
@ -4,14 +4,15 @@ import { isPhone, isEmail } from "@pureadmin/utils";
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
nickname: [{ required: true, message: "用户昵称为必填项", trigger: "blur" }],
|
||||
nickName: [{ required: true, message: "用户昵称为必填项", trigger: "blur" }],
|
||||
username: [{ required: true, message: "用户名称为必填项", trigger: "blur" }],
|
||||
password: [{ required: true, message: "用户密码为必填项", trigger: "blur" }],
|
||||
phone: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback();
|
||||
callback(new Error("手机号为必填项"));
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error("请输入正确的手机号码格式"));
|
||||
} else {
|
||||
@ -24,9 +25,10 @@ export const formRules = reactive(<FormRules>{
|
||||
],
|
||||
email: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback();
|
||||
callback(new Error("邮箱为必填项"));
|
||||
} else if (!isEmail(value)) {
|
||||
callback(new Error("请输入正确的邮箱格式"));
|
||||
} else {
|
||||
@ -35,5 +37,69 @@ export const formRules = reactive(<FormRules>{
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
parentId: [{ required: true, message: "部门为必填项", trigger: "blur" }],
|
||||
roleOptionsId: [{ required: true, message: "角色为必填项", trigger: "blur" }],
|
||||
jobOptionsId: [{ required: true, message: "岗位为必填项", trigger: "blur" }]
|
||||
});
|
||||
|
||||
/** 自定义修改密码规则校验 */
|
||||
export const formRulesPwd = reactive(<FormRules>{
|
||||
oldPwd: [{ required: true, message: "旧密码为必填项", trigger: "blur" }],
|
||||
newPwd: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("新密码为必填项"));
|
||||
} else if (value.length < 6) {
|
||||
callback(new Error("密码长度小于6"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
newPwdCop: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("确认新密码为必填项"));
|
||||
} else if (value.length < 6) {
|
||||
callback(new Error("密码长度小于6"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
/** 自定义修改邮箱规则校验 */
|
||||
export const formRulesEmail = reactive(<FormRules>{
|
||||
email: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("邮箱为必填项"));
|
||||
} else if (!isEmail(value)) {
|
||||
callback(new Error("请输入正确的邮箱格式"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: "验证码为必填项", trigger: "blur" },
|
||||
{ min: 4, max: 8, message: "Length should be 4 to 8", trigger: "blur" }
|
||||
],
|
||||
pwd: [
|
||||
{ required: true, message: "密码为必填项", trigger: "blur" },
|
||||
{ min: 6, max: 18, message: "Length should be 4 to 8", trigger: "blur" }
|
||||
]
|
||||
});
|
||||
|
@ -4,18 +4,22 @@ interface FormItemProps {
|
||||
title: string;
|
||||
higherDeptOptions: Record<string, unknown>[];
|
||||
parentId: number;
|
||||
nickname: string;
|
||||
username: string;
|
||||
nickName: string;
|
||||
password: string;
|
||||
phone: string | number;
|
||||
email: string;
|
||||
sex: string | number;
|
||||
status: number;
|
||||
gender: string | number;
|
||||
enabled: boolean;
|
||||
dept?: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
};
|
||||
remark: string;
|
||||
roleOptionsId: number[];
|
||||
roleOptions: Record<string, number>[];
|
||||
jobOptionsId: number[];
|
||||
jobOptions: Record<string, number>[];
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
@ -23,7 +27,7 @@ interface FormProps {
|
||||
|
||||
interface RoleFormItemProps {
|
||||
username: string;
|
||||
nickname: string;
|
||||
nickName: string;
|
||||
/** 角色列表 */
|
||||
roleOptions: any[];
|
||||
/** 选中的角色列表 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user