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 companyType: string registeredAddress: string establishedDate: string registeredCapital: string legalRepresentative: string legalRepresentativePhone: string contactName: string contactPhone: string contactEmail: string contactWechat: string businessAddress: 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> = { '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 initialRoles: TenantRole[] = [ { id: 'ROLE-ADMIN', name: '企业管理员', code: 'TENANT_ADMIN', description: '企业内置管理员,拥有全部客户端权限', status: 'ACTIVE', builtin: true, permissionIds: [], permissionCodes: [...allPermissionCodes] }, { id: 'ROLE-ORDER', name: '订单专员', code: 'ORDER_OPERATOR', description: '负责头程订单创建、提交和跟进', status: 'ACTIVE', builtin: false, permissionIds: [], permissionCodes: ['account:profile:view', 'dashboard:view', 'order:first-leg:list', 'order:first-leg:create', 'order:first-leg:edit', 'order:first-leg:submit', 'order:first-leg:cancel', 'company:profile:view'] }, { id: 'ROLE-VIEWER', name: '业务查看员', code: 'BUSINESS_VIEWER', description: '只读查看企业资料和业务订单', status: 'ACTIVE', builtin: false, permissionIds: [], permissionCodes: ['account:profile:view', 'dashboard:view', 'order:first-leg:list', 'company:profile:view'] }, ] const initialMembers: TenantMember[] = [ { id: 'MEMBER-001', name: '企业管理员', account: 'customer', phone: '138****6601', email: 'admin@example.com', roleIds: ['ROLE-ADMIN'], status: 'ACTIVE', lastLoginAt: '2026-07-28 16:42', isAdministrator: true }, { id: 'MEMBER-002', name: '张伟', account: 'zhangwei', phone: '138****6789', email: 'zhangwei@example.com', roleIds: ['ROLE-ORDER'], status: 'ACTIVE', lastLoginAt: '2026-07-28 09:18', isAdministrator: false }, { id: 'MEMBER-003', name: '李娜', account: 'lina', phone: '137****2210', email: 'lina@example.com', roleIds: ['ROLE-VIEWER'], status: 'INACTIVE', lastLoginAt: '2026-07-12 11:30', isAdministrator: false }, ] export const useTenantAdministrationStore = defineStore('tenant-administration', () => { const company = ref({ companyName: '示例贸易有限公司', creditCode: '91330100MA2XXXXXXX', companyType: '有限责任公司', registeredAddress: '浙江省杭州市滨江区', establishedDate: '2018-06-15', registeredCapital: '500 万人民币', legalRepresentative: '陈明', legalRepresentativePhone: '139****8866', contactName: '张伟', contactPhone: '138****6789', contactEmail: 'business@example.com', contactWechat: 'cc_business', businessAddress: '浙江省杭州市滨江区演示大道 88 号', auditStatus: 'PROFILE_REQUIRED', }) const qualifications = ref([ { id: 'QUAL-001', type: '营业执照', fileName: '示例贸易有限公司营业执照.pdf', status: 'VERIFIED', expiresAt: '2028-06-15', updatedAt: '2026-07-27 14:20', documentUrl: '' }, { id: 'QUAL-002', type: '法人身份证', fileName: '法人身份证明.pdf', status: 'VERIFIED', expiresAt: '2030-01-01', updatedAt: '2026-07-27 14:22', documentUrl: '' }, { id: 'QUAL-003', type: '货代资质证书', fileName: '货代资质证书.pdf', status: 'PENDING', expiresAt: '2027-03-01', updatedAt: '2026-07-28 10:08', documentUrl: '' }, ]) const members = ref(structuredClone(initialMembers)) const roles = ref(structuredClone(initialRoles)) const rolesLoadedFromApi = ref(false) const membersLoadedFromApi = ref(false) const permissionResources = ref([]) const activeMemberCount = computed(() => members.value.filter((member) => member.status === 'ACTIVE').length) async function loadCompanyContext() { const [profile, qualificationRecords] = await Promise.all([ getTenantProfile(), getTenantQualifications(), ]) company.value = { ...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() { rolesLoadedFromApi.value = true roles.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() { membersLoadedFromApi.value = true 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 = { ...company.value, companyName: updated.tenantName, creditCode: updated.unifiedSocialCreditCode ?? '', legalRepresentative: updated.legalName, contactName: updated.contactName, contactPhone: updated.contactPhone, contactEmail: updated.contactEmail ?? '', auditStatus: updated.status, } } function setCompanyAuditStatusForPreview(status: TenantCompanyProfile['auditStatus']) { company.value.auditStatus = status } function upsertQualification(payload: Omit) { const existing = qualifications.value.find((item) => item.type === payload.type) const next = { ...payload, status: 'PENDING' as const, updatedAt: new Date().toLocaleString('zh-CN', { hour12: false }), } if (existing) Object.assign(existing, next) else qualifications.value.unshift({ id: `QUAL-${Date.now()}`, ...next }) company.value.auditStatus = 'PENDING' } 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 & { 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) { 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) { 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, rolesLoadedFromApi, membersLoadedFromApi, loadCompanyContext, loadRoles, loadMembers, updateCompany, setCompanyAuditStatusForPreview, upsertQualification, createMember, updateMember, toggleMemberStatus, resetMemberPassword, createRole, updateRole, toggleRoleStatus, deleteRole, setRolePermissions, roleMemberCount, roleNames, } })