feat: update admin navigation and customer management
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { Building2, CheckCircle2, Download, Eye, FileUp, Pencil, Plus, Power } from '@lucide/vue'
|
||||
import { Building2, CheckCircle2, Download, Eye, FileCheck2, FileUp, Pencil, Plus, Power } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/api/http'
|
||||
import {
|
||||
@@ -366,7 +366,45 @@ onMounted(loadCustomers)
|
||||
<template #footer><div class="drawer-footer customer-editor-footer"><span>{{ editingId ? '保存资料和上传资质均不会送审,提交审核需要再次确认' : '请先保存资料生成客户草稿,再上传资质' }}</span><div><el-button :disabled="editorLoading || saving || submitting || uploadingQualifications" @click="editorOpen = false">关闭</el-button><el-button :loading="saving" :disabled="editorLoading || submitting || uploadingQualifications" @click="saveCustomer">保存资料</el-button><el-button v-if="canUploadQualifications" :loading="uploadingQualifications" :disabled="editorLoading || !editingId || saving || submitting || !hasSelectedQualifications" @click="uploadQualifications">上传已选资质</el-button><el-button v-if="canSubmitReview" type="primary" :loading="submitting" :disabled="editorLoading || saving || uploadingQualifications" @click="submitReview">提交审核</el-button></div></div></template>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="detailOpen" title="客户详情" size="620px" class="business-drawer"><div v-loading="detailLoading" class="customer-detail"><template v-if="detail"><div class="detail-title"><span><Building2 :size="22" /></span><div><h3>{{ detail.tenantName }}</h3><p>{{ detail.tenantCode }}</p></div></div><el-descriptions :column="1" border><el-descriptions-item label="统一社会信用代码">{{ displayText(detail.unifiedSocialCreditCode) }}</el-descriptions-item><el-descriptions-item label="联系人">{{ displayText(detail.contactName) }} · {{ displayText(detail.contactPhone) }}</el-descriptions-item><el-descriptions-item label="联系邮箱">{{ displayText(detail.contactEmail) }}</el-descriptions-item><el-descriptions-item label="客户套餐">{{ displayText(detail.servicePackage) }}</el-descriptions-item><el-descriptions-item label="销售人员">{{ displayText(detail.salesOwnerName) }}</el-descriptions-item><el-descriptions-item label="账期">{{ detail.paymentTermDays ?? 0 }} 天</el-descriptions-item><el-descriptions-item label="注册地址">{{ displayText(detail.registeredAddress) }}</el-descriptions-item><el-descriptions-item label="入驻时间">{{ formatDate(detail.createdAt) }}</el-descriptions-item><el-descriptions-item label="内部备注">{{ displayText(detail.internalRemark) }}</el-descriptions-item></el-descriptions><div class="detail-qualification-list"><h4>资质文件</h4><div v-if="detailQualifications.length"><div v-for="item in detailQualifications" :key="String(item.id)"><span>{{ qualifications.find((draft) => draft.type === item.qualificationType)?.label || item.qualificationType }}</span><el-tag :type="item.status === 'APPROVED' ? 'success' : item.status === 'REJECTED' ? 'danger' : 'warning'" size="small">{{ item.status === 'APPROVED' ? '已通过' : item.status === 'REJECTED' ? '已驳回' : '待审核' }}</el-tag></div></div><p v-else>暂未上传资质文件</p></div></template></div></el-drawer>
|
||||
<el-drawer v-model="detailOpen" title="客户详情" size="620px" class="business-drawer">
|
||||
<div v-loading="detailLoading" class="customer-detail">
|
||||
<template v-if="detail">
|
||||
<div class="detail-title">
|
||||
<span><Building2 :size="22" /></span>
|
||||
<div><h3>{{ detail.tenantName }}</h3><p>{{ detail.tenantCode }}</p></div>
|
||||
<el-tag :type="statusMeta(detail.status).type" effect="light">{{ statusMeta(detail.status).label }}</el-tag>
|
||||
</div>
|
||||
|
||||
<section class="customer-detail-section" aria-labelledby="customer-profile-heading">
|
||||
<h4 id="customer-profile-heading">企业资料</h4>
|
||||
<dl class="customer-detail-grid">
|
||||
<div class="customer-detail-item is-wide"><dt>统一社会信用代码</dt><dd>{{ displayText(detail.unifiedSocialCreditCode) }}</dd></div>
|
||||
<div class="customer-detail-item"><dt>联系人</dt><dd>{{ displayText(detail.contactName) }}</dd></div>
|
||||
<div class="customer-detail-item"><dt>联系电话</dt><dd>{{ displayText(detail.contactPhone) }}</dd></div>
|
||||
<div class="customer-detail-item is-wide"><dt>联系邮箱</dt><dd>{{ displayText(detail.contactEmail) }}</dd></div>
|
||||
<div class="customer-detail-item"><dt>客户套餐</dt><dd>{{ displayText(detail.servicePackage) }}</dd></div>
|
||||
<div class="customer-detail-item"><dt>账期</dt><dd>{{ detail.paymentTermDays ?? 0 }} 天</dd></div>
|
||||
<div class="customer-detail-item"><dt>销售人员</dt><dd>{{ displayText(detail.salesOwnerName) }}</dd></div>
|
||||
<div class="customer-detail-item"><dt>入驻时间</dt><dd>{{ formatDate(detail.createdAt) }}</dd></div>
|
||||
<div class="customer-detail-item is-wide"><dt>注册地址</dt><dd>{{ displayText(detail.registeredAddress) }}</dd></div>
|
||||
<div class="customer-detail-item is-wide"><dt>内部备注</dt><dd>{{ displayText(detail.internalRemark) }}</dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="customer-detail-section detail-qualification-list" aria-labelledby="customer-qualification-heading">
|
||||
<div class="detail-section-heading"><h4 id="customer-qualification-heading">资质文件</h4><span>共 {{ detailQualifications.length }} 份</span></div>
|
||||
<div v-if="detailQualifications.length" class="detail-qualification-items">
|
||||
<article v-for="item in detailQualifications" :key="String(item.id)" class="detail-qualification-item">
|
||||
<span class="detail-qualification-icon"><FileCheck2 :size="18" /></span>
|
||||
<div><strong>{{ qualifications.find((draft) => draft.type === item.qualificationType)?.label || item.qualificationType }}</strong><small>{{ item.validTo ? `有效期至 ${item.validTo}` : '长期有效或未设置有效期' }}</small></div>
|
||||
<el-tag :type="item.status === 'APPROVED' ? 'success' : item.status === 'REJECTED' ? 'danger' : 'warning'" size="small" effect="light">{{ item.status === 'APPROVED' ? '已通过' : item.status === 'REJECTED' ? '已驳回' : '待审核' }}</el-tag>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else class="detail-empty-qualification"><FileCheck2 :size="20" /><span>暂未上传资质文件</span></div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -381,11 +419,35 @@ onMounted(loadCustomers)
|
||||
.qualification-heading { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; } .qualification-heading h3 { margin-bottom:4px; } .qualification-heading p,.qualification-hint { margin:0; color:var(--cc-muted); font-size:12px; line-height:1.6; } .qualification-hint { margin-top:12px; } .qualification-hint span,.qualification-label > span { color:var(--el-color-danger); }
|
||||
.qualification-table { overflow:hidden; margin-top:14px; border:1px solid var(--cc-border); border-radius:8px; } .qualification-row { display:grid; grid-template-columns:180px minmax(260px,1fr) 180px; min-width:0; border-top:1px solid var(--cc-border); } .qualification-row:first-child { border-top:0; } .qualification-row > * { display:flex; min-width:0; min-height:68px; align-items:center; padding:12px 14px; } .qualification-header { min-height:44px; background:#f7f8fc; color:var(--cc-muted); font-size:12px; font-weight:600; } .qualification-header > * { min-height:44px; }
|
||||
.qualification-label { gap:5px; font-weight:600; } .qualification-label svg { margin-left:auto; color:var(--el-color-success); }
|
||||
.qualification-file { flex-wrap:wrap; gap:8px; } .file-picker { display:inline-flex; min-height:36px; align-items:center; gap:7px; padding:0 12px; border:1px solid var(--cc-border); border-radius:6px; background:#fff; color:var(--cc-text); cursor:pointer; } .file-picker:focus-within { outline:2px solid color-mix(in srgb,var(--cc-primary) 35%,transparent); outline-offset:2px; } .file-picker.is-disabled { cursor:not-allowed; opacity:.6; } .file-picker input { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; } .qualification-file-name { min-width:0; overflow:hidden; color:var(--cc-muted); font-size:12px; text-overflow:ellipsis; white-space:nowrap; } .qualification-error { width:100%; color:var(--el-color-danger); line-height:1.4; }
|
||||
.qualification-file { flex-wrap:wrap; gap:8px; } .file-picker { position:relative; display:inline-flex; min-height:36px; align-items:center; gap:7px; overflow:hidden; padding:0 12px; border:1px solid var(--cc-border); border-radius:6px; background:#fff; color:var(--cc-text); cursor:pointer; } .file-picker:focus-within { outline:2px solid color-mix(in srgb,var(--cc-primary) 35%,transparent); outline-offset:2px; } .file-picker.is-disabled { cursor:not-allowed; opacity:.6; } .file-picker input { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:inherit; } .qualification-file-name { min-width:0; overflow:hidden; color:var(--cc-muted); font-size:12px; text-overflow:ellipsis; white-space:nowrap; } .qualification-error { width:100%; color:var(--el-color-danger); line-height:1.4; }
|
||||
.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; }
|
||||
.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; }
|
||||
.customer-detail { min-height:200px; } .detail-title { display:flex; align-items:center; gap:12px; margin-bottom:20px; } .detail-title h3 { margin:0; font-size:20px; } .detail-title p { margin:4px 0 0; color:var(--cc-muted); font-size:12px; }
|
||||
.detail-qualification-list { margin-top:20px; } .detail-qualification-list h4 { margin:0 0 10px; } .detail-qualification-list > div > div { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 0; border-bottom:1px solid var(--cc-border); } .detail-qualification-list p { color:var(--cc-muted); }
|
||||
@media (max-width:720px) { .customers-error,.customers-table-header,.customer-editor-footer { align-items:stretch; flex-direction:column; } .customers-table-actions { display:grid; grid-template-columns:1fr 1fr; } .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; } }
|
||||
.customer-detail { min-height:200px; padding:20px 24px 24px; }
|
||||
.detail-title { display:grid; grid-template-columns:44px minmax(0,1fr) auto; align-items:center; gap:12px; margin-bottom:24px; padding:4px 0 20px; border-bottom:1px solid var(--cc-border); }
|
||||
.detail-title > span { width:44px; height:44px; border-radius:10px; }
|
||||
.detail-title h3 { margin:0; overflow:hidden; color:var(--cc-text); font-size:20px; line-height:1.35; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.detail-title p { margin:4px 0 0; color:var(--cc-muted); font-size:12px; letter-spacing:.02em; }
|
||||
.customer-detail-section + .customer-detail-section { margin-top:24px; }
|
||||
.customer-detail-section h4 { margin:0 0 12px; color:var(--cc-text); font-size:14px; }
|
||||
.customer-detail-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); overflow:hidden; margin:0; border:1px solid var(--cc-border); border-radius:10px; background:#fff; }
|
||||
.customer-detail-item { min-width:0; padding:13px 16px; border-bottom:1px solid var(--cc-border); }
|
||||
.customer-detail-item:nth-child(odd):not(.is-wide) { border-right:1px solid var(--cc-border); }
|
||||
.customer-detail-item.is-wide { grid-column:1 / -1; }
|
||||
.customer-detail-item:last-child { border-bottom:0; }
|
||||
.customer-detail-item dt { margin-bottom:5px; color:var(--cc-muted); font-size:12px; line-height:1.4; }
|
||||
.customer-detail-item dd { margin:0; color:var(--cc-text); font-size:14px; font-weight:600; line-height:1.55; overflow-wrap:anywhere; }
|
||||
.detail-section-heading { display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
|
||||
.detail-section-heading h4 { margin-bottom:12px; }
|
||||
.detail-section-heading span { color:var(--cc-muted); font-size:12px; }
|
||||
.detail-qualification-items { display:flex; flex-direction:column; gap:8px; }
|
||||
.detail-qualification-item { display:grid; grid-template-columns:36px minmax(0,1fr) auto; align-items:center; gap:12px; padding:12px 14px; border:1px solid var(--cc-border); border-radius:8px; background:#fff; }
|
||||
.detail-qualification-icon { display:grid; width:36px; height:36px; place-items:center; border-radius:8px; background:#eef4ff; color:var(--cc-primary); }
|
||||
.detail-qualification-item strong,.detail-qualification-item small { display:block; }
|
||||
.detail-qualification-item strong { color:var(--cc-text); font-size:13px; }
|
||||
.detail-qualification-item small { margin-top:4px; color:var(--cc-muted); font-size:11px; line-height:1.4; }
|
||||
.detail-empty-qualification { display:flex; min-height:88px; align-items:center; justify-content:center; gap:8px; border:1px dashed var(--cc-border); border-radius:8px; background:#fafbfc; color:var(--cc-muted); font-size:12px; }
|
||||
@media (max-width:720px) { .customers-error,.customers-table-header,.customer-editor-footer { align-items:stretch; flex-direction:column; } .customers-table-actions { display:grid; grid-template-columns:1fr 1fr; } .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; } .customer-detail { padding:16px; } .customer-detail-grid { grid-template-columns:1fr; } .customer-detail-item,.customer-detail-item:nth-child(odd):not(.is-wide) { grid-column:1; border-right:0; } .customer-detail-item:last-child { border-bottom:0; } }
|
||||
@media (max-width:360px) { .customers-table-actions,.capital-input { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user