refactor(projects): 页面布局调整为rdms风格

This commit is contained in:
2026-04-15 09:35:54 +08:00
parent a6fc7b48dc
commit e22f6550ae
21 changed files with 990 additions and 188 deletions

View File

@@ -37,6 +37,11 @@ const treeProps = {
label: 'name'
} as const;
function applyCheckedKeys(keys: number[]) {
checkedKeys.value = [...keys];
treeRef.value?.setCheckedKeys(keys);
}
function getTagType(type: Api.SystemManage.MenuType): UI.ThemeColor {
const tagMap: Record<Api.SystemManage.MenuType, UI.ThemeColor> = {
1: 'info',
@@ -83,8 +88,7 @@ function collectExpandableNodeIds(nodes: Api.SystemManage.MenuSimple[]) {
async function loadRoleMenus() {
if (!props.role) {
checkedKeys.value = [];
treeRef.value?.setCheckedKeys([]);
applyCheckedKeys([]);
treeRef.value?.filter(filterKeyword.value);
return;
}
@@ -96,14 +100,14 @@ async function loadRoleMenus() {
permissionLoading.value = false;
if (error) {
checkedKeys.value = [];
treeRef.value?.setCheckedKeys([]);
applyCheckedKeys([]);
treeRef.value?.filter(filterKeyword.value);
return;
}
checkedKeys.value = data;
treeRef.value?.setCheckedKeys(data);
// Role-menu bindings are exact IDs from the backend, so tree echo must not
// cascade parent checks down to unrelated descendants.
applyCheckedKeys(data);
treeRef.value?.filter(filterKeyword.value);
}
@@ -131,7 +135,7 @@ async function handleSave() {
return;
}
checkedKeys.value = menuIds;
checkedKeys.value = [...menuIds];
window.$message?.success($t('common.modifySuccess'));
emit('saved');
@@ -153,7 +157,7 @@ watch(
() => props.menuTree.length,
value => {
if (value && props.role) {
treeRef.value?.setCheckedKeys(checkedKeys.value);
applyCheckedKeys(checkedKeys.value);
treeRef.value?.filter(filterKeyword.value);
}
}
@@ -203,6 +207,7 @@ watch(
ref="treeRef"
node-key="id"
show-checkbox
check-strictly
:default-expanded-keys="defaultExpandedKeys"
:data="menuTree"
:props="treeProps"

View File

@@ -15,20 +15,21 @@
* - 叶子节点:基层员工,没有下级
*/
import {nextTick, onMounted, reactive, ref} from 'vue';
import type {ElTree} from 'element-plus';
import {ElButton, ElPopconfirm, ElTag} from 'element-plus';
import {useBoolean} from '@sa/hooks';
import { nextTick, onMounted, reactive, ref } from 'vue';
import type { ElTree } from 'element-plus';
import { ElButton, ElPopconfirm, ElTag } from 'element-plus';
import { useBoolean } from '@sa/hooks';
import {
fetchBatchDeleteUserManagementRelation,
fetchDeleteUserManagementRelation, fetchGetUserListByDeptId,
fetchDeleteUserManagementRelation,
fetchGetUserListByDeptId,
fetchGetUserManagementRelationQuery,
fetchGetUserManagementRelationTree,
fetchGetUserManagementRelationTree
} from '@/service/api';
import RelationOperateDialog from './modules/relation-operate-dialog.vue';
import RelationSearch from './modules/relation-search.vue';
defineOptions({name: 'UserManagementRelation'});
defineOptions({ name: 'UserManagementRelation' });
/**
* 组件 userQuery 定义
@@ -40,8 +41,8 @@ interface userQuery {
deptId?: number | null;
}
//从user的index组件访问带人关系fromUserIndex为true否则false; dept=100是灿能电力的id
const {fromUserIndex = false, deptId = 100} = defineProps<userQuery>()
// 从user的index组件访问带人关系fromUserIndex为true否则false; dept=100是灿能电力的id
const { fromUserIndex = false, deptId = 100 } = defineProps<userQuery>();
/**
* 初始化搜索参数
@@ -86,7 +87,7 @@ const treeProps: any = {
* 获取用户简单列表,供搜索组件和对话框组件共享使用
*/
async function loadUserList() {
const {data, error} = await fetchGetUserListByDeptId(deptId);
const { data, error } = await fetchGetUserListByDeptId(deptId);
if (!error) {
userList.value = data || [];
}
@@ -103,10 +104,10 @@ async function loadTreeData() {
try {
// 默认不是来自user的index组件访问且deptId=100查询灿能电力及其以下所有部门的用户的带人关系
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
fromUserIndex: fromUserIndex,
deptId: deptId
fromUserIndex,
deptId
};
const {data, error} = await fetchGetUserManagementRelationTree(query);
const { data, error } = await fetchGetUserManagementRelationTree(query);
if (!error) {
treeData.value = data || [];
@@ -127,7 +128,7 @@ async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelatio
loading.value = true;
try {
const {data, error} = await fetchGetUserManagementRelationQuery(query);
const { data, error } = await fetchGetUserManagementRelationQuery(query);
if (!error) {
treeData.value = data || [];
@@ -164,8 +165,8 @@ async function handleSearch() {
// 有搜索条件,调用查询接口
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
subordinateUserId: searchParams.subordinateUserId,
fromUserIndex: fromUserIndex,
deptId: deptId
fromUserIndex,
deptId
};
await loadTreeDataByQuery(query);
} else {
@@ -188,7 +189,7 @@ function resetSearchParams() {
}
// 对话框相关状态
const {bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal} = useBoolean();
const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal } = useBoolean();
const operateType = ref<UI.TableOperateType>('add');
const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null);
@@ -203,14 +204,14 @@ function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
// 否则默认管理者为当前登录用户(在对话框组件中处理)
editingData.value = item
? {
id: null,
managerUserId: item.userId,
subordinateUserId: null,
effectiveFrom: null,
effectiveUntil: null,
remark: null,
createTime: Date.now()
}
id: null,
managerUserId: item.userId,
subordinateUserId: null,
effectiveFrom: null,
effectiveUntil: null,
remark: null,
createTime: Date.now()
}
: null;
openOperateModal();
}
@@ -225,14 +226,14 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
// 构建树节点数据为编辑所需格式
editingData.value = item.id
? {
id: item.id,
managerUserId: item.managerUserId,
subordinateUserId: item.userId,
effectiveFrom: null,
effectiveUntil: null,
remark: null,
createTime: Date.now()
}
id: item.id,
managerUserId: item.managerUserId,
subordinateUserId: item.userId,
effectiveFrom: null,
effectiveUntil: null,
remark: null,
createTime: Date.now()
}
: null;
openOperateModal();
}
@@ -243,7 +244,7 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
* @param item 要删除的关系记录
*/
async function handleDelete(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
const {error} = await fetchDeleteUserManagementRelation(item.id);
const { error } = await fetchDeleteUserManagementRelation(item.id);
if (error) {
return;
@@ -263,7 +264,7 @@ async function handleBatchDelete() {
return;
}
const {error} = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
const { error } = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
if (error) {
return;
@@ -350,23 +351,23 @@ onMounted(async () => {
<div class="flex items-center gap-10px">
<ElButton plain type="primary" @click="openAdd()">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
<icon-ic-round-plus class="text-icon" />
</template>
新增
</ElButton>
<!-- <ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete">-->
<!-- <template #reference>-->
<!-- <ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0">-->
<!-- <template #icon>-->
<!-- <icon-ic-round-delete class="text-icon"/>-->
<!-- </template>-->
<!-- 批量删除-->
<!-- </ElButton>-->
<!-- </template>-->
<!-- </ElPopconfirm>-->
<!-- <ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete">-->
<!-- <template #reference>-->
<!-- <ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0">-->
<!-- <template #icon>-->
<!-- <icon-ic-round-delete class="text-icon"/>-->
<!-- </template>-->
<!-- 批量删除-->
<!-- </ElButton>-->
<!-- </template>-->
<!-- </ElPopconfirm>-->
<ElButton @click="reloadTreeData">
<template #icon>
<icon-ic-round-refresh class="text-icon"/>
<icon-ic-round-refresh class="text-icon" />
</template>
刷新
</ElButton>
@@ -390,18 +391,18 @@ onMounted(async () => {
<div class="flex flex-1 items-center justify-between">
<span class="flex items-center gap-8px">
<span>{{ node.label }}</span>
<!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级{{ data.managerNickname }}</ElTag>-->
<!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级{{ data.managerNickname }}</ElTag>-->
</span>
<div class="flex items-center">
<ElButton link type="primary" size="default" @click.stop="openAdd(data)">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
<icon-ic-round-plus class="text-icon" />
</template>
新增
</ElButton>
<ElButton link type="primary" size="small" @click.stop="openEdit(data)">
<template #icon>
<icon-ic-round-edit class="text-icon"/>
<icon-ic-round-edit class="text-icon" />
</template>
编辑
</ElButton>
@@ -413,7 +414,7 @@ onMounted(async () => {
<template #reference>
<ElButton link type="danger" size="small">
<template #icon>
<icon-ic-round-delete class="text-icon"/>
<icon-ic-round-delete class="text-icon" />
</template>
删除
</ElButton>

View File

@@ -284,26 +284,26 @@ watch(visible, value => {
</ElRow>
<ElRow :gutter="16">
<ElCol :span="12">
<ElFormItem label="生效开始时间" prop="effectiveFrom" style="width:100%">
<ElFormItem label="生效开始时间" prop="effectiveFrom" style="width: 100%">
<ElDatePicker
v-model="model.effectiveFrom"
class="w-full"
type="datetime"
placeholder="请选择生效开始时间"
value-format="x"
style="width:100%"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="生效结束时间" prop="effectiveUntil" style="width:100%">
<ElFormItem label="生效结束时间" prop="effectiveUntil" style="width: 100%">
<ElDatePicker
v-model="model.effectiveUntil"
class="w-full"
type="datetime"
placeholder="请选择生效结束时间"
value-format="x"
style="width:100%"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
@@ -319,6 +319,4 @@ watch(visible, value => {
</BusinessFormDialog>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@@ -14,9 +14,9 @@
* - 用户列表通过 props 传入,由父组件统一管理
*/
import {defineEmits, defineModel, defineOptions} from 'vue';
import { defineEmits, defineModel, defineOptions } from 'vue';
defineOptions({name: 'RelationSearch'});
defineOptions({ name: 'RelationSearch' });
/**
* 组件 Emits 定义
@@ -43,7 +43,7 @@ defineProps<Props>();
/**
* 搜索参数模型,支持双向绑定
*/
const model = defineModel<Api.SystemManage.UserManagementRelationQueryReqVO>('model', {required: true});
const model = defineModel<Api.SystemManage.UserManagementRelationQueryReqVO>('model', { required: true });
/**
* 重置搜索
@@ -83,7 +83,7 @@ function search() {
<ElCol :lg="8" :md="12" :sm="12">
<ElFormItem label="用户名" prop="subordinateUserId" style="margin-left: -50px">
<ElSelect v-model="model.subordinateUserId" class="w-full" placeholder="请选择用户名" clearable filterable>
<ElOption v-for="user in userList" :key="user.id" :label="user.nickname" :value="user.id"/>
<ElOption v-for="user in userList" :key="user.id" :label="user.nickname" :value="user.id" />
</ElSelect>
</ElFormItem>
</ElCol>

View File

@@ -1,10 +1,10 @@
<script setup lang="tsx">
import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue';
import type {TableInstance} from 'element-plus';
import {ElButton, ElPopconfirm, ElSwitch, ElTag} from 'element-plus';
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
import type { TableInstance } from 'element-plus';
import { ElButton, ElPopconfirm, ElSwitch, ElTag } from 'element-plus';
import dayjs from 'dayjs';
import type {FlatResponseData} from '@sa/axios';
import {userGenderRecord} from '@/constants/business';
import type { FlatResponseData } from '@sa/axios';
import { userGenderRecord } from '@/constants/business';
import {
fetchBatchDeleteUser,
fetchDeleteDept,
@@ -17,11 +17,12 @@ import {
fetchUpdateUser,
fetchUpdateUserStatus
} from '@/service/api';
import {useUIPaginatedTable} from '@/hooks/common/table';
import { useUIPaginatedTable } from '@/hooks/common/table';
import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import {$t} from '@/locales';
import {buildMenuTree} from '@/views/system/shared/menu-tree';
import { $t } from '@/locales';
import { buildMenuTree } from '@/views/system/shared/menu-tree';
import UserManagementRelation from '@/views/system/user-management-relation/index.vue';
import UserOperateDialog from './modules/user-operate-dialog.vue';
import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue';
import UserOrgOperateDialog from './modules/user-org-operate-dialog.vue';
@@ -29,9 +30,8 @@ import UserOrgPanel from './modules/user-org-panel.vue';
import UserResignedDialog from './modules/user-resigned-dialog.vue';
import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue';
import UserSearch from './modules/user-search.vue';
import UserManagementRelation from '@/views/system/user-management-relation/index.vue';
defineOptions({name: 'UserManage'});
defineOptions({ name: 'UserManage' });
function getInitSearchParams(): Api.SystemManage.UserSearchParams {
return {
@@ -154,7 +154,7 @@ const deptTree = computed(() => buildMenuTree(deptList.value));
const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null);
const deptCount = computed(() => deptList.value.length);
const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} = useUIPaginatedTable<
const { columns, columnChecks, data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable<
FlatResponseData<any, Api.SystemManage.UserList>,
Api.SystemManage.User
>({
@@ -178,9 +178,9 @@ const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} =
searchParams.pageSize = params.pageSize ?? 10;
},
columns: () => [
{prop: 'selection', type: 'selection', width: 48},
{prop: 'index', type: 'index', label: $t('common.index'), width: 64},
{prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true},
{ prop: 'selection', type: 'selection', width: 48 },
{ prop: 'index', type: 'index', label: $t('common.index'), width: 64 },
{ prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true },
{
prop: 'nickname',
label: $t('page.system.user.nickName'),
@@ -260,9 +260,9 @@ const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} =
formatter: row => {
const state = getUserResignedState(row);
const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = {
active: {type: 'success', label: 'page.system.user.resignedStateEnum.active'},
pending: {type: 'warning', label: 'page.system.user.resignedStateEnum.pending'},
resigned: {type: 'info', label: 'page.system.user.resignedStateEnum.resigned'}
active: { type: 'success', label: 'page.system.user.resignedStateEnum.active' },
pending: { type: 'warning', label: 'page.system.user.resignedStateEnum.pending' },
resigned: { type: 'info', label: 'page.system.user.resignedStateEnum.resigned' }
};
return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>;
@@ -323,7 +323,7 @@ const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} =
async function loadDeptTree() {
deptLoading.value = true;
const {error, data: deptItems} = await fetchGetDeptList({
const { error, data: deptItems } = await fetchGetDeptList({
status: 0
});
@@ -425,7 +425,7 @@ function openOrgLeader(row: Api.SystemManage.Dept) {
}
async function handleDeleteDeptAction(row: Api.SystemManage.Dept) {
const {error} = await fetchDeleteDept(row.id);
const { error } = await fetchDeleteDept(row.id);
if (error) {
return;
@@ -450,7 +450,7 @@ async function handleDeleteAction(row: Api.SystemManage.User) {
return;
}
const {error} = await fetchDeleteUser(row.id);
const { error } = await fetchDeleteUser(row.id);
if (error) {
return;
@@ -469,7 +469,7 @@ async function updateUserResignedAt(userId: number, value: number | null) {
const user = detailResult.data;
const {error} = await fetchUpdateUser({
const { error } = await fetchUpdateUser({
id: userId,
username: user.username,
nickname: user.nickname ?? null,
@@ -521,7 +521,7 @@ async function handleBatchDelete() {
return;
}
const {error} = await fetchBatchDeleteUser(userCheckedRowKeys.value);
const { error } = await fetchBatchDeleteUser(userCheckedRowKeys.value);
if (error) {
return;
@@ -534,7 +534,7 @@ async function handleBatchDelete() {
async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) {
statusLoadingIds.value = [...statusLoadingIds.value, row.id];
const {error} = await fetchUpdateUserStatus({
const { error } = await fetchUpdateUserStatus({
id: row.id,
status: enabled ? 0 : 1
});
@@ -643,13 +643,13 @@ onMounted(async () => {
<template #default>
<ElButton plain type="primary" :disabled="!currentDept" @click="openAdd">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
<icon-ic-round-plus class="text-icon" />
</template>
{{ $t('common.add') }}
</ElButton>
<ElButton plain type="primary" :disabled="!currentDept" @click="userManagementRelationVisible = true">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
<icon-ic-round-plus class="text-icon" />
</template>
带人关系
</ElButton>
@@ -657,7 +657,7 @@ onMounted(async () => {
<template #reference>
<ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0">
<template #icon>
<icon-ic-round-delete class="text-icon"/>
<icon-ic-round-delete class="text-icon" />
</template>
{{ $t('common.batchDelete') }}
</ElButton>
@@ -679,7 +679,7 @@ onMounted(async () => {
:data="data"
@selection-change="handleUserSelectionChange"
>
<ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col"/>
<ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col" />
</ElTable>
</div>
<div class="mt-20px flex justify-end">
@@ -694,7 +694,7 @@ onMounted(async () => {
</template>
<div v-else class="h-full flex items-center justify-center">
<ElEmpty :description="$t('page.system.user.emptyOrg')"/>
<ElEmpty :description="$t('page.system.user.emptyOrg')" />
</div>
</ElCard>
</div>
@@ -733,7 +733,7 @@ onMounted(async () => {
@submitted="handleDeptSubmitted"
/>
<UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData"/>
<UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData" />
<BusinessFormDialog
v-model="userManagementRelationVisible"
@@ -742,7 +742,7 @@ onMounted(async () => {
:show-footer="false"
max-body-height="70vh"
>
<UserManagementRelation :fromUserIndex="true" :deptId="currentDeptId"/>
<UserManagementRelation :from-user-index="true" :dept-id="currentDeptId" />
</BusinessFormDialog>
</div>
</template>