Files
admin-web/src/views/CustomersView.vue
T

166 lines
6.1 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { UsersRound } from '@lucide/vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/api/http'
import {
listPlatformTenants,
type PlatformTenantResource,
type PlatformTenantStatus,
} from '@/api/platformIam'
import ApiErrorMeta from '@/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
const page = ref(1)
const pageSize = ref(10)
const loading = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
const result = ref({ records: [] as PlatformTenantResource[], total: 0, pages: 0 })
type CustomerFilters = QueryModel & {
keyword: string
status: PlatformTenantStatus | ''
}
const filters = ref<CustomerFilters>({ keyword: '', status: '' })
const queryFields: QueryField[] = [
{ key: 'keyword', type: 'input', placeholder: '企业名称、企业编号、联系人或信用代码' },
{
key: 'status',
type: 'select',
placeholder: '全部状态',
options: [
{ label: '启用', value: 'ACTIVE' },
{ label: '待审核', value: 'PENDING' },
{ label: '已驳回', value: 'REJECTED' },
{ label: '已停用', value: 'INACTIVE' },
],
},
]
let loadSequence = 0
function displayText(value?: string | null) {
return value?.trim() || '—'
}
const customerColumns: DataTableColumn[] = [
{ key: 'company', label: '企业', minWidth: 230, slotName: 'company' },
{
prop: 'unifiedSocialCreditCode',
label: '统一社会信用代码',
minWidth: 205,
formatter: (row: PlatformTenantResource) => displayText(row.unifiedSocialCreditCode),
},
{
prop: 'legalName',
label: '法定代表人',
minWidth: 150,
formatter: (row: PlatformTenantResource) => displayText(row.legalName),
},
{ key: 'contact', label: '联系人', minWidth: 180, slotName: 'contact' },
{
prop: 'contactEmail',
label: '联系邮箱',
minWidth: 220,
formatter: (row: PlatformTenantResource) => displayText(row.contactEmail),
},
{ key: 'status', label: '状态', width: 110, slotName: 'status' },
]
function statusMeta(status: PlatformTenantStatus) {
if (status === 'ACTIVE') return { label: '启用', tone: 'is-success' }
if (status === 'PENDING') return { label: '待审核', tone: 'is-warning' }
if (status === 'REJECTED') return { label: '已驳回', tone: 'is-danger' }
if (status === 'INACTIVE') return { label: '已停用', tone: 'is-info' }
return { label: displayText(status), tone: 'is-info' }
}
async function loadCustomers() {
const sequence = ++loadSequence
loading.value = true
loadError.value = null
try {
const pageResult = await listPlatformTenants({
page: page.value,
size: pageSize.value,
keyword: filters.value.keyword.trim() || undefined,
status: filters.value.status || undefined,
})
if (sequence !== loadSequence) return
result.value = {
records: pageResult.records,
total: pageResult.total,
pages: pageResult.pages,
}
} catch (error) {
if (sequence !== loadSequence) return
loadError.value = getApiErrorDetails(error, '客户列表加载失败,请稍后重试')
} finally {
if (sequence === loadSequence) loading.value = false
}
}
function applyFilters() {
if (page.value === 1) {
void loadCustomers()
return
}
page.value = 1
}
watch([page, pageSize], () => { void loadCustomers() })
onMounted(() => {
void loadCustomers()
})
</script>
<template>
<div class="business-page customers-page">
<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" @click="loadCustomers">重新加载</el-button>
</section>
<section v-else class="surface-panel table-panel" :aria-busy="loading">
<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 #company="{ row }">
<div class="customer-company-cell">
<span><UsersRound :size="18" /></span>
<div class="primary-cell"><strong>{{ displayText((row as PlatformTenantResource).tenantName) }}</strong><span>{{ displayText((row as PlatformTenantResource).tenantCode) }}</span></div>
</div>
</template>
<template #contact="{ row }"><div class="primary-cell"><strong>{{ displayText((row as PlatformTenantResource).contactName) }}</strong><span>{{ displayText((row as PlatformTenantResource).contactPhone) }}</span></div></template>
<template #status="{ row }"><span class="status-badge" :class="statusMeta((row as PlatformTenantResource).status).tone"><i />{{ statusMeta((row as PlatformTenantResource).status).label }}</span></template>
</DataTable>
</section>
</div>
</template>
<style scoped>
.customers-error { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 16px; padding: 18px 20px; border-color: #fecdca; background: #fffafa; }
.customers-error strong { color: #7a271a; font-size: 13px; }
.customers-error p { margin: 4px 0 0; color: #b54708; font-size: 12px; overflow-wrap: anywhere; }
.customer-company-cell { display: flex; min-width: 0; align-items: center; gap: 10px; }
.customer-company-cell > span { display: grid; width: 34px; height: 34px; flex: 0 0 auto; place-items: center; border-radius: 7px; background: #eef4ff; color: var(--cc-primary); }
@media (max-width: 720px) {
.customers-error { align-items: stretch; flex-direction: column; }
.customers-error .el-button { min-height: 44px; }
}
</style>