Files
client-web/src/modules/tenant-management/views/MemberEditorView.vue
T

114 lines
7.1 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.
<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>