Files
oci-portal-dash/src/components/tenant/UserFormModal.vue
T
Wang Defa 5464d2dfea
CI / test (push) Successful in 27s
Release / release (push) Successful in 26s
发布 0.1.0:通知渠道、告警规则与多项体验修复
2026-07-10 17:31:19 +08:00

239 lines
8.4 KiB
Vue

<script setup lang="ts">
import { NCheckbox, NInput } from 'naive-ui'
import { computed, reactive, ref, watch } from 'vue'
import {
createUser,
getIdentitySetting,
getUserDetail,
updateUser,
type UpdateUserBody,
} from '@/api/tenant'
import FormField from '@/components/FormField.vue'
import FormModal from '@/components/FormModal.vue'
import type { IamUser, IamUserDetail } from '@/types/api'
import { useToast } from '@/composables/useToast'
// user 非空即编辑模式:打开时拉取域档案预填充,提交只发送与原值不同的字段
const props = defineProps<{ show: boolean; cfgId: number; domainId?: string; user?: IamUser | null }>()
const emit = defineEmits<{ 'update:show': [boolean]; created: [] }>()
const message = useToast()
const submitting = ref(false)
// 域开启「用户需要提供主电子邮件地址」时邮箱必填
const emailRequired = ref(false)
// 编辑模式的域档案;加载失败保持 null,降级为「留空不修改、勾选仅添加」
const detail = ref<IamUserDetail | null>(null)
const detailLoading = ref(false)
const blank = {
name: '',
givenName: '',
familyName: '',
email: '',
description: '',
grantDomainAdmin: false,
addToAdminGroup: false,
}
const form = reactive({ ...blank })
const editing = computed(() => !!props.user)
// 经典 IAM 用户不在身份域中,姓名与管理员状态均不可操作
const classicUser = computed(() => editing.value && !!detail.value && !detail.value.inDomain)
watch(
() => props.show,
(v) => {
if (!v) return
Object.assign(form, blank)
detail.value = null
if (props.user) {
form.name = props.user.name
form.email = props.user.email
form.description = props.user.description
void loadDetail(props.user.id)
}
void loadIdentitySetting()
},
)
async function loadIdentitySetting() {
try {
emailRequired.value = (await getIdentitySetting(props.cfgId, props.domainId)).primaryEmailRequired
} catch {
emailRequired.value = false
}
}
// 拉取域档案并回填表单;返回时弹窗已切到别的用户则丢弃结果
async function loadDetail(userId: string) {
detailLoading.value = true
try {
const d = await getUserDetail(props.cfgId, userId, props.domainId)
if (props.user?.id !== userId) return
detail.value = d
if (!d.inDomain) return
form.givenName = d.givenName
form.familyName = d.familyName
form.grantDomainAdmin = d.isDomainAdmin
form.addToAdminGroup = d.inAdminGroup
} catch {
if (props.user?.id === userId)
message.warning('用户详情加载失败:姓名留空表示不修改,管理员选项仅执行添加')
} finally {
if (props.user?.id === userId) detailLoading.value = false
}
}
const nameRequired = computed(() => !editing.value || !!detail.value?.inDomain)
const nameHint = computed(() => {
if (!editing.value || detailLoading.value) return undefined
if (classicUser.value) return '经典 IAM 用户不支持修改姓名'
if (!detail.value) return '详情加载失败:留空表示不修改,仅身份域用户可改姓名'
return undefined
})
const adminHint = computed(() => {
if (!editing.value)
return form.grantDomainAdmin && form.addToAdminGroup
? '同时勾选时先授予身份域管理员,再加入管理员组'
: undefined
if (detailLoading.value) return '正在加载当前管理员状态…'
if (classicUser.value) return '经典 IAM 用户不在身份域中,无法调整管理员角色与组'
if (!detail.value) return '详情加载失败:仅执行添加操作,不会撤销已有的管理员角色或组成员'
return '已按当前状态勾选;取消勾选保存后将撤销对应的管理员角色 / 组成员'
})
const canSubmit = computed(() => {
const emailOk = !emailRequired.value || !!form.email.trim()
if (!editing.value)
return !!form.name.trim() && !!form.givenName.trim() && !!form.familyName.trim() && emailOk
if (detailLoading.value) return false
if (detail.value?.inDomain && (!form.givenName.trim() || !form.familyName.trim())) return false
return emailOk
})
// 编辑只提交与原值不同的字段;域档案在手时管理员勾选与当前状态比对,取消勾选即撤销
function buildUpdateBody(u: IamUser): UpdateUserBody {
const body: UpdateUserBody = {}
if (form.email.trim() !== u.email) body.email = form.email.trim()
if (form.description.trim() !== u.description) body.description = form.description.trim()
if (classicUser.value) return body
const d = detail.value
if (d?.inDomain) {
if (form.givenName.trim() !== d.givenName) body.givenName = form.givenName.trim()
if (form.familyName.trim() !== d.familyName) body.familyName = form.familyName.trim()
if (form.grantDomainAdmin !== d.isDomainAdmin) body.grantDomainAdmin = form.grantDomainAdmin
if (form.addToAdminGroup !== d.inAdminGroup) body.addToAdminGroup = form.addToAdminGroup
return body
}
if (form.givenName.trim()) body.givenName = form.givenName.trim()
if (form.familyName.trim()) body.familyName = form.familyName.trim()
if (form.grantDomainAdmin) body.grantDomainAdmin = true
if (form.addToAdminGroup) body.addToAdminGroup = true
return body
}
async function submitEdit(u: IamUser) {
const body = buildUpdateBody(u)
if (!Object.keys(body).length) {
emit('update:show', false)
return
}
await updateUser(props.cfgId, u.id, body, props.domainId)
message.success(`用户「${u.name}」已更新`)
}
async function submitCreate() {
const created = await createUser(
props.cfgId,
{
name: form.name.trim(),
givenName: form.givenName.trim(),
familyName: form.familyName.trim(),
email: form.email.trim() || undefined,
description: form.description.trim() || undefined,
grantDomainAdmin: form.grantDomainAdmin,
addToAdminGroup: form.addToAdminGroup,
},
props.domainId,
)
message.success(`用户「${created.name}」已创建,可再执行「重置密码」下发初始密码`)
}
async function submit() {
if (!canSubmit.value) return
submitting.value = true
try {
if (props.user) await submitEdit(props.user)
else await submitCreate()
emit('update:show', false)
emit('created')
} catch (e) {
message.error(e instanceof Error ? e.message : editing.value ? '更新失败' : '创建失败')
} finally {
submitting.value = false
}
}
</script>
<template>
<FormModal
:show="show"
:title="editing ? `修改用户 · ${user?.name ?? ''}` : '添加用户'"
:submitting="submitting"
:submit-disabled="!canSubmit"
:submit-text="editing ? '保存' : '创建'"
@update:show="emit('update:show', $event)"
@submit="submit"
>
<FormField v-if="!editing" label="用户名" required>
<NInput v-model:value="form.name" placeholder="new-user" @keyup.enter="submit" />
</FormField>
<div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
<FormField label="名字" :required="nameRequired" :hint="nameHint">
<NInput
v-model:value="form.givenName"
placeholder="First name"
:disabled="classicUser"
:loading="detailLoading"
/>
</FormField>
<FormField label="姓氏" :required="nameRequired">
<NInput
v-model:value="form.familyName"
placeholder="Last name"
:disabled="classicUser"
:loading="detailLoading"
/>
</FormField>
</div>
<FormField
label="邮箱"
:required="emailRequired"
:hint="
emailRequired
? '域已开启「用户需要提供主电子邮件地址」,必须填写'
: '用于接收欢迎邮件与找回密码'
"
>
<NInput v-model:value="form.email" placeholder="user@example.com" />
</FormField>
<FormField label="备注" :hint="editing ? undefined : '留空默认取「名字 姓氏」'">
<NInput v-model:value="form.description" placeholder="可选" />
</FormField>
<div class="mb-2 flex flex-col gap-1.5">
<NCheckbox
v-model:checked="form.grantDomainAdmin"
:disabled="classicUser || detailLoading"
>
{{ editing ? '身份域管理员' : '添加到身份域管理员' }}
</NCheckbox>
<NCheckbox v-model:checked="form.addToAdminGroup" :disabled="classicUser || detailLoading">
{{ editing ? '租户管理员组成员' : '添加到租户管理员组' }}
</NCheckbox>
<div v-if="adminHint" class="text-xs text-ink-3">{{ adminHint }}</div>
</div>
</FormModal>
</template>