优化构架
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
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,
|
||||
type MerchantTenantStatus,
|
||||
} from '@/api/merchantIam'
|
||||
|
||||
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[]
|
||||
}
|
||||
|
||||
export interface TenantPermissionItem {
|
||||
code: string
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface TenantPermissionGroup {
|
||||
key: string
|
||||
label: string
|
||||
items: TenantPermissionItem[]
|
||||
}
|
||||
|
||||
export const TENANT_PERMISSION_GROUPS: readonly TenantPermissionGroup[] = [
|
||||
{
|
||||
key: 'account',
|
||||
label: '个人账号',
|
||||
items: [{ code: 'account:profile:view', label: '维护我的信息', description: '查看并维护当前成员自己的资料和登录密码' }],
|
||||
},
|
||||
{
|
||||
key: 'dashboard',
|
||||
label: '业务总览',
|
||||
items: [{ code: 'dashboard:view', label: '查看总览', description: '查看企业统计和最近订单' }],
|
||||
},
|
||||
{
|
||||
key: 'orders',
|
||||
label: '头程订单',
|
||||
items: [
|
||||
{ code: 'order:first-leg:list', label: '查看订单', description: '查看本企业头程订单列表和详情' },
|
||||
{ code: 'order:first-leg:create', label: '创建订单', description: '新建并保存订单草稿' },
|
||||
{ code: 'order:first-leg:edit', label: '编辑订单', description: '编辑本企业尚未提交的订单草稿' },
|
||||
{ code: 'order:first-leg:submit', label: '提交订单', description: '提交草稿进入平台审核' },
|
||||
{ code: 'order:first-leg:cancel', label: '取消订单', description: '在允许的状态下取消订单' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'company',
|
||||
label: '企业资料',
|
||||
items: [
|
||||
{ code: 'company:profile:view', label: '查看资料', description: '查看企业、联系人和资质资料' },
|
||||
{ code: 'company:profile:edit', label: '编辑资料', description: '维护企业联系信息和业务地址' },
|
||||
{ code: 'company:qualification:upload', label: '更新资质', description: '上传或更新企业资质文件' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'members',
|
||||
label: '企业成员',
|
||||
items: [
|
||||
{ code: 'company:member:list', label: '查看成员', description: '查看本企业成员列表' },
|
||||
{ code: 'company:member:write', label: '维护成员', description: '新增、编辑、启停企业成员' },
|
||||
{ code: 'company:member:reset-password', label: '重置密码', description: '重置企业成员登录密码' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'roles',
|
||||
label: '角色与权限',
|
||||
items: [
|
||||
{ code: 'company:role:list', label: '查看角色', description: '查看本企业角色列表' },
|
||||
{ code: 'company:role:write', label: '维护角色', description: '新增、编辑、启停企业角色' },
|
||||
{ code: 'company:permission:list', label: '查看权限', description: '查看角色拥有的菜单和操作权限' },
|
||||
{ code: 'company:permission:assign', label: '分配权限', description: '为本企业角色分配菜单及操作权限' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const allPermissionCodes = TENANT_PERMISSION_GROUPS.flatMap((group) => group.items.map((item) => item.code))
|
||||
|
||||
export const FRONTEND_TO_MERCHANT_PERMISSION_CODES: Readonly<Record<string, readonly string[]>> = {
|
||||
'order:first-leg:list': ['merchant:order:list', 'merchant:order:view'],
|
||||
'order:first-leg:create': ['merchant:order:create'],
|
||||
'order:first-leg:edit': ['merchant:order:edit'],
|
||||
'order:first-leg:submit': ['merchant:order:submit'],
|
||||
'order:first-leg:cancel': ['merchant:order:cancel'],
|
||||
'company:profile:view': ['merchant:profile:view'],
|
||||
'company:profile:edit': ['merchant:profile:edit'],
|
||||
'company:qualification:upload': ['merchant:qualification:upload'],
|
||||
'company:member:list': ['merchant:member:list'],
|
||||
'company:member:write': ['merchant:member:create', 'merchant:member:edit', 'merchant:member:grant-role', 'merchant:member:status'],
|
||||
'company:member:reset-password': ['merchant:member:reset-password'],
|
||||
'company:role:list': ['merchant:role:list'],
|
||||
'company:role:write': ['merchant:role:create', 'merchant:role:edit', 'merchant:role:status', 'merchant:role:delete'],
|
||||
'company:permission:list': ['merchant:role:list'],
|
||||
'company:permission:assign': ['merchant:role:grant'],
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user