Files
client-web/src/modules/tenant-management/store/tenantAdministration.ts
T
2026-08-03 18:52:30 +08:00

309 lines
11 KiB
TypeScript

import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import {
changeMerchantMemberStatus,
changeMerchantRoleStatus,
createMerchantMember,
createMerchantRole,
deleteMerchantRole,
getMerchantMembers,
getMerchantPermissionResources,
getMerchantRoles,
getTenantProfile,
getTenantQualifications,
resetMerchantMemberPassword,
replaceMerchantRolePermissions,
updateMerchantMember,
updateMerchantRole,
updateTenantProfile,
type MerchantRole,
type MerchantMember,
type MerchantPermissionResource,
} from '@/modules/tenant-management/api/merchantIam'
import {
FRONTEND_TO_MERCHANT_PERMISSION_CODES,
TENANT_PERMISSION_GROUPS,
type MerchantTenantStatus,
} from '@/modules/tenant-management/contracts'
export type TenantRecordStatus = 'ACTIVE' | 'INACTIVE'
export interface TenantCompanyProfile {
companyName: string
creditCode: string
legalRepresentative: string
contactName: string
contactPhone: string
contactEmail: string
auditStatus: MerchantTenantStatus
}
export interface TenantQualification {
id: string
type: string
fileName: string
status: 'VERIFIED' | 'PENDING' | 'REJECTED' | 'EXPIRED'
expiresAt: string
updatedAt: string
documentUrl: string
}
export interface TenantMember {
id: string
name: string
account: string
phone: string
email: string
roleIds: string[]
status: TenantRecordStatus
lastLoginAt: string
isAdministrator: boolean
}
export interface TenantRole {
id: string
name: string
code: string
description: string
status: TenantRecordStatus
builtin: boolean
permissionIds: string[]
permissionCodes: string[]
}
const allPermissionCodes = TENANT_PERMISSION_GROUPS.flatMap((group) => group.items.map((item) => item.code))
const mappedMerchantPermissionCodes = new Set(Object.values(FRONTEND_TO_MERCHANT_PERMISSION_CODES).flat())
const emptyCompany: TenantCompanyProfile = {
companyName: '',
creditCode: '',
legalRepresentative: '',
contactName: '',
contactPhone: '',
contactEmail: '',
auditStatus: 'PROFILE_REQUIRED',
}
export const useTenantAdministrationStore = defineStore('tenant-administration', () => {
const company = ref<TenantCompanyProfile>({ ...emptyCompany })
const qualifications = ref<TenantQualification[]>([])
const members = ref<TenantMember[]>([])
const roles = ref<TenantRole[]>([])
const permissionResources = ref<MerchantPermissionResource[]>([])
const activeMemberCount = computed(() => members.value.filter((member) => member.status === 'ACTIVE').length)
async function loadCompanyContext() {
company.value = { ...emptyCompany }
qualifications.value = []
const [profile, qualificationRecords] = await Promise.all([
getTenantProfile(),
getTenantQualifications(),
])
company.value = {
companyName: profile.tenantName,
creditCode: profile.unifiedSocialCreditCode ?? '',
legalRepresentative: profile.legalName,
contactName: profile.contactName,
contactPhone: profile.contactPhone,
contactEmail: profile.contactEmail ?? '',
auditStatus: profile.status,
}
qualifications.value = qualificationRecords.map((item) => ({
id: String(item.id),
type: item.qualificationType === 'BUSINESS_LICENSE' ? '营业执照' : item.qualificationType,
fileName: item.documentUrl.split('/').pop() || '营业执照',
status: item.status === 'APPROVED' ? 'VERIFIED' : item.status,
expiresAt: '-',
updatedAt: '-',
documentUrl: item.documentUrl,
}))
}
function mapMerchantRole(role: MerchantRole): TenantRole {
const grantedIds = (role.permissionIds ?? []).map(String)
const grantedBackendCodes = new Set(permissionResources.value
.filter((resource) => grantedIds.includes(String(resource.id)))
.map((resource) => resource.permissionCode))
const permissionCodes = Object.entries(FRONTEND_TO_MERCHANT_PERMISSION_CODES)
.filter(([, backendCodes]) => backendCodes.some((code) => grantedBackendCodes.has(code)))
.map(([frontendCode]) => frontendCode)
return {
id: String(role.id),
name: role.roleName,
code: role.roleCode,
description: role.description ?? '暂无角色说明',
status: role.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE',
builtin: role.roleType === 'SYSTEM',
permissionIds: grantedIds,
permissionCodes: role.roleType === 'SYSTEM' && role.roleCode === 'MERCHANT_OWNER' ? [...allPermissionCodes] : permissionCodes,
}
}
function mapMerchantMember(member: MerchantMember): TenantMember {
return {
id: String(member.membershipId),
name: member.displayName || `企业成员 ${member.membershipId}`,
account: member.memberNo || `用户 ${member.authUserId}`,
phone: member.phone ?? '',
email: member.email ?? '',
roleIds: member.roleIds.map(String),
status: member.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE',
lastLoginAt: '服务端未提供',
isAdministrator: member.memberType === 'OWNER',
}
}
async function loadRoles() {
roles.value = []
permissionResources.value = []
const [roleRecords, resources] = await Promise.all([getMerchantRoles(), getMerchantPermissionResources()])
permissionResources.value = resources.filter((item) => item.status === 'ACTIVE')
roles.value = roleRecords.map(mapMerchantRole)
}
async function loadMembers() {
members.value = []
members.value = (await getMerchantMembers()).map(mapMerchantMember)
}
async function updateCompany(payload: TenantCompanyProfile) {
const updated = await updateTenantProfile({
tenantName: payload.companyName.trim(),
legalName: payload.legalRepresentative.trim(),
contactName: payload.contactName.trim(),
contactPhone: payload.contactPhone.trim(),
contactEmail: payload.contactEmail.trim(),
})
company.value = {
companyName: updated.tenantName,
creditCode: updated.unifiedSocialCreditCode ?? '',
legalRepresentative: updated.legalName,
contactName: updated.contactName,
contactPhone: updated.contactPhone,
contactEmail: updated.contactEmail ?? '',
auditStatus: updated.status,
}
}
async function createMember(payload: { name: string; account: string; password: string; phone: string; email: string; roleIds: string[]; memberNo: string }) {
const created = await createMerchantMember({
username: payload.account.trim(),
password: payload.password,
displayName: payload.name.trim(),
phone: payload.phone.trim(),
email: payload.email.trim(),
memberType: 'EMPLOYEE',
memberNo: payload.memberNo.trim(),
roleIds: payload.roleIds,
})
members.value.unshift(mapMerchantMember(created))
}
async function updateMember(id: string, payload: Pick<TenantMember, 'name' | 'phone' | 'email' | 'roleIds'> & { memberNo: string }) {
const member = members.value.find((item) => item.id === id)
if (!member) throw new Error('成员不存在')
const updated = await updateMerchantMember(id, {
displayName: payload.name.trim(),
phone: payload.phone.trim(),
email: payload.email.trim(),
memberType: member.isAdministrator ? 'OWNER' : 'EMPLOYEE',
memberNo: payload.memberNo.trim(),
roleIds: payload.roleIds,
})
Object.assign(member, mapMerchantMember(updated))
}
async function toggleMemberStatus(id: string) {
const member = members.value.find((item) => item.id === id)
if (!member) throw new Error('成员不存在')
if (member.isAdministrator) throw new Error('企业管理员不能停用')
const updated = await changeMerchantMemberStatus(id, member.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE')
Object.assign(member, mapMerchantMember(updated))
}
async function resetMemberPassword(id: string, newPassword: string) {
await resetMerchantMemberPassword(id, newPassword)
}
async function createRole(payload: Pick<TenantRole, 'name' | 'code' | 'description'>) {
if (roles.value.some((role) => role.code === payload.code)) throw new Error('角色编码已存在')
const created = await createMerchantRole({ roleCode: payload.code, roleName: payload.name, description: payload.description })
roles.value.push(mapMerchantRole(created))
}
async function updateRole(id: string, payload: Pick<TenantRole, 'name' | 'description'>) {
const role = roles.value.find((item) => item.id === id)
if (!role) throw new Error('角色不存在')
if (role.builtin) throw new Error('内置角色不能编辑')
const updated = await updateMerchantRole(id, { roleName: payload.name.trim(), description: payload.description.trim() })
Object.assign(role, mapMerchantRole(updated))
}
async function toggleRoleStatus(id: string) {
const role = roles.value.find((item) => item.id === id)
if (!role) throw new Error('角色不存在')
if (role.builtin) throw new Error('内置角色不能停用')
const updated = await changeMerchantRoleStatus(id, role.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE')
Object.assign(role, mapMerchantRole(updated))
}
async function deleteRole(id: string) {
const role = roles.value.find((item) => item.id === id)
if (!role) throw new Error('角色不存在')
if (role.builtin) throw new Error('内置角色不能删除')
if (members.value.some((member) => member.roleIds.includes(id))) throw new Error('该角色仍有成员使用,不能删除')
await deleteMerchantRole(id)
roles.value = roles.value.filter((item) => item.id !== id)
}
async function setRolePermissions(id: string, permissionCodes: string[]) {
const role = roles.value.find((item) => item.id === id)
if (!role) throw new Error('角色不存在')
if (role.builtin) throw new Error('内置管理员权限不能修改')
const backendCodes = new Set(permissionCodes.flatMap((code) => FRONTEND_TO_MERCHANT_PERMISSION_CODES[code] ?? []))
const selectedPermissionIds = permissionResources.value
.filter((resource) => backendCodes.has(resource.permissionCode))
.map((resource) => resource.id)
const mappedResourceIds = new Set(permissionResources.value
.filter((resource) => mappedMerchantPermissionCodes.has(resource.permissionCode))
.map((resource) => String(resource.id)))
// The endpoint replaces every ID, including MENU nodes this screen does not render.
const preservedPermissionIds = role.permissionIds.filter((permissionId) => !mappedResourceIds.has(permissionId))
const nextPermissionIds = [...new Set([...preservedPermissionIds, ...selectedPermissionIds])]
await replaceMerchantRolePermissions(id, nextPermissionIds)
role.permissionIds = nextPermissionIds.map(String)
role.permissionCodes = [...new Set(permissionCodes)]
}
function roleMemberCount(roleId: string) {
return members.value.filter((member) => member.roleIds.includes(roleId)).length
}
function roleNames(roleIds: readonly string[]) {
return roleIds.map((id) => roles.value.find((role) => role.id === id)?.name).filter((name): name is string => Boolean(name))
}
return {
company,
qualifications,
members,
roles,
activeMemberCount,
loadCompanyContext,
loadRoles,
loadMembers,
updateCompany,
createMember,
updateMember,
toggleMemberStatus,
resetMemberPassword,
createRole,
updateRole,
toggleRoleStatus,
deleteRole,
setRolePermissions,
roleMemberCount,
roleNames,
}
})