166 lines
6.1 KiB
Vue
166 lines
6.1 KiB
Vue
<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>
|