This commit is contained in:
2026-08-03 18:15:41 +08:00
parent 370a3926d7
commit 1241d11c01
12 changed files with 637 additions and 44 deletions
+16 -13
View File
@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { Building2, CheckCircle2, Download, Eye, FileCheck2, FileText, 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 { createPlatformExportTask } from '@/api/platformExportTasks'
import {
changePlatformTenantStatus,
createPlatformTenant,
@@ -54,6 +55,7 @@ const access = useAccessPreviewStore()
const page = ref(1)
const pageSize = ref(10)
const loading = ref(false)
const exportSubmitting = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
const result = ref({ records: [] as PlatformTenantResource[], total: 0, pages: 0 })
const orderCounts = ref<Record<string, number>>({})
@@ -464,20 +466,21 @@ async function toggleStatus(row: PlatformTenantResource) {
} finally { rowActionId.value = '' }
}
function xmlEscape(value: unknown) { return String(value ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') }
async function exportCustomers() {
loading.value = true
exportSubmitting.value = true
try {
const first = await listPlatformTenants({ page: 1, size: 200, keyword: filters.value.keyword.trim() || undefined, status: filters.value.status || undefined })
const rows = [...first.records]
for (let current = 2; current <= first.pages; current += 1) rows.push(...(await listPlatformTenants({ page: current, size: 200, keyword: filters.value.keyword.trim() || undefined, status: filters.value.status || undefined })).records)
const body = rows.map((row) => `<tr>${[row.tenantCode, row.tenantName, row.unifiedSocialCreditCode, row.contactName, row.contactPhone, row.salesOwnerName, row.paymentTermDays, statusMeta(row.status).label, formatDate(row.createdAt)].map((cell) => `<td>${xmlEscape(cell)}</td>`).join('')}</tr>`).join('')
const html = `<html><head><meta charset="utf-8"></head><body><table><tr>${['客户ID','公司名称','统一社会信用代码','联系人','电话','销售人员','账期(天)','状态','入驻时间'].map((cell) => `<th>${cell}</th>`).join('')}</tr>${body}</table></body></html>`
const url = URL.createObjectURL(new Blob(['\ufeff', html], { type: 'application/vnd.ms-excel;charset=utf-8' }))
const anchor = document.createElement('a'); anchor.href = url; anchor.download = `客户列表-${new Date().toISOString().slice(0, 10)}.xls`; anchor.click(); URL.revokeObjectURL(url)
ElMessage.success(`已导出 ${rows.length} 条客户数据`)
} catch (error) { ElMessage.error(getApiErrorMessage(error, '客户数据导出失败')) }
finally { loading.value = false }
await createPlatformExportTask({
exportType: 'CUSTOMER_LIST',
format: 'XLSX',
taskName: '客户列表',
parameters: {
keyword: filters.value.keyword.trim() || undefined,
status: filters.value.status || undefined,
},
})
ElMessage.success('导出任务已创建,可前往下载中心查看')
} catch (error) { ElMessage.error(getApiErrorMessage(error, '客户导出任务创建失败')) }
finally { exportSubmitting.value = false }
}
watch([page, pageSize], () => { void loadCustomers() })
@@ -493,7 +496,7 @@ onMounted(loadCustomers)
<QueryToolbar v-model="filters" class="surface-panel" ariaLabel="客户筛选" :fields="queryFields" @search="applyFilters" />
<section v-if="loadError" class="surface-panel customers-error" role="alert"><div><strong>客户列表暂时无法加载</strong><p>{{ loadError.message }}</p><ApiErrorMeta :details="loadError" /></div><el-button type="primary" :loading="loading" @click="loadCustomers">重新加载</el-button></section>
<section v-else class="surface-panel table-panel" :aria-busy="loading">
<div class="customers-table-header"><div class="customers-table-heading"><h2>客户列表</h2><span>共 {{ result.total }} 个客户</span></div><div class="customers-table-actions"><el-button :disabled="loading" @click="exportCustomers"><Download :size="16" />导出Excel</el-button><el-button v-if="access.can('platform:customer:create')" type="primary" :disabled="loading" @click="openCreate"><Plus :size="16" />新增客户</el-button></div></div>
<div class="customers-table-header"><div class="customers-table-heading"><h2>客户列表</h2><span>共 {{ result.total }} 个客户</span></div><div class="customers-table-actions"><el-button v-if="access.can('platform:customer:export')" :loading="exportSubmitting" :disabled="loading" @click="exportCustomers"><Download :size="16" />导出Excel</el-button><el-button v-if="access.can('platform:customer:create')" type="primary" :disabled="loading" @click="openCreate"><Plus :size="16" />新增客户</el-button></div></div>
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="customerColumns" :loading="loading" :total="result.total" aria-label="客户列表" row-key="id" empty-text="暂无符合筛选条件的客户">
<template #actions="{ row }"><div class="table-actions"><el-button v-if="access.can('platform:customer:view')" link type="primary" @click="showDetail(row as PlatformTenantResource)"><Eye :size="15" />详情</el-button><el-button v-if="access.can('platform:customer:view') && access.can('platform:customer:edit')" link type="primary" @click="openEdit(row as PlatformTenantResource)"><Pencil :size="15" />编辑</el-button><el-button v-if="access.can('platform:customer:status') && ['ACTIVE','DISABLED'].includes((row as PlatformTenantResource).status)" link :type="(row as PlatformTenantResource).status === 'ACTIVE' ? 'danger' : 'success'" :loading="rowActionId === String((row as PlatformTenantResource).id)" @click="toggleStatus(row as PlatformTenantResource)"><Power :size="15" />{{ (row as PlatformTenantResource).status === 'ACTIVE' ? '停用' : '启用' }}</el-button></div></template>
<template #company="{ row }"><div class="customer-company-cell"><span><Building2 :size="18" /></span><strong>{{ (row as PlatformTenantResource).tenantName }}</strong></div></template>