fix(UserOperateDialog): 用户新增和编辑时的对话框里,昵称应该是必填项。

feat(User):搜索框组件新增按“所属公司”搜索;index组件新增“所属公司”字段;新增、编辑的对话框应该可以新增或修改“所属公司”。
This commit is contained in:
dk
2026-04-16 20:27:08 +08:00
parent b265d0d4f1
commit b4878845da
5 changed files with 84 additions and 31 deletions

View File

@@ -102,3 +102,11 @@ export function fetchBatchDeleteDictData(ids: number[]) {
method: 'delete' method: 'delete'
}); });
} }
/** 通过岗位编码获取该字典的所有字典数据 */
export function fetchGetDictDataByCode(code: String) {
return request<Api.Dict.PageResult<Api.Dict.DictData>>({
url: `${DICT_DATA_PREFIX}/code?code=${code}`,
method: 'get'
});
}

View File

@@ -130,6 +130,7 @@ declare namespace Api {
deptName?: string | null; deptName?: string | null;
positionId?: number | null; positionId?: number | null;
positionName?: string | null; positionName?: string | null;
company?: string | null;
email?: string | null; email?: string | null;
mobile?: string | null; mobile?: string | null;
sex?: UserGender | null; sex?: UserGender | null;
@@ -148,6 +149,7 @@ declare namespace Api {
mobile?: string; mobile?: string;
deptId?: number; deptId?: number;
roleId?: number; roleId?: number;
company?: string;
} }
>; >;
@@ -158,6 +160,7 @@ declare namespace Api {
remark?: string | null; remark?: string | null;
positionId?: number | null; positionId?: number | null;
resignedAt?: number | null; resignedAt?: number | null;
company?: string | null;
email?: string | null; email?: string | null;
mobile?: string | null; mobile?: string | null;
sex?: UserGender | null; sex?: UserGender | null;

View File

@@ -1,15 +1,16 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'; import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue';
import type { TableInstance } from 'element-plus'; import type {TableInstance} from 'element-plus';
import { ElButton, ElPopconfirm, ElSwitch, ElTag } from 'element-plus'; import {ElButton, ElPopconfirm, ElSwitch, ElTag} from 'element-plus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { FlatResponseData } from '@sa/axios'; import type {FlatResponseData} from '@sa/axios';
import { userGenderRecord } from '@/constants/business'; import {userGenderRecord} from '@/constants/business';
import { import {
fetchBatchDeleteUser, fetchBatchDeleteUser,
fetchDeleteDept, fetchDeleteDept,
fetchDeleteUser, fetchDeleteUser,
fetchGetDeptList, fetchGetDeptList,
fetchGetDictDataByCode,
fetchGetPostSimpleList, fetchGetPostSimpleList,
fetchGetRoleSimpleList, fetchGetRoleSimpleList,
fetchGetUser, fetchGetUser,
@@ -17,11 +18,11 @@ import {
fetchUpdateUser, fetchUpdateUser,
fetchUpdateUserStatus fetchUpdateUserStatus
} from '@/service/api'; } 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 BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue'; import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import { $t } from '@/locales'; import {$t} from '@/locales';
import { buildMenuTree } from '@/views/system/shared/menu-tree'; import {buildMenuTree} from '@/views/system/shared/menu-tree';
import UserManagementRelation from '@/views/system/user-management-relation/index.vue'; import UserManagementRelation from '@/views/system/user-management-relation/index.vue';
import UserOperateDialog from './modules/user-operate-dialog.vue'; import UserOperateDialog from './modules/user-operate-dialog.vue';
import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue'; import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue';
@@ -31,7 +32,7 @@ import UserResignedDialog from './modules/user-resigned-dialog.vue';
import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue'; import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue';
import UserSearch from './modules/user-search.vue'; import UserSearch from './modules/user-search.vue';
defineOptions({ name: 'UserManage' }); defineOptions({name: 'UserManage'});
function getInitSearchParams(): Api.SystemManage.UserSearchParams { function getInitSearchParams(): Api.SystemManage.UserSearchParams {
return { return {
@@ -41,7 +42,8 @@ function getInitSearchParams(): Api.SystemManage.UserSearchParams {
mobile: undefined, mobile: undefined,
status: undefined, status: undefined,
deptId: undefined, deptId: undefined,
roleId: undefined roleId: undefined,
company: undefined
}; };
} }
@@ -135,6 +137,7 @@ const operateType = ref<UI.TableOperateType>('add');
const editingUserId = ref<number | null>(null); const editingUserId = ref<number | null>(null);
const postOptions = ref<Api.SystemManage.PostSimple[]>([]); const postOptions = ref<Api.SystemManage.PostSimple[]>([]);
const roleOptions = ref<Api.SystemManage.RoleSimple[]>([]); const roleOptions = ref<Api.SystemManage.RoleSimple[]>([]);
const companyOptions = ref<any>([]);
const resetPasswordVisible = ref(false); const resetPasswordVisible = ref(false);
const resetPasswordUserId = ref<number | null>(null); const resetPasswordUserId = ref<number | null>(null);
const resetPasswordUsername = ref<string | null>(null); const resetPasswordUsername = ref<string | null>(null);
@@ -154,7 +157,7 @@ const deptTree = computed(() => buildMenuTree(deptList.value));
const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null); const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null);
const deptCount = computed(() => deptList.value.length); 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>, FlatResponseData<any, Api.SystemManage.UserList>,
Api.SystemManage.User Api.SystemManage.User
>({ >({
@@ -178,9 +181,9 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
searchParams.pageSize = params.pageSize ?? 10; searchParams.pageSize = params.pageSize ?? 10;
}, },
columns: () => [ columns: () => [
{ prop: 'selection', type: 'selection', width: 48 }, {prop: 'selection', type: 'selection', width: 48},
{ prop: 'index', type: 'index', label: $t('common.index'), width: 64 }, {prop: 'index', type: 'index', label: $t('common.index'), width: 64},
{ prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true }, {prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true},
{ {
prop: 'nickname', prop: 'nickname',
label: $t('page.system.user.nickName'), label: $t('page.system.user.nickName'),
@@ -194,6 +197,16 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
showOverflowTooltip: true, showOverflowTooltip: true,
formatter: row => getNullableLabel(row.deptName) formatter: row => getNullableLabel(row.deptName)
}, },
{
prop: 'company',
label: '所属公司',
minWidth: 140,
showOverflowTooltip: true,
formatter: row => {
const companyItem = companyOptions.value.find((item: any) => item.value === row.company);
return companyItem?.label || '--';
}
},
{ {
prop: 'positionName', prop: 'positionName',
label: $t('page.system.user.positionName'), label: $t('page.system.user.positionName'),
@@ -260,9 +273,9 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
formatter: row => { formatter: row => {
const state = getUserResignedState(row); const state = getUserResignedState(row);
const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = { const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = {
active: { type: 'success', label: 'page.system.user.resignedStateEnum.active' }, active: {type: 'success', label: 'page.system.user.resignedStateEnum.active'},
pending: { type: 'warning', label: 'page.system.user.resignedStateEnum.pending' }, pending: {type: 'warning', label: 'page.system.user.resignedStateEnum.pending'},
resigned: { type: 'info', label: 'page.system.user.resignedStateEnum.resigned' } resigned: {type: 'info', label: 'page.system.user.resignedStateEnum.resigned'}
}; };
return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>; return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>;
@@ -323,7 +336,7 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
async function loadDeptTree() { async function loadDeptTree() {
deptLoading.value = true; deptLoading.value = true;
const { error, data: deptItems } = await fetchGetDeptList({ const {error, data: deptItems} = await fetchGetDeptList({
status: 0 status: 0
}); });
@@ -347,7 +360,11 @@ async function loadDeptTree() {
} }
async function loadFormOptions() { async function loadFormOptions() {
const [postResult, roleResult] = await Promise.all([fetchGetPostSimpleList(), fetchGetRoleSimpleList()]); const [postResult, roleResult, companyResult] = await Promise.all([
fetchGetPostSimpleList(),
fetchGetRoleSimpleList(),
fetchGetDictDataByCode('system_user_company')
]);
if (!postResult.error) { if (!postResult.error) {
postOptions.value = postResult.data; postOptions.value = postResult.data;
@@ -356,6 +373,10 @@ async function loadFormOptions() {
if (!roleResult.error) { if (!roleResult.error) {
roleOptions.value = roleResult.data.filter(item => item.status === 0); roleOptions.value = roleResult.data.filter(item => item.status === 0);
} }
if (!companyResult.error) {
companyOptions.value = companyResult.data;
}
} }
async function reloadUserTable(page = searchParams.pageNo) { async function reloadUserTable(page = searchParams.pageNo) {
@@ -425,7 +446,7 @@ function openOrgLeader(row: Api.SystemManage.Dept) {
} }
async function handleDeleteDeptAction(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) { if (error) {
return; return;
@@ -450,7 +471,7 @@ async function handleDeleteAction(row: Api.SystemManage.User) {
return; return;
} }
const { error } = await fetchDeleteUser(row.id); const {error} = await fetchDeleteUser(row.id);
if (error) { if (error) {
return; return;
@@ -469,7 +490,7 @@ async function updateUserResignedAt(userId: number, value: number | null) {
const user = detailResult.data; const user = detailResult.data;
const { error } = await fetchUpdateUser({ const {error} = await fetchUpdateUser({
id: userId, id: userId,
username: user.username, username: user.username,
nickname: user.nickname ?? null, nickname: user.nickname ?? null,
@@ -521,7 +542,7 @@ async function handleBatchDelete() {
return; return;
} }
const { error } = await fetchBatchDeleteUser(userCheckedRowKeys.value); const {error} = await fetchBatchDeleteUser(userCheckedRowKeys.value);
if (error) { if (error) {
return; return;
@@ -534,7 +555,7 @@ async function handleBatchDelete() {
async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) { async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) {
statusLoadingIds.value = [...statusLoadingIds.value, row.id]; statusLoadingIds.value = [...statusLoadingIds.value, row.id];
const { error } = await fetchUpdateUserStatus({ const {error} = await fetchUpdateUserStatus({
id: row.id, id: row.id,
status: enabled ? 0 : 1 status: enabled ? 0 : 1
}); });
@@ -624,6 +645,7 @@ onMounted(async () => {
<UserSearch <UserSearch
v-model:model="searchParams" v-model:model="searchParams"
:role-options="roleOptions" :role-options="roleOptions"
:company-options="companyOptions"
:disabled="!currentDept" :disabled="!currentDept"
@reset="handleResetSearch" @reset="handleResetSearch"
@search="handleSearch" @search="handleSearch"
@@ -643,13 +665,13 @@ onMounted(async () => {
<template #default> <template #default>
<ElButton plain type="primary" :disabled="!currentDept" @click="openAdd"> <ElButton plain type="primary" :disabled="!currentDept" @click="openAdd">
<template #icon> <template #icon>
<icon-ic-round-plus class="text-icon" /> <icon-ic-round-plus class="text-icon"/>
</template> </template>
{{ $t('common.add') }} {{ $t('common.add') }}
</ElButton> </ElButton>
<ElButton plain type="primary" :disabled="!currentDept" @click="userManagementRelationVisible = true"> <ElButton plain type="primary" :disabled="!currentDept" @click="userManagementRelationVisible = true">
<template #icon> <template #icon>
<icon-ic-round-plus class="text-icon" /> <icon-ic-round-plus class="text-icon"/>
</template> </template>
管理链路 管理链路
</ElButton> </ElButton>
@@ -657,7 +679,7 @@ onMounted(async () => {
<template #reference> <template #reference>
<ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0"> <ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0">
<template #icon> <template #icon>
<icon-ic-round-delete class="text-icon" /> <icon-ic-round-delete class="text-icon"/>
</template> </template>
{{ $t('common.batchDelete') }} {{ $t('common.batchDelete') }}
</ElButton> </ElButton>
@@ -679,7 +701,7 @@ onMounted(async () => {
:data="data" :data="data"
@selection-change="handleUserSelectionChange" @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> </ElTable>
</div> </div>
<div class="mt-20px flex justify-end"> <div class="mt-20px flex justify-end">
@@ -694,7 +716,7 @@ onMounted(async () => {
</template> </template>
<div v-else class="h-full flex items-center justify-center"> <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> </div>
</ElCard> </ElCard>
</div> </div>
@@ -707,6 +729,7 @@ onMounted(async () => {
:dept-tree="deptTree" :dept-tree="deptTree"
:post-options="postOptions" :post-options="postOptions"
:role-options="roleOptions" :role-options="roleOptions"
:company-options="companyOptions"
@submitted="handleSubmitted" @submitted="handleSubmitted"
/> />
@@ -733,7 +756,7 @@ onMounted(async () => {
@submitted="handleDeptSubmitted" @submitted="handleDeptSubmitted"
/> />
<UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData" /> <UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData"/>
<BusinessFormDialog <BusinessFormDialog
v-model="userManagementRelationVisible" v-model="userManagementRelationVisible"
@@ -742,7 +765,7 @@ onMounted(async () => {
:show-footer="false" :show-footer="false"
max-body-height="70vh" max-body-height="70vh"
> >
<UserManagementRelation :from-user-index="true" :dept-id="currentDeptId" /> <UserManagementRelation :from-user-index="true" :dept-id="currentDeptId"/>
</BusinessFormDialog> </BusinessFormDialog>
</div> </div>
</template> </template>

View File

@@ -23,6 +23,7 @@ interface Props {
deptTree: Api.SystemManage.Dept[]; deptTree: Api.SystemManage.Dept[];
postOptions: Api.SystemManage.PostSimple[]; postOptions: Api.SystemManage.PostSimple[];
roleOptions: Api.SystemManage.RoleSimple[]; roleOptions: Api.SystemManage.RoleSimple[];
companyOptions: Api.Dict.DictData[];
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
@@ -80,6 +81,7 @@ function createDefaultModel(): Model {
remark: '', remark: '',
deptId: props.currentDeptId ?? 0, deptId: props.currentDeptId ?? 0,
positionId: null, positionId: null,
company: null,
email: '', email: '',
mobile: '', mobile: '',
sex: 1, sex: 1,
@@ -100,6 +102,7 @@ const rules = computed(() => {
return { return {
username: [createRequiredRule($t('page.system.user.form.userName')), patternRules.userName], username: [createRequiredRule($t('page.system.user.form.userName')), patternRules.userName],
nickname: [createRequiredRule($t('page.system.user.form.nickName'))],
deptId: [createRequiredRule($t('page.system.user.form.deptName'))], deptId: [createRequiredRule($t('page.system.user.form.deptName'))],
positionId: [createRequiredRule($t('page.system.user.form.positionName'))], positionId: [createRequiredRule($t('page.system.user.form.positionName'))],
mobile: getNullableText(model.value.mobile) ? [patternRules.phone] : [], mobile: getNullableText(model.value.mobile) ? [patternRules.phone] : [],
@@ -133,6 +136,7 @@ async function handleInitModel() {
remark: user.remark ?? '', remark: user.remark ?? '',
deptId: user.deptId, deptId: user.deptId,
positionId: user.positionId ?? null, positionId: user.positionId ?? null,
company: user.company ?? null,
email: user.email ?? '', email: user.email ?? '',
mobile: user.mobile ?? '', mobile: user.mobile ?? '',
sex: user.sex ?? 0, sex: user.sex ?? 0,
@@ -155,6 +159,7 @@ async function handleSubmit() {
remark: getNullableText(model.value.remark), remark: getNullableText(model.value.remark),
deptId: model.value.deptId, deptId: model.value.deptId,
positionId: model.value.positionId, positionId: model.value.positionId,
company: model.value.company,
email: getNullableText(model.value.email), email: getNullableText(model.value.email),
mobile: getNullableText(model.value.mobile), mobile: getNullableText(model.value.mobile),
sex: model.value.sex, sex: model.value.sex,

View File

@@ -8,6 +8,7 @@ defineOptions({ name: 'UserSearch' });
interface Props { interface Props {
roleOptions: Api.SystemManage.RoleSimple[]; roleOptions: Api.SystemManage.RoleSimple[];
companyOptions: Api.Dict.DictData[];
disabled?: boolean; disabled?: boolean;
} }
@@ -72,6 +73,19 @@ const model = defineModel<Api.SystemManage.UserSearchParams>('model', { required
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :lg="6" :md="8" :sm="12">
<ElFormItem label="所属公司" prop="company">
<ElSelect
v-model="model.company"
clearable
filterable
:disabled="disabled"
placeholder="请选择所属公司"
>
<ElOption v-for="item in companyOptions" :key="item.value" :label="item.label" :value="item.value" />
</ElSelect>
</ElFormItem>
</ElCol>
<ElCol :lg="6" :md="8" :sm="12"> <ElCol :lg="6" :md="8" :sm="12">
<ElFormItem :label="$t('page.system.user.userRole')" prop="roleId"> <ElFormItem :label="$t('page.system.user.userRole')" prop="roleId">
<ElSelect <ElSelect