refactor(F006-W17.4.4): UserFormDialog 优化 (去头像URL + 去是否企业认证 + 加关联企业)
用户原话: 新增用户界面部分内容要优化 - 头像 URL 字段去掉 (UI 无意义, 用户用默认头像) - 是否企业认证 去掉 (这个由企业认证流程带过来, 不是手工字段) - 增加关联企业字段 (可以选择用户属于哪些企业 - 多选) 实施: - 单选 enterpriseId (当前 tb_user 表只支持单值, 真多对多需后端新增 user_enterprise_bind 中间表, 待用户决策) - 企业下拉从 /enterprise/list 拉取, 弹窗打开时拉一次缓存 - 字段名沿用后端 UserFeignDTO.enterpriseId (认证企业id) CRUD 验证 (SQL 直查 product_network_user.tb_user): - 当前 2284 用户, 2283 正常 + 1 禁用, max_id 2318 (前后端完全一致) - 后端 login() 在 enabled=1 时抛 "账号已被禁用" (UserServiceImpl.java 行 142) → 禁用会阻止登录, 已确认 - save/update/delete 三个端点都直接走 MyBatis-Plus baseDao, 改完即落库 → 新增/禁用/删除真实写库, 已确认 修改: - UserFormDialog.vue 234 → 294 行 - 去 header, isEnterprise 字段 - 加 enterpriseId (关联企业) 单选下拉 + enterpriseOptions 缓存 - 加关联企业下拉 tip 提示用户此字段语义
This commit is contained in:
parent
a978b1572b
commit
ec473af046
@ -1,23 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* F006 用户表单弹窗 (新建/修改 共用)
|
* F006 用户表单弹窗 (新建/修改 共用) - W17.4.4 优化
|
||||||
*
|
*
|
||||||
* 后端:
|
* 后端:
|
||||||
* - 新建: POST /user/user/save (UserDTO body)
|
* - 新建: POST /user/user/save (UserDTO body)
|
||||||
* - 详情: GET /user/user/getUserInfo?id=...
|
* - 详情: GET /user/user/getUserInfo?id=...
|
||||||
* - 修改: POST /user/user/update (UserFeignDTO body, 字段子集)
|
* - 修改: POST /user/user/update (UserFeignDTO body, 字段子集)
|
||||||
*
|
*
|
||||||
* 字段 (沿用老 /views/api/user.vue + UserDTO.java):
|
* 字段 (W17.4.4 优化):
|
||||||
* nickName 昵称 (必填)
|
* nickName 昵称 (必填)
|
||||||
* mobile 手机号 (必填, 11 位)
|
* mobile 手机号 (必填, 11 位)
|
||||||
* password 密码 (新建必填, 修改可选-留空表示不修改)
|
* password 密码 (新建必填, 修改可选-留空表示不修改)
|
||||||
* header 头像 URL (选填, 暂不实现上传, 直接 URL 输入)
|
* sex 性别 0男1女 (选填)
|
||||||
* sex 性别 0男1女 (选填)
|
* enabled 禁用标识 0正常1禁用 (选填, 默认正常)
|
||||||
* isEnterprise 是否企业认证 0否1是 (选填)
|
* enterpriseId 关联企业 (单选, 取 tb_user.enterpriseId; 真多对多需后端加 user_enterprise_bind 表, 当前未做)
|
||||||
* enabled 禁用标识 0正常1禁用 (选填)
|
* remark 备注 (选填)
|
||||||
* remark 备注 (选填)
|
*
|
||||||
|
* 已去掉:
|
||||||
|
* - header (头像 URL): 改用默认头像, 此处无意义
|
||||||
|
* - isEnterprise (是否企业认证): 由企业认证流程自动带过来, 不允许手工改
|
||||||
*/
|
*/
|
||||||
import { ref, reactive, watch, computed, nextTick } from 'vue'
|
import { ref, reactive, watch, computed, nextTick, onMounted } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { httpGet, httpPost } from '@/utils/request'
|
import { httpGet, httpPost } from '@/utils/request'
|
||||||
|
|
||||||
@ -26,13 +29,14 @@ interface UserForm {
|
|||||||
nickName: string
|
nickName: string
|
||||||
mobile: string
|
mobile: string
|
||||||
password: string
|
password: string
|
||||||
header: string
|
|
||||||
sex: '' | '0' | '1'
|
sex: '' | '0' | '1'
|
||||||
isEnterprise: '' | '0' | '1'
|
enterpriseId: number | null
|
||||||
enabled: '' | '0' | '1'
|
enabled: '' | '0' | '1'
|
||||||
remark: string
|
remark: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EnterpriseOpt { id: number; name: string }
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
userId: number | string | null
|
userId: number | string | null
|
||||||
@ -47,15 +51,16 @@ const mode = computed<'create' | 'update'>(() => (props.userId ? 'update' : 'cre
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
|
const enterpriseOptions = ref<EnterpriseOpt[]>([])
|
||||||
|
const enterpriseLoading = ref(false)
|
||||||
|
|
||||||
const form = reactive<UserForm>({
|
const form = reactive<UserForm>({
|
||||||
id: null,
|
id: null,
|
||||||
nickName: '',
|
nickName: '',
|
||||||
mobile: '',
|
mobile: '',
|
||||||
password: '',
|
password: '',
|
||||||
header: '',
|
|
||||||
sex: '',
|
sex: '',
|
||||||
isEnterprise: '',
|
enterpriseId: null,
|
||||||
enabled: '0',
|
enabled: '0',
|
||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
@ -87,6 +92,7 @@ watch(
|
|||||||
async (v) => {
|
async (v) => {
|
||||||
if (!v) return
|
if (!v) return
|
||||||
resetForm()
|
resetForm()
|
||||||
|
await loadEnterprises()
|
||||||
if (props.userId) {
|
if (props.userId) {
|
||||||
await loadDetail()
|
await loadDetail()
|
||||||
}
|
}
|
||||||
@ -98,13 +104,34 @@ function resetForm() {
|
|||||||
form.nickName = ''
|
form.nickName = ''
|
||||||
form.mobile = ''
|
form.mobile = ''
|
||||||
form.password = ''
|
form.password = ''
|
||||||
form.header = ''
|
|
||||||
form.sex = ''
|
form.sex = ''
|
||||||
form.isEnterprise = ''
|
form.enterpriseId = null
|
||||||
form.enabled = '0'
|
form.enabled = '0'
|
||||||
form.remark = ''
|
form.remark = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载企业下拉选项 (弹窗打开时拉一次, 后续复用)
|
||||||
|
* 后端: GET /enterprise/list 返 EnterpriseFeignDTO 列表 [{id, enterpriseName, ...}]
|
||||||
|
* 兜底: 后端失败时给空数组, UI 仍可用 (只是下拉是空的)
|
||||||
|
*/
|
||||||
|
async function loadEnterprises() {
|
||||||
|
if (enterpriseOptions.value.length > 0) return
|
||||||
|
enterpriseLoading.value = true
|
||||||
|
try {
|
||||||
|
const data: any = await httpGet('/enterprise/list')
|
||||||
|
const arr = Array.isArray(data) ? data : (data?.list || [])
|
||||||
|
enterpriseOptions.value = arr.map((it: any) => ({
|
||||||
|
id: Number(it.id ?? it.enterpriseId ?? 0),
|
||||||
|
name: it.enterpriseName || it.name || `企业 #${it.id}`
|
||||||
|
})).filter((it: EnterpriseOpt) => it.id > 0)
|
||||||
|
} catch {
|
||||||
|
enterpriseOptions.value = []
|
||||||
|
} finally {
|
||||||
|
enterpriseLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@ -113,10 +140,9 @@ async function loadDetail() {
|
|||||||
form.id = Number(data.id ?? props.userId)
|
form.id = Number(data.id ?? props.userId)
|
||||||
form.nickName = data.nickName || ''
|
form.nickName = data.nickName || ''
|
||||||
form.mobile = data.mobile || ''
|
form.mobile = data.mobile || ''
|
||||||
form.password = '' // 修改时不回显密码
|
form.password = ''
|
||||||
form.header = data.header || ''
|
|
||||||
form.sex = (data.sex === 0 || data.sex === 1 ? String(data.sex) : '') as UserForm['sex']
|
form.sex = (data.sex === 0 || data.sex === 1 ? String(data.sex) : '') as UserForm['sex']
|
||||||
form.isEnterprise = (data.isEnterprise === 0 || data.isEnterprise === 1 ? String(data.isEnterprise) : '') as UserForm['isEnterprise']
|
form.enterpriseId = data.enterpriseId ? Number(data.enterpriseId) : null
|
||||||
form.enabled = (data.enabled === 0 || data.enabled === 1 ? String(data.enabled) : '0') as UserForm['enabled']
|
form.enabled = (data.enabled === 0 || data.enabled === 1 ? String(data.enabled) : '0') as UserForm['enabled']
|
||||||
form.remark = data.remark || ''
|
form.remark = data.remark || ''
|
||||||
}
|
}
|
||||||
@ -142,17 +168,14 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
// 后端 save 接口吃 UserDTO (全字段), update 接口吃 UserFeignDTO (字段子集)
|
|
||||||
// 为安全起见, 两种模式都按字段子集发, 后端可接受的最小 payload
|
|
||||||
const payload: Record<string, any> = {
|
const payload: Record<string, any> = {
|
||||||
id: form.id,
|
id: form.id,
|
||||||
nickName: form.nickName.trim(),
|
nickName: form.nickName.trim(),
|
||||||
mobile: form.mobile.trim()
|
mobile: form.mobile.trim()
|
||||||
}
|
}
|
||||||
if (form.password) payload.password = form.password
|
if (form.password) payload.password = form.password
|
||||||
if (form.header) payload.header = form.header
|
|
||||||
if (form.sex !== '') payload.sex = Number(form.sex)
|
if (form.sex !== '') payload.sex = Number(form.sex)
|
||||||
if (form.isEnterprise !== '') payload.isEnterprise = Number(form.isEnterprise)
|
if (form.enterpriseId) payload.enterpriseId = Number(form.enterpriseId)
|
||||||
if (form.enabled !== '') payload.enabled = Number(form.enabled)
|
if (form.enabled !== '') payload.enabled = Number(form.enabled)
|
||||||
if (form.remark) payload.remark = form.remark
|
if (form.remark) payload.remark = form.remark
|
||||||
|
|
||||||
@ -212,10 +235,6 @@ async function submit() {
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="头像 URL">
|
|
||||||
<el-input v-model="form.header" placeholder="选填, 头像图片地址" maxlength="200" clearable />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="性别">
|
<el-form-item label="性别">
|
||||||
<el-radio-group v-model="form.sex">
|
<el-radio-group v-model="form.sex">
|
||||||
<el-radio value="">未知</el-radio>
|
<el-radio value="">未知</el-radio>
|
||||||
@ -224,12 +243,23 @@ async function submit() {
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="是否企业认证">
|
<el-form-item label="关联企业">
|
||||||
<el-radio-group v-model="form.isEnterprise">
|
<el-select
|
||||||
<el-radio value="">未设置</el-radio>
|
v-model="form.enterpriseId"
|
||||||
<el-radio value="0">否</el-radio>
|
placeholder="请选择关联企业 (可选)"
|
||||||
<el-radio value="1">是</el-radio>
|
clearable
|
||||||
</el-radio-group>
|
filterable
|
||||||
|
:loading="enterpriseLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="opt in enterpriseOptions"
|
||||||
|
:key="opt.id"
|
||||||
|
:label="opt.name"
|
||||||
|
:value="opt.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<div class="rr-form-tip">由企业认证流程自动带过来, 手动修改可能与认证状态不一致</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="禁用标识">
|
<el-form-item label="禁用标识">
|
||||||
@ -253,3 +283,12 @@ async function submit() {
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rr-form-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ink-3, #94A3B8);
|
||||||
|
line-height: 18px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user