优化
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import ElementPlus from 'element-plus'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
@@ -26,6 +28,8 @@ import QueryToolbar from '@/shared/components/QueryToolbar.vue'
|
||||
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
|
||||
import CustomersView from './CustomersView.vue'
|
||||
|
||||
const customersSource = readFileSync(resolve(process.cwd(), 'src/modules/customers/pages/CustomersView.vue'), 'utf8')
|
||||
|
||||
const { routerPush, routerReplace, routeControl } = vi.hoisted(() => ({
|
||||
routerPush: vi.fn(),
|
||||
routerReplace: vi.fn(),
|
||||
@@ -152,6 +156,15 @@ describe('CustomersView', () => {
|
||||
vi.mocked(showExportTaskCreatedDialog).mockResolvedValue(false)
|
||||
})
|
||||
|
||||
it('matches the frozen prototype containers for customer operations', () => {
|
||||
expect(customersSource).toContain("router.push({ name: 'customer-detail'")
|
||||
expect(customersSource).toContain("'min(640px, calc(100vw - 28px))'")
|
||||
expect(customersSource).toContain("'min(680px, calc(100vw - 28px))'")
|
||||
expect(customersSource).toContain('width="min(520px, calc(100vw - 28px))" class="customer-password-dialog"')
|
||||
expect(customersSource).toContain('width="min(900px, calc(100vw - 28px))" class="review-decision-dialog"')
|
||||
expect(customersSource).not.toContain('<AppDrawer v-model="editorOpen"')
|
||||
})
|
||||
|
||||
it('loads the complete customer detail before editing masked list fields', async () => {
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
@@ -368,7 +381,7 @@ describe('CustomersView', () => {
|
||||
tableButtonByText('审核')?.click()
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.trim() === '确认通过')?.click()
|
||||
.find((button) => button.textContent?.trim() === '审核通过')?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(approveEnterpriseReview).toHaveBeenCalledWith(pendingReview.id, '企业资料与必填资质完整、清晰,审核通过')
|
||||
@@ -398,7 +411,7 @@ describe('CustomersView', () => {
|
||||
tableButtonByText('审核')?.click()
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.trim() === '确认通过')?.click()
|
||||
.find((button) => button.textContent?.trim() === '审核通过')?.click()
|
||||
await flushPromises()
|
||||
|
||||
expect(approveEnterpriseReview).toHaveBeenCalledWith(pendingReview.id, '企业资料与必填资质完整、清晰,审核通过')
|
||||
|
||||
@@ -39,7 +39,6 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import AppDrawer from '@/shared/components/AppDrawer.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
@@ -204,6 +203,9 @@ let loadSequence = 0
|
||||
let reviewLoadSequence = 0
|
||||
let editorLoadSequence = 0
|
||||
const editorTitle = computed(() => editingId.value ? '编辑客户' : '新增客户')
|
||||
const editorDialogWidth = computed(() => editingId.value
|
||||
? 'min(640px, calc(100vw - 28px))'
|
||||
: 'min(680px, calc(100vw - 28px))')
|
||||
const canUploadQualifications = computed(() => !editorStatus.value || ['PROFILE_REQUIRED', 'REJECTED'].includes(editorStatus.value))
|
||||
const canSubmitReview = computed(() => Boolean(editingId.value) && ['PROFILE_REQUIRED', 'REJECTED'].includes(editorStatus.value || ''))
|
||||
const uploadingQualifications = computed(() => qualifications.value.some((item) => item.uploading))
|
||||
@@ -963,7 +965,7 @@ onMounted(() => { void loadCustomers() })
|
||||
</section>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDrawer v-model="editorOpen" :title="editorTitle" size="760px" class="business-drawer customer-editor">
|
||||
<AppDialog v-model="editorOpen" :title="editorTitle" :width="editorDialogWidth" class="customer-editor-dialog" :loading="editorLoading" :confirm-loading="saving" destroy-on-close>
|
||||
<el-form ref="formRef" v-loading="editorLoading" :model="form" :rules="rules" label-position="top" class="drawer-form" :disabled="editorLoading || saving || submitting || uploadingQualifications">
|
||||
<FormSection title="基础资料" description="维护工商登记和企业主体信息。"><div class="form-grid two-cols">
|
||||
<el-form-item label="公司名称" prop="tenantName" required><el-input v-model="form.tenantName" placeholder="请输入工商登记公司名称" maxlength="128" /></el-form-item>
|
||||
@@ -1006,7 +1008,7 @@ onMounted(() => { void loadCustomers() })
|
||||
</FormSection>
|
||||
</el-form>
|
||||
<template #footer><div class="customer-editor-footer"><span>保存资料时会一并上传已选资质;进入审核仍需点击“提交审核”并再次确认</span><div><el-button :disabled="editorLoading || saving || submitting || uploadingQualifications" @click="editorOpen = false">关闭</el-button><el-button :loading="saving" :disabled="editorLoading || submitting || uploadingQualifications" @click="saveCustomer"><Save :size="16" />保存资料和资质</el-button><el-button v-if="canSubmitReview" type="primary" :loading="submitting" :disabled="editorLoading || saving || uploadingQualifications" @click="submitReview">提交审核</el-button></div></div></template>
|
||||
</AppDrawer>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog v-model="qualificationPreviewOpen" :title="qualificationPreviewName || '查看资质文件'" width="min(860px, calc(100vw - 28px))" class="qualification-preview-dialog" destroy-on-close :show-confirm="false" cancel-text="关闭预览">
|
||||
<div class="qualification-preview">
|
||||
@@ -1026,16 +1028,35 @@ onMounted(() => { void loadCustomers() })
|
||||
<template #footer><el-button type="primary" @click="closeApprovalResult">我已保存,关闭</el-button></template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="decisionOpen" :title="decision.type === 'approve' ? '确认审核通过' : '确认驳回企业资料'" width="min(520px, calc(100vw - 28px))" class="review-decision-dialog" :close-on-click-modal="false">
|
||||
<div v-if="selectedReview" class="decision-summary"><strong>{{ selectedReview.tenantName || '当前客户' }}</strong><span>客户资料审核</span></div>
|
||||
<el-dialog v-model="decisionOpen" title="客户入驻审核" width="min(900px, calc(100vw - 28px))" class="review-decision-dialog" :close-on-click-modal="false">
|
||||
<div v-if="selectedReview" class="review-dialog-grid">
|
||||
<section class="review-dialog-section">
|
||||
<h3>企业基本信息</h3>
|
||||
<dl class="review-info-grid">
|
||||
<div><dt>客户名称</dt><dd>{{ selectedReview.tenantName || '—' }}</dd></div>
|
||||
<div><dt>客户编号</dt><dd>{{ selectedReview.tenantCode || '—' }}</dd></div>
|
||||
<div><dt>联系人</dt><dd>{{ selectedReview.contactName || '—' }}</dd></div>
|
||||
<div><dt>联系电话</dt><dd>{{ selectedReview.contactPhone || '—' }}</dd></div>
|
||||
<div><dt>联系邮箱</dt><dd>{{ selectedReview.contactEmail || '—' }}</dd></div>
|
||||
<div><dt>提交时间</dt><dd>{{ formatDate(selectedReview.submittedAt || selectedReview.createdAt) }}</dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
<section class="review-dialog-section">
|
||||
<h3>资质文件</h3>
|
||||
<div class="review-document-table" role="table" aria-label="待审核客户资质">
|
||||
<div class="review-document-header" role="row"><span role="columnheader">文件类型</span><span role="columnheader">证书编号</span><span role="columnheader">状态</span></div>
|
||||
<div class="review-document-row" role="row"><span role="cell">{{ selectedReview.qualificationType || '企业资质' }}</span><span role="cell">{{ selectedReview.certificateNo || '—' }}</span><span role="cell">待审核</span></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<el-radio-group v-if="access.can('platform:customer:approve') && access.can('platform:customer:reject')" v-model="decision.type" class="decision-type" aria-label="审核决定"><el-radio-button label="approve">通过</el-radio-button><el-radio-button label="reject">驳回</el-radio-button></el-radio-group>
|
||||
<el-form ref="decisionFormRef" :model="decision" :rules="decisionRules" label-position="top">
|
||||
<el-form-item prop="comment" :label="decision.type === 'approve' ? '审核说明' : '驳回原因'" required><el-input v-model="decision.comment" type="textarea" :rows="4" maxlength="300" show-word-limit :placeholder="decision.type === 'approve' ? '请填写审核通过说明' : '请明确说明需要企业修改的内容'" /></el-form-item>
|
||||
<el-form-item prop="comment" :label="decision.type === 'approve' ? '审核意见' : '驳回原因'" required><el-input v-model="decision.comment" type="textarea" :rows="3" maxlength="300" show-word-limit :placeholder="decision.type === 'approve' ? '可填写审核意见' : '请明确说明需要企业修改的内容'" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><div class="drawer-footer"><el-button :disabled="reviewSubmitting" @click="decisionOpen = false">取消</el-button><el-button :type="decision.type === 'approve' ? 'success' : 'danger'" :loading="reviewSubmitting" @click="submitDecision">{{ decision.type === 'approve' ? '确认通过' : '确认驳回' }}</el-button></div></template>
|
||||
<template #footer><div class="drawer-footer"><el-button :disabled="reviewSubmitting" @click="decisionOpen = false">返回</el-button><el-button v-if="access.can('platform:customer:reject') && decision.type === 'reject'" type="danger" :loading="reviewSubmitting" @click="submitDecision">确认驳回</el-button><el-button v-if="access.can('platform:customer:approve') && decision.type === 'approve'" type="primary" :loading="reviewSubmitting" @click="submitDecision">审核通过</el-button></div></template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="passwordDialogOpen" title="重置客户密码" width="min(560px, calc(100vw - 28px))" :close-on-click-modal="false" :close-on-press-escape="!passwordSubmitting" :show-close="!passwordSubmitting">
|
||||
<el-dialog v-model="passwordDialogOpen" title="重置客户密码" width="min(520px, calc(100vw - 28px))" class="customer-password-dialog" :close-on-click-modal="false" :close-on-press-escape="!passwordSubmitting" :show-close="!passwordSubmitting">
|
||||
<div v-if="passwordTenant" class="decision-summary"><div><strong>正在重置企业管理员登录密码</strong><span>{{ passwordTenant.tenantName }}</span></div><span>客户编号:{{ passwordTenant.tenantCode }}</span></div>
|
||||
<el-alert v-if="passwordError" type="error" :closable="false" show-icon role="alert"><template #title><div class="api-error-alert-title"><span>{{ passwordError.message }}</span><ApiErrorMeta :details="passwordError" /></div></template></el-alert>
|
||||
<el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-position="top">
|
||||
@@ -1115,12 +1136,29 @@ onMounted(() => { void loadCustomers() })
|
||||
.qualification-preview img { display:block; max-width:100%; height:auto; }
|
||||
.qualification-preview iframe { width:100%; min-height:min(68vh,720px); border:0; background:#fff; }
|
||||
.qualification-load-error { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:12px; padding:10px 12px; border:1px solid #fecdca; border-radius:6px; background:#fffafa; color:#b42318; font-size:12px; }
|
||||
.customer-editor :deep(.el-drawer__body) { min-height:0; overflow-y:auto; overscroll-behavior:contain; }
|
||||
.customer-editor :deep(.el-drawer__footer) { flex:0 0 auto; }
|
||||
:global(.customer-editor-dialog.el-dialog) { display:flex; max-height:min(90vh,900px); flex-direction:column; }
|
||||
:global(.customer-editor-dialog .el-dialog__body) { min-height:0; overflow-y:auto; overscroll-behavior:contain; }
|
||||
:global(.customer-editor-dialog .el-dialog__footer) { flex:0 0 auto; }
|
||||
.customer-editor-footer { display:flex; align-items:center; justify-content:space-between; gap:16px; } .customer-editor-footer > span { color:var(--cc-muted); font-size:12px; text-align:left; } .customer-editor-footer > div { display:flex; flex:0 0 auto; gap:8px; } .customer-editor-footer :deep(.el-button + .el-button) { margin-left:0; }
|
||||
.decision-summary { display:flex; min-width:0; align-items:center; justify-content:space-between; gap:12px; margin-bottom:18px; padding:12px 14px; border-radius:8px; background:#f8fafc; } .decision-summary > * { min-width:0; overflow-wrap:anywhere; } .decision-summary strong { color:var(--cc-text); font-size:13px; } .decision-summary span { color:var(--cc-muted); font-size:11px; }
|
||||
:global(.review-decision-dialog.el-dialog) { display:flex; max-height:min(90vh,820px); flex-direction:column; }
|
||||
:global(.review-decision-dialog .el-dialog__body) { min-height:0; overflow-y:auto; padding:20px 28px; }
|
||||
:global(.review-decision-dialog .el-dialog__footer) { flex:0 0 auto; }
|
||||
.review-dialog-grid { display:grid; grid-template-columns:1fr 1fr; gap:24px; margin-bottom:18px; }
|
||||
.review-dialog-section { min-width:0; }
|
||||
.review-dialog-section h3 { margin:0 0 12px; padding-bottom:8px; border-bottom:2px solid #667eea; color:#1a1a2e; font-size:14px; font-weight:600; }
|
||||
.review-info-grid { display:grid; gap:7px; margin:0; }
|
||||
.review-info-grid > div { display:grid; grid-template-columns:110px minmax(0,1fr); gap:12px; }
|
||||
.review-info-grid dt { color:#64748b; font-size:12px; }
|
||||
.review-info-grid dd { min-width:0; margin:0; overflow-wrap:anywhere; color:#1e293b; font-size:13px; }
|
||||
.review-document-table { overflow:hidden; border:1px solid #e4e4e7; border-radius:6px; }
|
||||
.review-document-header,.review-document-row { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) 76px; }
|
||||
.review-document-header { background:rgba(102,126,234,.08); color:#52525b; font-size:12px; font-weight:600; }
|
||||
.review-document-header > span,.review-document-row > span { min-width:0; padding:9px 10px; overflow-wrap:anywhere; border-right:1px solid #e4e4e7; }
|
||||
.review-document-header > span:last-child,.review-document-row > span:last-child { border-right:0; }
|
||||
.review-document-row { border-top:1px solid #e4e4e7; color:#334155; font-size:13px; }
|
||||
.decision-summary > div > span { display:block; margin-top:4px; }.decision-type { margin:0 0 16px; }.password-input-row { display:grid; grid-template-columns:minmax(0,1fr) auto; width:100%; gap:8px; }.dialog-hint { margin:12px 0 0; color:var(--cc-muted); font-size:12px; line-height:1.6; }.archive-confirm { display:grid; gap:8px; margin:16px 0; }.archive-confirm strong { color:var(--cc-text); }.archive-confirm span { color:var(--cc-muted); font-size:12px; }
|
||||
.approval-result-warning { display:flex; align-items:flex-start; gap:10px; padding:12px 14px; border:1px solid #fecdca; border-radius:8px; background:#fffafa; color:#b42318; } .approval-result-warning strong,.approval-result-warning span { display:block; } .approval-result-warning strong { font-size:14px; } .approval-result-warning span { margin-top:4px; font-size:12px; line-height:1.5; } .approval-credentials { margin:18px 0 0; border:1px solid var(--cc-border); border-radius:8px; } .approval-credentials > div { display:grid; grid-template-columns:90px minmax(0,1fr); gap:12px; padding:12px 14px; } .approval-credentials > div + div { border-top:1px solid var(--cc-border); } .approval-credentials dt { color:var(--cc-muted); font-size:12px; } .approval-credentials dd { display:flex; min-width:0; align-items:center; gap:10px; margin:0; } .approval-credentials code { min-width:0; flex:1; overflow-wrap:anywhere; color:var(--cc-text); font-size:14px; font-weight:700; }
|
||||
@media (max-width:720px) { .customer-editor-footer,.review-sync-error { align-items:stretch; flex-direction:column; } .customers-table-actions { display:grid; grid-template-columns:1fr 1fr; width:100%; } .customers-table-actions :deep(.el-button) { min-height:40px; margin:0; } .qualification-table { overflow-x:auto; } .qualification-row { min-width:640px; } .customer-editor-footer > div { display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr)); width:100%; } .customer-editor-footer :deep(.el-button) { min-height:40px; margin:0; } .decision-summary { align-items:flex-start; flex-direction:column; }.password-input-row { grid-template-columns:1fr; }.table-actions :deep(.el-button) { min-height:40px; } }
|
||||
@media (max-width:720px) { .customer-editor-footer,.review-sync-error { align-items:stretch; flex-direction:column; } .customers-table-actions { display:grid; grid-template-columns:1fr 1fr; width:100%; } .customers-table-actions :deep(.el-button) { min-height:40px; margin:0; } .qualification-table { overflow-x:auto; } .qualification-row { min-width:640px; } .customer-editor-footer > div { display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr)); width:100%; } .customer-editor-footer :deep(.el-button) { min-height:40px; margin:0; } .decision-summary { align-items:flex-start; flex-direction:column; }.review-dialog-grid { grid-template-columns:1fr; gap:18px; }.review-info-grid > div { grid-template-columns:92px minmax(0,1fr); }.password-input-row { grid-template-columns:1fr; }.table-actions :deep(.el-button) { min-height:40px; } }
|
||||
@media (max-width:360px) { .customers-table-actions,.capital-input { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user