chore: initial commit
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import {
|
||||
changeMerchantMemberStatus,
|
||||
createMerchantMember,
|
||||
createMerchantRole,
|
||||
getMerchantMembers,
|
||||
getMerchantPermissionResources,
|
||||
getMerchantRoles,
|
||||
getTenantProfile,
|
||||
getTenantQualifications,
|
||||
resetMerchantMemberPassword,
|
||||
replaceMerchantRolePermissions,
|
||||
updateMerchantMember,
|
||||
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
|
||||
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))
|
||||
|
||||
const frontendToBackendPermissionCodes: 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 initialRoles: TenantRole[] = [
|
||||
{ id: 'ROLE-ADMIN', name: '企业管理员', code: 'TENANT_ADMIN', description: '企业内置管理员,拥有全部客户端权限', status: 'ACTIVE', builtin: true, permissionCodes: [...allPermissionCodes] },
|
||||
{ id: 'ROLE-ORDER', name: '订单专员', code: 'ORDER_OPERATOR', description: '负责头程订单创建、提交和跟进', status: 'ACTIVE', builtin: false, 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, 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<TenantCompanyProfile>({
|
||||
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<TenantQualification[]>([
|
||||
{ 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<TenantMember[]>(structuredClone(initialMembers))
|
||||
const roles = ref<TenantRole[]>(structuredClone(initialRoles))
|
||||
const rolesLoadedFromApi = ref(false)
|
||||
const membersLoadedFromApi = ref(false)
|
||||
const permissionResources = ref<MerchantPermissionResource[]>([])
|
||||
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(frontendToBackendPermissionCodes)
|
||||
.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',
|
||||
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<TenantQualification, 'id' | 'status' | 'updatedAt'>) {
|
||||
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<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))
|
||||
}
|
||||
|
||||
function updateRole(id: string, payload: Pick<TenantRole, 'name' | 'description'>) {
|
||||
const role = roles.value.find((item) => item.id === id)
|
||||
if (!role) throw new Error('角色不存在')
|
||||
Object.assign(role, payload)
|
||||
}
|
||||
|
||||
function toggleRoleStatus(id: string) {
|
||||
const role = roles.value.find((item) => item.id === id)
|
||||
if (!role) throw new Error('角色不存在')
|
||||
if (role.builtin) throw new Error('内置角色不能停用')
|
||||
role.status = role.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'
|
||||
}
|
||||
|
||||
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('该角色仍有成员使用,不能删除')
|
||||
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) => frontendToBackendPermissionCodes[code] ?? []))
|
||||
const permissionIds = permissionResources.value
|
||||
.filter((resource) => backendCodes.has(resource.permissionCode))
|
||||
.map((resource) => resource.id)
|
||||
await replaceMerchantRolePermissions(id, permissionIds)
|
||||
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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user