diff --git a/README.md b/README.md index a58b4f1..01b392e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ pnpm test 认证会话保存 Access Token 和轮换后的 Refresh Token,并对并发 401 请求执行单次刷新。刷新或会话上下文加载遇到临时网络故障、超时等非认证错误时保留当前有效 Token,只有服务端明确返回 401/会话失效才清理登录状态。客户端只接受 `app_code` 为 `MERCHANT` 的 JWT;页面权限取 JWT 声明与后端启用权限资源的交集。前端权限仅用于导航和交互控制,服务端仍须按用户、租户和权限编码完成最终授权。 -本地联调账号提示和默认用户名只在 Vite 开发模式下启用,URL 显式传入的登录用户名仍优先。生产登录表单默认留空,生产构建产物不得包含本地联调账号或开发提示文案。 +登录表单在开发和生产环境均默认留空;URL 显式传入的登录用户名仍优先。源码和构建产物不得包含联调账号、默认密码或开发提示文案。 “我的信息”使用真实个人资料和修改密码接口,姓名、电话、邮箱按服务端约束维护;修改密码成功后清理旧会话并返回登录页。成员列表返回姓名、联系方式和 `roleIds`,新增、编辑、角色全量保存、启停和管理员重置密码均使用真实接口,不回退静态成功。角色页同时加载成员关系,直接打开或刷新后成员数仍按真实 `roleIds` 统计。角色列表返回 `permissionIds`,页面可回显并全量保存角色权限;角色编辑、启停、删除分别调用 `PUT /merchant/v1/iam/roles/{roleId}`、`PUT /merchant/v1/iam/roles/{roleId}/status` 和 `DELETE /merchant/v1/iam/roles/{roleId}`,内置角色及仍有关联成员的角色继续受保护。 diff --git a/index.html b/index.html index be4f977..462b6e4 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,12 @@ - - 虫虫出海客户工作台 + + + 柏悦物流客户工作台
- diff --git a/src/api/merchantIam.test.mjs b/src/api/merchantIam.test.mjs index 071c428..a129319 100644 --- a/src/api/merchantIam.test.mjs +++ b/src/api/merchantIam.test.mjs @@ -33,9 +33,12 @@ test('uses membershipId and full replacement arrays in authorization writes', () assert.doesNotMatch(source, /data: \{ userId, roleIds \}/) }) -test('clears mock records before loading server role and member lists', () => { - assert.match(storeSource, /rolesLoadedFromApi\.value = true\s+roles\.value = \[\]/) - assert.match(storeSource, /membersLoadedFromApi\.value = true\s+members\.value = \[\]/) +test('starts empty and only loads server role and member records', () => { + assert.match(storeSource, /const members = ref\(\[\]\)/) + assert.match(storeSource, /const roles = ref\(\[\]\)/) + assert.match(storeSource, /async function loadRoles\(\) \{\s+roles\.value = \[\]/) + assert.match(storeSource, /async function loadMembers\(\) \{\s+members\.value = \[\]/) + assert.doesNotMatch(storeSource, /initialRoles|initialMembers|示例贸易|演示大道/) assert.match(storeSource, /await createMerchantRole/) assert.match(storeSource, /await updateMerchantRole/) assert.match(storeSource, /await changeMerchantRoleStatus/) diff --git a/src/assets/brand/baiyue-favicon.png b/src/assets/brand/baiyue-favicon.png new file mode 100644 index 0000000..e6151c8 Binary files /dev/null and b/src/assets/brand/baiyue-favicon.png differ diff --git a/src/assets/brand/baiyue-logo-dark.png b/src/assets/brand/baiyue-logo-dark.png new file mode 100644 index 0000000..d23f9a9 Binary files /dev/null and b/src/assets/brand/baiyue-logo-dark.png differ diff --git a/src/data/firstLegParties.ts b/src/data/firstLegParties.ts index 3d6755f..46ded97 100644 --- a/src/data/firstLegParties.ts +++ b/src/data/firstLegParties.ts @@ -36,12 +36,3 @@ export function createFirstLegPartySnapshot(party: FirstLegParty, role: FirstLeg address: party.address, } } - -// API 接入前供客户端订单静态流程使用,不代表已确认的往来单位类型或数据范围契约。 -export const DEMO_FIRST_LEG_PARTIES: readonly FirstLegParty[] = [ - { id: 'CP001', code: 'CN-SHIP-001', name: '示例贸易有限公司', roles: ['SHIPPER'], contactName: '王经理', phone: '138****1201', email: 'shipping@example.com', countryRegion: '中国', province: '浙江省', city: '杭州市', postalCode: '310000', address: '滨江区演示产业园 A 座', status: 'ACTIVE' }, - { id: 'CP002', code: 'CN-SHIP-002', name: '华南演示供应链有限公司', roles: ['SHIPPER'], contactName: '陈主管', phone: '139****8820', email: 'ops@example.cn', countryRegion: '中国', province: '广东省', city: '深圳市', postalCode: '518000', address: '宝安区演示供应链园 2 号楼', status: 'ACTIVE' }, - { id: 'CP003', code: 'US-CONS-001', name: 'DEMO GLOBAL LLC', roles: ['CONSIGNEE'], contactName: 'Alex Morgan', phone: '+1 *** *** 2108', email: 'receiving@example.us', countryRegion: '美国', province: 'California', city: 'Los Angeles', postalCode: '90001', address: '100 Demo Logistics Ave', status: 'ACTIVE' }, - { id: 'CP004', code: 'DE-CONS-001', name: 'DEMO EUROPE GMBH', roles: ['CONSIGNEE'], contactName: 'Lena Fischer', phone: '+49 *** *** 4810', email: 'warehouse@example.de', countryRegion: '德国', province: 'Hamburg', city: 'Hamburg', postalCode: '20095', address: '8 Demo Hafenstrasse', status: 'ACTIVE' }, - { id: 'CP005', code: 'US-CONS-OLD', name: '停用演示收货单位', roles: ['CONSIGNEE'], contactName: 'Demo Contact', phone: '', email: '', countryRegion: '美国', province: '', city: '', postalCode: '', address: '', status: 'INACTIVE' }, -] diff --git a/src/data/firstLegPartySource.ts b/src/data/firstLegPartySource.ts index 6496e2f..f6d6182 100644 --- a/src/data/firstLegPartySource.ts +++ b/src/data/firstLegPartySource.ts @@ -41,6 +41,7 @@ export const firstLegPartySource: FirstLegPartySource = { return parties.find((party) => party.id === id && isAvailableForRole(party, role)) }, async load() { - parties.splice(0, parties.length, ...(await getMerchantPartnerCandidates()).map(mapPartner)) + parties.splice(0, parties.length) + parties.push(...(await getMerchantPartnerCandidates()).map(mapPartner)) }, } diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue index f9a33ea..11bac2a 100644 --- a/src/layouts/AppLayout.vue +++ b/src/layouts/AppLayout.vue @@ -8,7 +8,7 @@ import { Menu, X, } from '@lucide/vue' -import { PRODUCT_NAME } from '@chongchong/shared' +import baiyueLogoDark from '@/assets/brand/baiyue-logo-dark.png' import ClientSidebarMenu from '@/components/ClientSidebarMenu.vue' import { useAuthStore } from '@/stores/auth' @@ -106,8 +106,7 @@ async function handleLogout() { :aria-hidden="mobileMenuOpen ? 'true' : undefined" > - CC - {{ PRODUCT_NAME }}客户工作台 +
{{ auth.user?.tenantName }}
diff --git a/src/router/index.ts b/src/router/index.ts index ff0866a..f5e3d2e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -28,7 +28,7 @@ const router = createRouter({ }) router.beforeEach(async (to) => { - document.title = to.meta.title ? `${to.meta.title} - 虫虫出海客户工作台` : '虫虫出海客户工作台' + document.title = to.meta.title ? `${to.meta.title} - 柏悦物流客户工作台` : '柏悦物流客户工作台' const auth = useAuthStore() if (!auth.sessionRestored) { try { diff --git a/src/stores/firstLegOrders.ts b/src/stores/firstLegOrders.ts index 11b5a3a..08461ed 100644 --- a/src/stores/firstLegOrders.ts +++ b/src/stores/firstLegOrders.ts @@ -138,8 +138,8 @@ function timeline(detail: MerchantFirstLegOrderDetail): FirstLegOrderTimelineIte id: String(item.id), event: item.toStatus === 'SUBMITTED' || item.toStatus === 'REJECTED' ? 'SUBMITTED' : item.toStatus === 'CANCELLED' ? 'CANCELLED' : item.toStatus === 'COMPLETED' ? 'COMPLETED' : item.toStatus === 'ACCEPTED' || item.toStatus === 'APPROVED' ? 'ACCEPTED' : 'IN_TRANSIT', title: item.toStatus === 'REJECTED' ? '平台要求补充订单信息' : item.action || `状态更新为 ${item.toStatus}`, - description: item.reason || '订单状态已更新。', - operatorName: item.operatorName || '系统', + description: item.reason ?? '', + operatorName: item.operatorName ?? '', occurredAt: item.occurredAt, })) } @@ -155,7 +155,7 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar receivingWarehouseSnapshot: orderWarehouseSnapshot(order, warehouse), expectedDepartureDate: order.expectedShipDate ?? '', etd: '', eta: '', shipperPartyId: shipper?.id ?? '', shipperPartySnapshot: shipper, shipper: shipper?.name ?? '', consigneePartyId: consignee?.id ?? '', consigneePartySnapshot: consignee, consignee: consignee?.name ?? '', - expressCompanyName: order.expressCompany ?? '', trackingNumber: order.expressTrackingNo ?? '', priority: 'NORMAL', customerRemark: order.customerRemark ?? '', operatorName: '待平台分配', + expressCompanyName: order.expressCompany ?? '', trackingNumber: order.expressTrackingNo ?? '', priority: 'NORMAL', customerRemark: order.customerRemark ?? '', operatorName: '', status: clientStatus(order.status), version: order.version ?? 0, items: (detail?.cargoes ?? []).map((item) => ({ id: String(item.id), sku: item.skuNo ?? '', cargoNameCn: item.cargoName, cargoNameEn: item.cargoNameEn ?? '', @@ -174,9 +174,9 @@ function mapOrder(order: MerchantFirstLegOrder, warehouses: PlatformReceivingWar function mapWarehouse(item: MerchantWarehouse): PlatformReceivingWarehouse { return { - id: String(item.id), code: item.warehouseCode, name: item.warehouseName, province: item.countryCode, - city: item.countryCode, address: '详情地址待选择后加载', contactName: item.contactName ?? '', contactPhone: item.contactPhone ?? '', - businessHours: item.timezone ?? '以仓库通知为准', status: item.status, remark: item.remark ?? '', updatedAt: item.updatedAt ?? '', + id: String(item.id), code: item.warehouseCode, name: item.warehouseName, province: '', + city: '', address: '', contactName: item.contactName ?? '', contactPhone: item.contactPhone ?? '', + businessHours: '', status: item.status, remark: item.remark ?? '', updatedAt: item.updatedAt ?? '', } } diff --git a/src/stores/tenantAdministration.ts b/src/stores/tenantAdministration.ts index 753577b..fed76ec 100644 --- a/src/stores/tenantAdministration.ts +++ b/src/stores/tenantAdministration.ts @@ -27,17 +27,10 @@ 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 } @@ -160,56 +153,32 @@ export const FRONTEND_TO_MERCHANT_PERMISSION_CODES: Readonly { - 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 company = ref({ ...emptyCompany }) + const qualifications = ref([]) + const members = ref([]) + const roles = ref([]) const permissionResources = ref([]) 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 = { - ...company.value, companyName: profile.tenantName, creditCode: profile.unifiedSocialCreditCode ?? '', legalRepresentative: profile.legalName, @@ -223,8 +192,8 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', type: item.qualificationType === 'BUSINESS_LICENSE' ? '营业执照' : item.qualificationType, fileName: item.documentUrl.split('/').pop() || '营业执照', status: item.status === 'APPROVED' ? 'VERIFIED' : item.status, - expiresAt: '长期', - updatedAt: '以服务端记录为准', + expiresAt: '-', + updatedAt: '-', documentUrl: item.documentUrl, })) } @@ -264,15 +233,14 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', } async function loadRoles() { - rolesLoadedFromApi.value = true 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() { - membersLoadedFromApi.value = true members.value = [] members.value = (await getMerchantMembers()).map(mapMerchantMember) } @@ -286,7 +254,6 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', contactEmail: payload.contactEmail.trim(), }) company.value = { - ...company.value, companyName: updated.tenantName, creditCode: updated.unifiedSocialCreditCode ?? '', legalRepresentative: updated.legalName, @@ -297,22 +264,6 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', } } - 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(), @@ -417,14 +368,10 @@ export const useTenantAdministrationStore = defineStore('tenant-administration', members, roles, activeMemberCount, - rolesLoadedFromApi, - membersLoadedFromApi, loadCompanyContext, loadRoles, loadMembers, updateCompany, - setCompanyAuditStatusForPreview, - upsertQualification, createMember, updateMember, toggleMemberStatus, diff --git a/src/styles/index.css b/src/styles/index.css index e8cab7e..3093944 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -7,11 +7,8 @@ a { color: inherit; } .client-shell { min-height: 100vh; overflow-x: hidden; } .top-header { position: fixed; inset: 0 0 auto 0; z-index: 20; display: flex; width: 100%; max-width: 100vw; height: 68px; align-items: center; padding: 0 28px; border-bottom: 1px solid var(--border); background: #fff; } -.client-brand { display: flex; width: 232px; min-width: 0; align-items: center; gap: 11px; color: var(--navy); text-decoration: none; } -.client-brand-mark, .auth-brand span { display: grid; width: 38px; height: 38px; place-items: center; border-radius: 8px; background: var(--primary); color: #fff; font-size: 13px; font-weight: 800; } -.client-brand > span:last-child { display: flex; flex-direction: column; } -.client-brand strong { font-size: 15px; } -.client-brand small { margin-top: 1px; color: var(--muted); font-size: 10px; } +.client-brand { display: flex; width: 232px; min-width: 0; align-items: center; color: var(--navy); text-decoration: none; } +.client-brand-logo { display: block; width: 150px; height: 54px; object-fit: contain; object-position: left center; } .tenant-name { display: flex; flex: 1; align-items: center; gap: 8px; color: #475467; font-size: 13px; } .client-user { display: flex; align-items: center; gap: 10px; } .client-avatar { display: grid; width: 34px; height: 34px; place-items: center; border-radius: 50%; background: #e7f3f1; color: var(--primary); font-weight: 750; } @@ -57,7 +54,8 @@ a { color: inherit; } .auth-page { min-height: 100vh; background: #eef3f3; } .auth-header { display: flex; height: 72px; align-items: center; justify-content: space-between; padding: 0 clamp(24px, 6vw, 90px); border-bottom: 1px solid var(--border); background: #fff; color: var(--muted); font-size: 13px; } -.auth-brand { display: flex; align-items: center; gap: 10px; color: var(--navy); text-decoration: none; } +.auth-brand { display: flex; min-width: 0; align-items: center; color: var(--navy); text-decoration: none; } +.auth-brand-logo { display: block; width: 158px; height: 56px; object-fit: contain; object-position: left center; } .auth-header a { color: var(--primary); text-decoration: none; } .auth-main { display: grid; min-height: calc(100vh - 72px); grid-template-columns: 1fr 440px; align-items: center; gap: clamp(60px, 10vw, 150px); width: min(1180px, calc(100% - 48px)); margin: 0 auto; padding: 50px 0; } .auth-kicker, .section-label { color: var(--primary); font-size: 11px; font-weight: 750; letter-spacing: .13em; } @@ -74,8 +72,6 @@ a { color: inherit; } .auth-form-options button:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; } .auth-submit { width: 100%; } .auth-submit span { display: flex; align-items: center; gap: 7px; } -.demo-tip { display: flex; flex-direction: column; gap: 3px; margin-top: 18px; padding: 10px 12px; background: #f2f7f6; color: var(--muted); font-size: 12px; line-height: 1.55; } -.demo-tip strong { color: #40534f; } .register-page { min-height: 100vh; background: #f5f7f8; } .register-header { display: flex; height: 68px; align-items: center; justify-content: space-between; padding: 0 clamp(20px, 5vw, 72px); border-bottom: 1px solid var(--border); background: #fff; } @@ -139,12 +135,6 @@ a { color: inherit; } .profile-summary dt { color: var(--muted); } .profile-summary dd { margin: 0; font-weight: 600; } .module-stack { display: flex; flex-direction: column; gap: 14px; } -.module-actions { display: flex; justify-content: flex-end; gap: 10px; } -.module-actions .el-input { width: 280px; } -.module-empty { display: flex; min-height: 430px; align-items: center; justify-content: center; flex-direction: column; text-align: center; } -.module-empty-icon { display: grid; width: 56px; height: 56px; place-items: center; margin-bottom: 17px; background: #eaf2f1; color: var(--primary); } -.module-empty h2 { margin: 0 0 7px; font-size: 20px; } -.module-empty p { max-width: 540px; margin: 0 0 18px; color: var(--muted); } .el-button--primary { --el-button-bg-color: var(--primary); --el-button-border-color: var(--primary); --el-button-hover-bg-color: var(--primary-dark); --el-button-hover-border-color: var(--primary-dark); } @@ -163,6 +153,7 @@ a { color: inherit; } .client-sidebar-header button { display: grid; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: var(--navy); place-items: center; } .client-main { width: 100%; max-width: 100%; margin-left: 0; padding: 88px 14px 30px; } .client-brand { width: auto; } + .client-brand-logo { width: 132px; height: 50px; } .tenant-name, .client-user-copy { display: none; } .client-user { margin-left: auto; } .client-page-heading > button { display: grid; width: 44px; height: 44px; place-items: center; } diff --git a/src/utils/firstLegOrder.test.mjs b/src/utils/firstLegOrder.test.mjs index 04aa4b9..590d251 100644 --- a/src/utils/firstLegOrder.test.mjs +++ b/src/utils/firstLegOrder.test.mjs @@ -1,6 +1,6 @@ import assert from 'node:assert/strict' import test from 'node:test' -import { createFirstLegPartySnapshot, DEMO_FIRST_LEG_PARTIES } from '../data/firstLegParties.ts' +import { createFirstLegPartySnapshot } from '../data/firstLegParties.ts' import { canClientCancelFirstLegOrder, canClientEditFirstLegOrder, @@ -11,6 +11,17 @@ import { validateFirstLegOrderDraft, } from './firstLegOrder.ts' +const shipperParty = { + id: 'CP001', code: 'SHIPPER-001', name: '测试发货企业', roles: ['SHIPPER'], contactName: '测试联系人', + phone: '13800000000', email: 'shipper@example.test', countryRegion: '中国', province: '浙江省', city: '宁波市', + postalCode: '315000', address: '测试发货地址', status: 'ACTIVE', +} +const consigneeParty = { + id: 'CP002', code: 'CONSIGNEE-001', name: 'TEST CONSIGNEE LLC', roles: ['CONSIGNEE'], contactName: 'Test Receiver', + phone: '+1 555 0100', email: 'receiver@example.test', countryRegion: '美国', province: 'California', city: 'Los Angeles', + postalCode: '90001', address: '100 Test Logistics Ave', status: 'ACTIVE', +} + function validDraft() { return { referenceNo: 'REF-001', @@ -21,11 +32,11 @@ function validDraft() { receivingWarehouseId: 'WH005', expectedDepartureDate: '2026-08-08', shipperPartyId: 'CP001', - shipperPartySnapshot: createFirstLegPartySnapshot(DEMO_FIRST_LEG_PARTIES[0], 'SHIPPER'), - shipper: '示例贸易有限公司', + shipperPartySnapshot: createFirstLegPartySnapshot(shipperParty, 'SHIPPER'), + shipper: shipperParty.name, consigneePartyId: 'CP003', - consigneePartySnapshot: createFirstLegPartySnapshot(DEMO_FIRST_LEG_PARTIES[2], 'CONSIGNEE'), - consignee: 'DEMO GLOBAL LLC', + consigneePartySnapshot: createFirstLegPartySnapshot(consigneeParty, 'CONSIGNEE'), + consignee: consigneeParty.name, priority: 'NORMAL', customerRemark: '', tradeTerm: 'FOB', diff --git a/src/views/AuthViews.test.mjs b/src/views/AuthViews.test.mjs index 8516307..756bff2 100644 --- a/src/views/AuthViews.test.mjs +++ b/src/views/AuthViews.test.mjs @@ -13,9 +13,8 @@ const mainSource = readFileSync(new URL('../main.ts', import.meta.url), 'utf8') test('matches the backend username login contract', () => { assert.match(loginSource, /label="用户名"/) assert.match(loginSource, /\^\[A-Za-z0-9_\]/) - assert.match(loginSource, /const isDevelopment = import\.meta\.env\.DEV/) - assert.match(loginSource, /v-if="developmentHint" class="demo-tip"/) - assert.match(loginSource, /merchant_admin/) + assert.match(loginSource, /typeof route\.query\.account === 'string'/) + assert.doesNotMatch(loginSource, /developmentHint|demo-tip|merchant_admin|联调账号|本地后端/) assert.match(loginSource, /form\.remember/) assert.match(loginSource, /忘记密码/) }) diff --git a/src/views/CompanyProfileView.vue b/src/views/CompanyProfileView.vue index 4646bfc..00b0248 100644 --- a/src/views/CompanyProfileView.vue +++ b/src/views/CompanyProfileView.vue @@ -32,21 +32,25 @@ const qualificationFileError = ref('') const qualificationSubmitting = ref(false) const profileSubmitting = ref(false) const loading = ref(false) +const loadError = ref('') const selectedFile = ref(null) const draft = reactive({ ...store.company }) const qualificationDraft = reactive({ type: '营业执照', fileName: '', certificateNo: '' }) -onMounted(async () => { +async function loadCompanyContext() { loading.value = true + loadError.value = '' try { await store.loadCompanyContext() Object.assign(draft, store.company) } catch (error) { - ElMessage.error(error instanceof Error ? error.message : '企业资料加载失败') + loadError.value = error instanceof Error ? error.message : '企业资料加载失败' } finally { loading.value = false } -}) +} + +onMounted(() => void loadCompanyContext()) function startEdit() { Object.assign(draft, store.company) @@ -176,11 +180,16 @@ function qualificationStatus(status: string) {