This repository has been archived on 2026-09-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
UserBaseFront/src/views/group-user/user/UserForm.vue
T
chenxing 2321571060
deploy-userbase-front / build-and-deploy (push) Successful in 2m58s
修改角色bug
2026-08-17 18:54:00 +08:00

311 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Modal v-model="show" :mask-closable="false" width="800" scrollable :title="title"
@on-visible-change="onVisibleChange">
<Form ref="formRef" :model="form" :rules="rules" :label-width="130" class="user-form width-95">
<!-- 第 1 行:用户名 / 真实姓名 -->
<Row>
<Col span="12">
<FormItem label="用户名:" prop="account">
<Input v-model="form.account" :disabled="isEdit" placeholder="请输入用户名" />
</FormItem>
</Col>
<Col span="12">
<FormItem label="真实姓名:" prop="realName">
<Input v-model="form.realName" placeholder="请输入真实姓名" />
</FormItem>
</Col>
</Row>
<!-- 第 2 行:密码 / 确认密码(编辑时变为「重置密码」语义,但视觉一致) -->
<Row>
<Col span="12">
<FormItem label="密码:" prop="password">
<Input v-model="form.password" type="password" :placeholder="isEdit ? '不修改请留空' : '请输入密码'" />
</FormItem>
</Col>
<Col span="12">
<FormItem label="确认密码:" prop="confirmPassword">
<Input v-model="form.confirmPassword" type="password"
:placeholder="isEdit ? '不修改请留空' : '请再次确认密码'" />
</FormItem>
</Col>
</Row>
<!-- 第 3 行:身份证号 / 手机号码 -->
<Row>
<Col span="12">
<FormItem label="身份证号:" prop="idcard">
<Input v-model="form.idcard" placeholder="请输入身份证号" />
</FormItem>
</Col>
<Col span="12">
<FormItem label="手机号码:" prop="phone">
<Input v-model="form.phone" placeholder="请输入手机号码" />
</FormItem>
</Col>
</Row>
<!-- 第 4 行:性别 -->
<Row>
<Col span="24">
<FormItem label="性别:">
<RadioGroup v-model="form.gender">
<Radio :label="0">女</Radio>
<Radio :label="1">男</Radio>
</RadioGroup>
</FormItem>
</Col>
</Row>
<!-- 第 5 行:所在组(只读)(仅编辑展示,默认选中当前组;提交时不带 groupId,不改变所在组) -->
<Row v-if="isEdit">
<Col span="24">
<FormItem label="所在组(只读):">
<Select v-model="form.userGroupIds" multiple transfer
placeholder="默认选中当前组(提交不改变所在组)">
<Option v-for="g in userGroups" :key="g.id" :value="g.id" :label="g.name" />
</Select>
</FormItem>
</Col>
</Row>
<!-- 第 6 行:用户身份(checkbox 多选,整行) -->
<Row>
<Col span="24">
<FormItem label="用户身份:" prop="userType">
<CheckboxGroup v-model="form.userType" class="user-type-group">
<Checkbox v-for="opt in userTypeOptions" :key="opt.value" :label="opt.value">
{{ opt.label }}
</Checkbox>
</CheckboxGroup>
</FormItem>
</Col>
</Row>
<!-- 第 7 行:角色属性(业务角色多选,可编辑;来自角色授权页) -->
<Row>
<Col span="24">
<FormItem label="角色属性:">
<Select v-model="form.appRoles" multiple filterable clearable transfer
placeholder="可选择业务角色(来自角色授权)">
<Option v-for="r in roleOptions" :key="r.id" :value="r.id" :label="r.name" />
</Select>
</FormItem>
</Col>
</Row>
</Form>
<template #footer>
<Button type="primary" :loading="loading" @click="handleSubmit">提交</Button>
<Button @click="handleCancel">取消</Button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from "vue";
import { useDefaultStore } from "@/store";
/* ---------- Props / Emits ---------- */
const props = withDefaults(defineProps<{
modelValue: boolean;
initForm?: Record<string, any>;
userGroups?: { id: number; name: string }[];
roleOptions?: { id: number; name: string }[];
isEdit?: boolean;
loading?: boolean;
}>(), {
initForm: () => ({}),
userGroups: () => [],
roleOptions: () => [],
isEdit: false,
loading: false,
});
const emit = defineEmits(['update:modelValue', 'submit', 'cancel']);
const show = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
});
const title = computed(() => props.isEdit ? '编辑用户' : '新增用户');
/* ---------- 表单数据 ---------- */
const defaultForm = (): Record<string, any> => ({
id: null,
account: '',
realName: '',
password: '',
confirmPassword: '',
idcard: '',
phone: '',
gender: 0,
userType: [] as number[],
appRoles: [] as number[],
userGroupIds: [] as number[],
});
const form = reactive(defaultForm());
// 外部 initForm 变化时回填
watch(() => props.initForm, (val) => {
if (val && Object.keys(val).length > 0) {
Object.assign(form, defaultForm(), val);
} else {
Object.assign(form, defaultForm());
}
}, { immediate: true, deep: true });
/* ---------- 用户身份(业务身份多选)---------- */
// 唯一真源:后端 SysRoleEnum(经 GetSysDict.sysrole 下发),杜绝前端硬编码漂移。
// 若字典未加载(接口异常)则回退到与 SysRoleEnum 完全一致的兜底值。
const SYS_ROLE_FALLBACK = [
{ value: 1, label: '老师' },
{ value: 2, label: '校领导' },
{ value: 3, label: '学生' },
{ value: 4, label: '家长' },
{ value: 5, label: '局领导' },
{ value: 6, label: '工作号' },
{ value: 7, label: '管理员' },
];
const store = useDefaultStore();
const userTypeOptions = computed(() => {
const sysrole = (store.getSysDict as any)?.sysrole;
if (Array.isArray(sysrole) && sysrole.length) {
return sysrole.map((it: any) => ({ value: it.k, label: it.v }));
}
return SYS_ROLE_FALLBACK;
});
/* ---------- 校验规则 ---------- */
// 自定义确认密码校验:新增必填且必须与密码一致;编辑时两者皆空表示不修改,任一非空则必须一致
const validateConfirmPassword = (rule: any, value: string, callback: any) => {
const pwd = form.password || '';
const conf = value || '';
if (!props.isEdit) {
if (!conf) return callback(new Error('请再次输入密码'));
if (conf !== pwd) return callback(new Error('两次输入的密码不一致'));
return callback();
}
// 编辑:都为空 -> 不修改密码,放行
if (!pwd && !conf) return callback();
// 修改密码时两次输入必须同时填写且一致
if (!pwd || !conf) return callback(new Error('修改密码时两次输入必须一致'));
if (pwd !== conf) return callback(new Error('两次输入的密码不一致'));
callback();
};
// 自定义密码校验:新增必填(>=6位);编辑可留空,留空表示不修改
const validatePassword = (rule: any, value: string, callback: any) => {
const pwd = value || '';
if (!props.isEdit) {
if (!pwd) return callback(new Error('请输入密码'));
if (pwd.length < 6) return callback(new Error('密码长度不能少于 6 位'));
return callback();
}
if (!pwd) return callback();
if (pwd.length < 6) return callback(new Error('密码长度不能少于 6 位'));
callback();
};
// 用户名格式校验:新增时强制仅数字/字母;编辑时账号只读不可改,跳过格式校验
const validateAccount = (rule: any, value: string, callback: any) => {
if (props.isEdit) {
callback();
return;
}
if (value && !/^[A-Za-z0-9]+$/.test(value)) {
callback(new Error('用户名只能由数字或字母组成'));
} else {
callback();
}
};
// 密码红五星号仅新增显示;校验规则随 isEdit 变化
const rules = computed(() => ({
account: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 30, message: '用户名长度 3-30 位', trigger: 'blur' },
{ validator: validateAccount, trigger: 'blur' },
],
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' },
],
password: [{ required: !props.isEdit, validator: validatePassword, trigger: 'blur' }],
confirmPassword: [{ required: !props.isEdit, validator: validateConfirmPassword, trigger: 'blur' }],
phone: [
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' },
],
userType: [
{ required: true, type: 'array' as const, min: 1, message: '请选择至少一个用户身份', trigger: 'change' },
],
}));
/* ---------- 提交 ---------- */
const formRef = ref();
// 密码变化时,若已填确认密码则重新校验确认密码,保持一致性提示
watch(() => form.password, () => {
if (form.confirmPassword) {
formRef.value?.validateField('confirmPassword');
}
});
async function handleSubmit() {
try {
await formRef.value?.validate();
} catch {
return false;
}
emit('submit', { ...form });
}
function handleCancel() {
emit('cancel');
}
function onVisibleChange(val: boolean) {
if (!val) {
emit('cancel');
}
}
</script>
<style lang="scss" scoped>
.user-form {
margin: 0 auto;
max-height: 60vh;
overflow-y: auto;
overflow-x: hidden;
padding-right: 12px;
:deep(.ivu-form-item) {
display: block;
margin-bottom: 0;
}
:deep(.ivu-form-item-label) {
font-size: 13px;
color: #515a6e;
}
:deep(.ivu-row) {
margin-left: 0 !important;
margin-right: 0 !important;
margin-bottom: 22px;
}
:deep(.ivu-row:last-child) {
margin-bottom: 0;
}
}
/* 用户身份多选:横向换行,避免撑爆 Col */
.user-type-group {
display: flex;
flex-wrap: wrap;
gap: 0 18px;
}
</style>