369 lines
11 KiB
Vue
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>
|