This commit is contained in:
2026-08-10 15:36:53 +08:00
parent 5d97c7517a
commit f6556c57b5
54 changed files with 4107 additions and 170 deletions
@@ -3,8 +3,12 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { ExternalLink, Pencil, Plus, Power, Truck, Upload } from '@lucide/vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AdminPage from '@/shared/components/AdminPage.vue'
import BatchImportDialog from '@/shared/components/BatchImportDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import FormSection from '@/shared/components/FormSection.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import PageToolbar from '@/shared/components/PageToolbar.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
@@ -278,15 +282,26 @@ onMounted(() => {
</script>
<template>
<div class="business-page express-company-page">
<AdminPage>
<div class="business-page express-company-page">
<PageToolbar title="快递公司" :description="`平台公共数据 · 共 ${total} 家快递公司`">
<template #actions>
<el-button v-if="access.can('platform:express:import')" :disabled="loading" @click="openImport"><Upload :size="16" />批量导入</el-button>
<el-button v-if="access.can('platform:express:create')" type="primary" :disabled="loading" @click="openCreate"><Plus :size="16" />新增快递公司</el-button>
</template>
</PageToolbar>
<ListPageLayout ariaLabel="快递公司列表">
<template #toolbar>
<QueryToolbar v-model="filters" class="surface-panel" ariaLabel="快递公司筛选" :fields="queryFields" preference-key="express-companies-query" @search="applyFilters" />
</template>
<template #state>
<el-alert v-if="loadError" type="error" :closable="false" show-icon class="state-alert">
<template #title>快递公司加载失败</template>
<p>{{ loadError.message }}</p>
<ApiErrorMeta :details="loadError" />
<el-button size="small" @click="loadExpressCompanies()">重新查询</el-button>
</el-alert>
<QueryToolbar v-model="filters" class="surface-panel" ariaLabel="快递公司筛选" :fields="queryFields" preference-key="express-companies-query" @search="applyFilters" />
</template>
<section class="surface-panel table-panel">
<DataTable
@@ -302,8 +317,6 @@ onMounted(() => {
row-key="id"
:empty-text="appliedFilters.keyword.trim() || appliedFilters.status ? '暂无符合筛选条件的快递公司' : '暂无快递公司档案'"
>
<template #toolbar-start><div><h2>快递公司列表</h2><p>平台公共数据 · 共 {{ total }} 家快递公司</p></div></template>
<template #toolbar-actions><div class="panel-actions"><el-button v-if="access.can('platform:express:import')" :disabled="loading" @click="openImport"><Upload :size="16" />批量导入</el-button><el-button v-if="access.can('platform:express:create')" type="primary" :disabled="loading" @click="openCreate"><Plus :size="16" />新增快递公司</el-button></div></template>
<template #actions="{ row }">
<div v-if="access.can('platform:express:edit') || access.can('platform:express:status')" class="table-actions">
<el-button
@@ -341,6 +354,7 @@ onMounted(() => {
<template #pagination-left="{ total: tableTotal }"><span>共 {{ tableTotal }} 家快递公司</span></template>
</DataTable>
</section>
</ListPageLayout>
<BatchImportDialog
v-model="importOpen"
@@ -356,15 +370,15 @@ onMounted(() => {
<el-drawer v-model="editorOpen" :title="editorTitle" size="580px" class="business-drawer editor-drawer" :close-on-click-modal="false">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="drawer-form" :disabled="saving">
<div class="form-section">
<FormSection title="基础信息">
<h3>公司标识</h3>
<div class="form-grid two-cols">
<el-form-item label="快递公司编码" prop="companyCode" required><el-input v-model.trim="form.companyCode" maxlength="50" placeholder="例如 SF" /></el-form-item>
<el-form-item label="快递公司名称" prop="companyName" required><el-input v-model.trim="form.companyName" maxlength="128" placeholder="例如 顺丰速运" /></el-form-item>
</div>
<el-form-item label="状态" prop="status" required><el-radio-group v-model="form.status"><el-radio-button value="ACTIVE">启用</el-radio-button><el-radio-button value="INACTIVE">停用</el-radio-button></el-radio-group></el-form-item>
</div>
<div class="form-section">
</FormSection>
<FormSection title="服务配置">
<h3>服务与查询</h3>
<div class="form-grid two-cols">
<el-form-item label="客服电话"><el-input v-model.trim="form.servicePhone" maxlength="32" placeholder="例如 95338" /></el-form-item>
@@ -372,19 +386,21 @@ onMounted(() => {
</div>
<el-form-item label="轨迹查询地址模板"><el-input v-model.trim="form.trackingUrlTemplate" maxlength="500" placeholder="https://example.com/track/{trackingNo}" /></el-form-item>
<p class="form-help">可使用 <code>{trackingNo}</code> 作为快递单号占位符;一期不包含轨迹订阅和自动回传。</p>
</div>
<div class="form-section"><h3>备注</h3><el-form-item label="内部说明"><el-input v-model.trim="form.remark" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入适用范围、服务限制或其他说明" /></el-form-item></div>
</FormSection>
<FormSection title="备注"><el-form-item label="内部说明"><el-input v-model.trim="form.remark" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入适用范围、服务限制或其他说明" /></el-form-item></FormSection>
</el-form>
<template #footer><div class="drawer-footer"><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="saveExpressCompany">保存快递公司</el-button></div></template>
</el-drawer>
</div>
</div>
</AdminPage>
</template>
<style scoped>
.express-company-page { gap: 16px; }
.state-alert p { margin: 6px 0 0; line-height: 1.55; }
.state-alert .el-button { margin-top: 10px; }
.express-company-cell { display: flex; min-width: 0; align-items: center; gap: 10px; }
.express-company-cell > span { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border-radius: 7px; background: #eef3fa; color: var(--cc-primary); }
.express-company-cell > span { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border: 1px solid var(--cc-border); border-radius: 7px; background: #f8fafc; color: var(--cc-primary); }
.express-company-cell > div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
.express-company-cell strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.express-company-cell small { color: var(--cc-muted); font-size: 11px; }
@@ -397,5 +413,6 @@ onMounted(() => {
.panel-actions .el-button { margin-left: 0; }
@media (max-width: 720px) {
.panel-actions, .panel-actions .el-button { width: 100%; }
.express-company-page :deep(.query-toolbar__actions) { width: 100%; }
}
</style>