完善企业编辑页面并修复采购建单重试

This commit is contained in:
2026-10-30 15:42:38 +08:00
parent d6ef770e48
commit 55868177cd
19 changed files with 568 additions and 224 deletions
@@ -0,0 +1,113 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { useTenantAdministrationStore } from '../store/tenantAdministration'
import { useAuthStore } from '@/modules/auth/public'
const props = withDefaults(defineProps<{ memberId?: string }>(), { memberId: '' })
const router = useRouter()
const store = useTenantAdministrationStore()
const auth = useAuthStore()
const editingId = computed(() => props.memberId)
const title = computed(() => editingId.value ? '编辑成员' : '新增成员')
const canGrantMemberRole = computed(() => auth.hasPermission('company:member:grant-role'))
const canSave = computed(() => auth.hasPermission(editingId.value ? 'company:member:edit' : 'company:member:create'))
const loading = ref(false)
const loadError = ref('')
const saving = ref(false)
const formRef = ref<FormInstance>()
const form = reactive({ name: '', account: '', password: '', phone: '', email: '', roleIds: [] as string[] })
const memberRules: FormRules = {
name: [{ required: true, whitespace: true, message: '请输入成员姓名', trigger: 'blur' }],
account: [
{ required: true, message: '请输入登录账号', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9_.@-]{4,64}$/, message: '账号需为 4–64 位字母、数字或 _ . @ -', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入初始密码', trigger: 'blur' },
{ pattern: /^(?=.*[A-Za-z])(?=.*\d).{8,64}$/, message: '密码需为 8–64 位,并同时包含字母和数字', trigger: 'blur' },
],
phone: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的中国大陆登录手机号', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }],
roleIds: [{ type: 'array', required: true, min: 1, message: '请至少选择一个成员角色', trigger: 'change' }],
}
let loadVersion = 0
async function loadEditor() {
const version = ++loadVersion
loading.value = true
loadError.value = ''
Object.assign(form, { name: '', account: '', password: '', phone: '', email: '', roleIds: [] })
try {
await Promise.all([store.loadRoles(), ...(editingId.value ? [store.loadMembers()] : [])])
if (version !== loadVersion) return
if (editingId.value) {
const member = store.members.find(item => item.id === editingId.value)
if (!member) throw new Error('该成员不存在或已不可访问,请返回成员列表重新选择')
Object.assign(form, { name: member.name, account: member.account, phone: member.phone, email: member.email, roleIds: [...member.roleIds] })
}
formRef.value?.clearValidate()
} catch (error) {
if (version === loadVersion) loadError.value = error instanceof Error ? error.message : '成员资料加载失败'
} finally {
if (version === loadVersion) loading.value = false
}
}
onMounted(() => void loadEditor())
watch(() => props.memberId, () => void loadEditor())
function returnToList() { void router.push({ name: 'members' }) }
async function saveMember() {
if (saving.value || loading.value || loadError.value || !canSave.value) return
form.name = form.name.trim()
form.account = form.account.trim()
form.phone = form.phone.trim()
form.email = form.email.trim()
const valid = editingId.value
? await formRef.value?.validateField(['name', 'account', 'phone', 'email', 'roleIds']).then(() => true).catch(() => false)
: await formRef.value?.validate().catch(() => false)
if (!valid) return
saving.value = true
try {
if (editingId.value) {
await store.updateMember(editingId.value, { name: form.name, memberNo: form.account, phone: form.phone, email: form.email, roleIds: [...form.roleIds] })
ElMessage.success('成员信息已更新')
} else {
await store.createMember({ ...form, memberNo: form.account, roleIds: [...form.roleIds] })
ElMessage.success('成员已新增')
}
form.password = ''
await router.push({ name: 'members' })
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '成员保存失败,请重试')
} finally { saving.value = false }
}
</script>
<template>
<section v-loading="loading" class="company-editor client-ui" :aria-busy="loading">
<header class="company-editor-heading"><h2>{{ title }}</h2><el-button :disabled="saving" @click="returnToList">返回列表</el-button></header>
<el-alert v-if="loadError" type="error" :closable="false" :title="loadError"><el-button @click="loadEditor">重新加载</el-button></el-alert>
<el-form v-else ref="formRef" :model="form" :rules="memberRules" label-position="top" :disabled="saving || loading || !canSave" scroll-to-error @submit.prevent="saveMember">
<section class="company-editor-section">
<h3>账号信息</h3>
<div class="company-editor-grid">
<el-form-item label="成员姓名" prop="name" required><el-input v-model="form.name" maxlength="64" placeholder="请输入成员姓名" /></el-form-item>
<el-form-item label="登录账号" prop="account" required><el-input v-model="form.account" :disabled="Boolean(editingId)" maxlength="64" placeholder="4–64 位字母、数字或 _ . @ -" /></el-form-item>
<el-form-item v-if="!editingId" label="初始密码" prop="password" required><el-input v-model="form.password" type="password" show-password autocomplete="new-password" maxlength="64" placeholder="8–64 位,同时包含字母和数字" /></el-form-item>
</div>
</section>
<section class="company-editor-section">
<h3>联系信息与角色</h3>
<div class="company-editor-grid">
<el-form-item label="登录手机号" prop="phone"><el-input v-model="form.phone" maxlength="11" inputmode="tel" autocomplete="tel" placeholder="请输入登录手机号(选填)" /><span class="company-editor-help">用于该成员手机号登录;修改后会同步认证账号并退出该成员现有会话。</span></el-form-item>
<el-form-item label="邮箱" prop="email"><el-input v-model="form.email" maxlength="128" placeholder="请输入邮箱(选填)" /></el-form-item>
<el-form-item label="成员角色" prop="roleIds" required><el-select v-model="form.roleIds" multiple :disabled="Boolean(editingId) && !canGrantMemberRole" placeholder="请选择角色" popper-class="company-editor-popper"><el-option v-for="role in store.roles.filter((item) => item.status === 'ACTIVE' && (!item.builtin || form.roleIds.includes(item.id)))" :key="role.id" :label="role.name" :value="role.id" :disabled="role.builtin" /></el-select></el-form-item>
</div>
</section>
<p v-if="!editingId" class="company-editor-help">请通过安全渠道将初始密码交给成员;前端不会保存或再次展示该密码。</p>
<footer class="company-editor-actions"><el-button :disabled="saving" @click="returnToList">返回列表</el-button><el-button v-if="canSave" type="primary" native-type="submit" :loading="saving">{{ editingId ? '保存修改' : '新增成员' }}</el-button></footer>
</el-form>
</section>
</template>
<style scoped src="./companyEditor.css"></style>