This commit is contained in:
2026-08-17 19:52:08 +08:00
parent c6a1bcf611
commit 0f352bbdf9
14 changed files with 142 additions and 33 deletions
@@ -36,6 +36,10 @@ test('supports tenant profile updates and authenticated qualification downloads'
assert.doesNotMatch(source, /data\.append\('qualificationType'/)
assert.match(source, /'SUPERSEDED'/)
assert.match(storeSource, /normalizeQualificationStatus\(item\.status\)/)
assert.match(source, /validTo: string \| null/)
assert.match(storeSource, /expiresAt: item\.validTo \|\| '长期有效'/)
assert.match(source, /enterpriseType: string \| null/)
assert.match(storeSource, /enterpriseType: profile\.enterpriseType \?\? ''/)
})
test('uploads the three required qualification fields through one multipart package request', () => {
@@ -65,6 +69,13 @@ test('starts empty and only loads server role and member records', () => {
assert.match(storeSource, /await updateMerchantMember/)
assert.match(storeSource, /await changeMerchantMemberStatus/)
assert.match(storeSource, /await resetMerchantMemberPassword/)
assert.match(source, /username: string/)
assert.match(source, /lastLoginAt: string \| null/)
assert.match(source, /memberCount: number/)
assert.match(storeSource, /account: member\.username/)
assert.match(storeSource, /lastLoginAt: formatLastLoginAt\(member\.lastLoginAt\)/)
assert.match(storeSource, /roles\.value\.find\(\(role\) => role\.id === roleId\)\?\.memberCount \?\? 0/)
assert.doesNotMatch(storeSource, /lastLoginAt: '服务端未提供'/)
})
test('shares the merchant permission map and preserves resources not rendered by the role editor', () => {
@@ -14,6 +14,12 @@ export interface MerchantTenantProfile {
contactName: string
contactPhone: string
contactEmail: string | null
contactWechat: string | null
enterpriseType: string | null
establishedDate: string | null
registeredCapital: number | string | null
registeredCapitalCurrency: string | null
registeredAddress: string | null
}
export interface UpdateMerchantTenantProfileRequest {
@@ -31,6 +37,7 @@ export interface MerchantQualification {
qualificationType: MerchantQualificationType
certificateNo: string | null
documentUrl: string
validTo: string | null
status: QualificationStatus
reviewComment: string | null
}
@@ -73,6 +80,7 @@ export interface MerchantRole {
roleType: 'SYSTEM' | 'CUSTOM' | string
status: 'ACTIVE' | 'INACTIVE' | string
description: string | null
memberCount: number
permissionIds: Array<number | string>
}
@@ -91,12 +99,14 @@ export interface MerchantMember {
membershipId: number | string
userId: number | string
authUserId: number | string
username: string
displayName: string
phone: string | null
email: string | null
memberType: 'OWNER' | string
memberNo: string
status: 'ACTIVE' | 'INACTIVE' | string
lastLoginAt: string | null
roleIds: Array<number | string>
}
@@ -38,6 +38,12 @@ export interface TenantCompanyProfile {
contactName: string
contactPhone: string
contactEmail: string
contactWechat: string
enterpriseType: string
establishedDate: string
registeredCapital: number | string | null
registeredCapitalCurrency: string
registeredAddress: string
auditStatus: MerchantTenantStatus
}
@@ -70,6 +76,7 @@ export interface TenantRole {
description: string
status: TenantRecordStatus
builtin: boolean
memberCount: number
permissionIds: string[]
permissionCodes: string[]
}
@@ -77,6 +84,13 @@ export interface TenantRole {
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())
function formatLastLoginAt(value: string | null) {
if (!value) return '从未登录'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return '-'
return date.toLocaleString('zh-CN', { hour12: false })
}
const emptyCompany: TenantCompanyProfile = {
companyName: '',
creditCode: '',
@@ -84,6 +98,12 @@ const emptyCompany: TenantCompanyProfile = {
contactName: '',
contactPhone: '',
contactEmail: '',
contactWechat: '',
enterpriseType: '',
establishedDate: '',
registeredCapital: null,
registeredCapitalCurrency: '',
registeredAddress: '',
auditStatus: 'PROFILE_REQUIRED',
}
@@ -107,6 +127,12 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
contactName: profile.contactName,
contactPhone: profile.contactPhone,
contactEmail: profile.contactEmail ?? '',
contactWechat: profile.contactWechat ?? '',
enterpriseType: profile.enterpriseType ?? '',
establishedDate: profile.establishedDate ?? '',
registeredCapital: profile.registeredCapital,
registeredCapitalCurrency: profile.registeredCapitalCurrency ?? '',
registeredAddress: profile.registeredAddress ?? '',
auditStatus: profile.status,
}
qualifications.value = qualificationRecords.map((item) => ({
@@ -119,7 +145,7 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
} as const)[item.qualificationType],
fileName: item.documentUrl.split('/').pop() || '资质文件',
status: normalizeQualificationStatus(item.status),
expiresAt: '-',
expiresAt: item.validTo || '长期有效',
updatedAt: '-',
documentUrl: item.documentUrl,
}))
@@ -141,6 +167,7 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
description: role.description ?? '暂无角色说明',
status: role.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE',
builtin: role.roleType === 'SYSTEM',
memberCount: role.memberCount,
permissionIds: grantedIds,
permissionCodes: role.roleType === 'SYSTEM' && role.roleCode === 'MERCHANT_OWNER' ? [...allPermissionCodes] : effectivePermissionCodes,
}
@@ -150,12 +177,12 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
return {
id: String(member.membershipId),
name: member.displayName || `企业成员 ${member.membershipId}`,
account: member.memberNo || `用户 ${member.authUserId}`,
account: member.username,
phone: member.phone ?? '',
email: member.email ?? '',
roleIds: member.roleIds.map(String),
status: member.status === 'ACTIVE' ? 'ACTIVE' : 'INACTIVE',
lastLoginAt: '服务端未提供',
lastLoginAt: formatLastLoginAt(member.lastLoginAt),
isAdministrator: member.memberType === 'OWNER',
}
}
@@ -189,6 +216,12 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
contactName: updated.contactName,
contactPhone: updated.contactPhone,
contactEmail: updated.contactEmail ?? '',
contactWechat: updated.contactWechat ?? '',
enterpriseType: updated.enterpriseType ?? '',
establishedDate: updated.establishedDate ?? '',
registeredCapital: updated.registeredCapital,
registeredCapitalCurrency: updated.registeredCapitalCurrency ?? '',
registeredAddress: updated.registeredAddress ?? '',
auditStatus: updated.status,
}
}
@@ -285,7 +318,7 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
}
function roleMemberCount(roleId: string) {
return members.value.filter((member) => member.roleIds.includes(roleId)).length
return roles.value.find((role) => role.id === roleId)?.memberCount ?? 0
}
function roleNames(roleIds: readonly string[]) {
@@ -494,6 +494,10 @@ function closeQualificationPreview() {
<dl class="profile-grid">
<div><dt>公司名称</dt><dd>{{ store.company.companyName || '-' }}</dd></div>
<div><dt>统一社会信用代码</dt><dd>{{ store.company.creditCode || '-' }}</dd></div>
<div><dt>企业类型</dt><dd>{{ store.company.enterpriseType || '-' }}</dd></div>
<div><dt>注册地</dt><dd>{{ store.company.registeredAddress || '-' }}</dd></div>
<div><dt>成立日期</dt><dd>{{ store.company.establishedDate || '-' }}</dd></div>
<div><dt>注册资本</dt><dd>{{ store.company.registeredCapital == null ? '-' : `${store.company.registeredCapital} ${store.company.registeredCapitalCurrency}`.trim() }}</dd></div>
<div><dt>法人姓名</dt><dd>{{ store.company.legalRepresentative || '-' }}</dd></div>
</dl>
</div>
@@ -507,6 +511,7 @@ function closeQualificationPreview() {
<div><dt>联系人</dt><dd>{{ store.company.contactName || '-' }}</dd></div>
<div><dt>联系电话</dt><dd>{{ store.company.contactPhone || '-' }}</dd></div>
<div><dt>联系邮箱</dt><dd>{{ store.company.contactEmail || '-' }}</dd></div>
<div><dt>联系微信</dt><dd>{{ store.company.contactWechat || '-' }}</dd></div>
</dl>
</div>
</el-tab-pane>
@@ -69,9 +69,7 @@ async function loadRoles() {
loading.value = true
loadError.value = ''
try {
const requests = [store.loadRoles()]
if (auth.hasPermission('company:member:list')) requests.push(store.loadMembers())
await Promise.all(requests)
await store.loadRoles()
} catch (error) {
loadError.value = error instanceof Error ? error.message : '角色与成员统计加载失败'
} finally {
@@ -88,7 +88,12 @@ test('implements company details, contact information and qualification workflow
assert.match(profileSource, /URL\.revokeObjectURL/)
assert.match(profileSource, /企业资料加载失败/)
assert.match(profileSource, /@click="loadCompanyContext"/)
assert.doesNotMatch(profileSource, /企业类型|注册资本|contactWechat|businessAddress/)
assert.match(profileSource, /<dt>企业类型<\/dt><dd>\{\{ store\.company\.enterpriseType/)
assert.match(profileSource, /<dt>注册地<\/dt><dd>\{\{ store\.company\.registeredAddress/)
assert.match(profileSource, /<dt>成立日期<\/dt><dd>\{\{ store\.company\.establishedDate/)
assert.match(profileSource, /<dt>注册资本<\/dt><dd>\{\{ store\.company\.registeredCapital/)
assert.match(profileSource, /<dt>联系微信<\/dt><dd>\{\{ store\.company\.contactWechat/)
assert.match(storeSource, /expiresAt: item\.validTo \|\| '长期有效'/)
})
test('implements member list, role assignment, password reset and status control', () => {
@@ -140,9 +145,8 @@ test('protects built-in roles and exposes role permission assignment', () => {
assert.match(rolesSource, /function assignPermissions\(role: TenantRole\)/)
assert.match(storeSource, /if \(role\.builtin\) throw new Error\('内置角色不能删除'\)/)
assert.match(storeSource, /if \(members\.value\.some\(\(member\) => member\.roleIds\.includes\(id\)\)\)/)
assert.match(rolesSource, /store\.loadRoles\(\)/)
assert.match(rolesSource, /auth\.hasPermission\('company:member:list'\).*store\.loadMembers\(\)/)
assert.match(rolesSource, /await Promise\.all\(requests\)/)
assert.match(rolesSource, /await store\.loadRoles\(\)/)
assert.doesNotMatch(rolesSource, /store\.loadMembers\(\)/)
assert.match(rolesSource, /await store\.createRole/)
assert.match(rolesSource, /await store\.updateRole/)
assert.match(rolesSource, /await store\.toggleRoleStatus/)