Files
cn-rdms-web/src/views/system/user/modules/user-operate-dialog.vue
dk b4878845da fix(UserOperateDialog): 用户新增和编辑时的对话框里,昵称应该是必填项。
feat(User):搜索框组件新增按“所属公司”搜索;index组件新增“所属公司”字段;新增、编辑的对话框应该可以新增或修改“所属公司”。
2026-04-16 20:27:08 +08:00

369 lines
11 KiB
Vue

<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import { userGenderOptions } from '@/constants/business';
import {
fetchAssignUserRoles,
fetchCreateUser,
fetchGetUser,
fetchGetUserRoleIds,
fetchUpdateUser
} from '@/service/api';
import { useForm, useFormRules } from '@/hooks/common/form';
import { translateOptions } from '@/utils/common';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import BusinessFormSection from '@/components/custom/business-form-section.vue';
import { $t } from '@/locales';
defineOptions({ name: 'UserOperateDialog' });
interface Props {
operateType: UI.TableOperateType;
userId?: number | null;
currentDeptId?: number | null;
deptTree: Api.SystemManage.Dept[];
postOptions: Api.SystemManage.PostSimple[];
roleOptions: Api.SystemManage.RoleSimple[];
companyOptions: Api.Dict.DictData[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted', userId?: number): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate } = useForm();
const { createRequiredRule, patternRules } = useFormRules();
const loading = ref(false);
const submitting = ref(false);
const title = computed(() => {
const titles: Record<UI.TableOperateType, string> = {
add: $t('page.system.user.addUser'),
edit: $t('page.system.user.editUser')
};
return titles[props.operateType];
});
const isEdit = computed(() => props.operateType === 'edit');
type Model = Api.SystemManage.SaveUserParams & {
roleIds: number[];
};
const model = ref<Model>(createDefaultModel());
const genderOptions = computed(() =>
translateOptions(userGenderOptions).map(item => ({
...item,
value: Number(item.value) as Api.SystemManage.UserGender
}))
);
const deptTreeProps = {
value: 'id',
label: 'name',
children: 'children'
} as const;
function createDefaultModel(): Model {
return {
username: '',
nickname: '',
remark: '',
deptId: props.currentDeptId ?? 0,
positionId: null,
company: null,
email: '',
mobile: '',
sex: 1,
avatar: '',
password: '',
roleIds: []
};
}
function getNullableText(value?: string | null) {
return value?.trim() || null;
}
const rules = computed(() => {
const passwordRules = isEdit.value
? []
: [createRequiredRule($t('page.system.user.form.password')), patternRules.pwd];
return {
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'))],
positionId: [createRequiredRule($t('page.system.user.form.positionName'))],
mobile: getNullableText(model.value.mobile) ? [patternRules.phone] : [],
email: getNullableText(model.value.email) ? [patternRules.email] : [],
password: passwordRules
} satisfies Record<string, App.Global.FormRule[]>;
});
async function handleInitModel() {
model.value = createDefaultModel();
if (!isEdit.value || !props.userId) {
return;
}
loading.value = true;
const [userResult, roleResult] = await Promise.all([fetchGetUser(props.userId), fetchGetUserRoleIds(props.userId)]);
loading.value = false;
if (userResult.error) {
return;
}
const user = userResult.data;
model.value = {
username: user.username,
nickname: user.nickname ?? '',
remark: user.remark ?? '',
deptId: user.deptId,
positionId: user.positionId ?? null,
company: user.company ?? null,
email: user.email ?? '',
mobile: user.mobile ?? '',
sex: user.sex ?? 0,
avatar: user.avatar ?? '',
password: '',
roleIds: roleResult.error ? [] : roleResult.data
};
}
function closeDialog() {
visible.value = false;
}
async function handleSubmit() {
await validate();
const payload: Api.SystemManage.SaveUserParams = {
username: model.value.username.trim(),
nickname: getNullableText(model.value.nickname),
remark: getNullableText(model.value.remark),
deptId: model.value.deptId,
positionId: model.value.positionId,
company: model.value.company,
email: getNullableText(model.value.email),
mobile: getNullableText(model.value.mobile),
sex: model.value.sex,
avatar: getNullableText(model.value.avatar)
};
if (!isEdit.value) {
payload.password = String(model.value.password ?? '').trim();
}
submitting.value = true;
let userId = props.userId ?? undefined;
if (isEdit.value && props.userId) {
const result = await fetchUpdateUser({ id: props.userId, ...payload });
if (result.error) {
submitting.value = false;
return;
}
} else {
const result = await fetchCreateUser(payload);
if (result.error) {
submitting.value = false;
return;
}
userId = result.data;
}
if (userId !== undefined) {
const roleResult = await fetchAssignUserRoles({
userId,
roleIds: model.value.roleIds
});
if (roleResult.error) {
submitting.value = false;
return;
}
}
submitting.value = false;
window.$message?.success(isEdit.value ? $t('common.updateSuccess') : $t('common.addSuccess'));
closeDialog();
emit('submitted', userId);
}
watch(visible, async value => {
if (!value) {
return;
}
await handleInitModel();
await nextTick();
formRef.value?.clearValidate();
});
</script>
<template>
<BusinessFormDialog
v-model="visible"
:title="title"
preset="lg"
:loading="loading"
:confirm-loading="submitting"
max-body-height="70vh"
@confirm="handleSubmit"
>
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top" autocomplete="off">
<input class="business-form-autofill-guard" type="text" name="fake-username" autocomplete="username" />
<input class="business-form-autofill-guard" type="password" name="fake-password" autocomplete="new-password" />
<BusinessFormSection :title="$t('page.system.user.sections.basicInfo')">
<ElRow :gutter="16">
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.userName')" prop="username">
<ElInput
v-model="model.username"
name="system-user-username"
autocomplete="off"
:placeholder="$t('page.system.user.form.userName')"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.nickName')" prop="nickname">
<ElInput
v-model="model.nickname"
name="system-user-nickname"
autocomplete="off"
:placeholder="$t('page.system.user.form.nickName')"
/>
</ElFormItem>
</ElCol>
<ElCol v-if="!isEdit" :span="12">
<ElFormItem :label="$t('page.system.user.password')" prop="password">
<ElInput
v-model="model.password"
name="system-user-password"
show-password
autocomplete="new-password"
:placeholder="$t('page.system.user.form.password')"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.userGender')" prop="sex">
<ElSelect v-model="model.sex" :placeholder="$t('page.system.user.form.userGender')">
<ElOption v-for="{ label, value } in genderOptions" :key="value" :label="label" :value="value" />
</ElSelect>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.userPhone')" prop="mobile">
<ElInput
v-model="model.mobile"
name="system-user-mobile"
autocomplete="off"
:placeholder="$t('page.system.user.form.userPhone')"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.userEmail')" prop="email">
<ElInput
v-model="model.email"
name="system-user-email"
autocomplete="off"
:placeholder="$t('page.system.user.form.userEmail')"
/>
</ElFormItem>
</ElCol>
<ElCol :span="24">
<ElFormItem :label="$t('page.system.user.remark')" prop="remark">
<ElInput
v-model="model.remark"
type="textarea"
:rows="3"
:placeholder="$t('page.system.user.form.remark')"
/>
</ElFormItem>
</ElCol>
</ElRow>
</BusinessFormSection>
<BusinessFormSection :title="$t('page.system.user.sections.organizationInfo')">
<ElRow :gutter="16">
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.deptName')" prop="deptId">
<ElTreeSelect
v-model="model.deptId"
check-strictly
clearable
default-expand-all
:data="deptTree"
:props="deptTreeProps"
:render-after-expand="false"
:placeholder="$t('page.system.user.form.deptName')"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.positionName')" prop="positionId">
<ElSelect v-model="model.positionId" clearable :placeholder="$t('page.system.user.form.positionName')">
<ElOption v-for="item in postOptions" :key="item.id" :label="item.name" :value="item.id" />
</ElSelect>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem :label="$t('page.system.user.userRole')" prop="roleIds">
<ElSelect
v-model="model.roleIds"
multiple
collapse-tags
collapse-tags-tooltip
:placeholder="$t('page.system.user.form.userRole')"
>
<ElOption v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
</ElSelect>
</ElFormItem>
</ElCol>
</ElRow>
</BusinessFormSection>
</ElForm>
</BusinessFormDialog>
</template>
<style scoped>
.business-form-autofill-guard {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
opacity: 0;
pointer-events: none;
}
</style>