perf: 系统管理

This commit is contained in:
pan 2024-03-18 15:53:25 +08:00
parent 7443ac3ac4
commit 9bac6333fb
45 changed files with 2871 additions and 1405 deletions

View File

@ -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

View File

@ -35,9 +35,10 @@ menus:
hsAbout: 关于
hssysManagement: 系统管理
hsUser: 用户管理
hsRole: 角色管理
hsRole: 权限管理
hsSystemMenu: 菜单管理
hsDept: 部门管理
hsJob: 角色管理
hssysMonitor: 系统监控
hsOnlineUser: 在线用户
hsLoginLog: 登录日志

View File

@ -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
View 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
View 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
View 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
View 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
View 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"));
}

View 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>

View File

@ -58,7 +58,7 @@ const { locale, translationCh, translationEn } = useTranslationLang();
const ruleForm = reactive({
username: "admin",
password: "admin123",
password: "123456",
verifyCode: ""
});

View File

@ -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"

View File

@ -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;

View File

@ -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加父节点parentIdparentId取父节点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加父节点parentIdparentId取父节点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
};
}

View File

@ -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 {

View File

@ -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",

View 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>

View 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>

View 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
};
}

View 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" }]
});

View 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 };

View File

@ -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) |

View File

@ -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>

View File

@ -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>

View File

@ -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
};

View File

@ -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加父节点parentIdparentId取父节点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
};
}

View File

@ -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" }]
});

View File

@ -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 };

View File

@ -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>

View File

@ -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;

View 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>

View File

@ -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
};
}

View File

@ -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 即可
}
]
});

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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;

View 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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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 {

View 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
};
}

View File

@ -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" }
]
});

View File

@ -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[];
/** 选中的角色列表 */