优化
This commit is contained in:
@@ -31,6 +31,7 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import CustomerBillingRulesPanel from '@/modules/customers/components/CustomerBillingRulesPanel.vue'
|
||||
import CustomerOverseasStorageFeePanel from '@/modules/customers/CustomerOverseasStorageFeePanel.vue'
|
||||
|
||||
type DetailTab = 'basic' | 'business' | 'orders' | 'finance' | 'logs' | 'billing'
|
||||
type PreviewState = { url: string; kind: 'image' | 'pdf'; loading: boolean; failed: boolean }
|
||||
@@ -423,7 +424,7 @@ watch(orderPage, (page, previousPage) => {
|
||||
|
||||
<section v-else-if="activeTab === 'finance'" class="detail-panel module-panel"><div class="panel-title"><div><h2>财务账单</h2><p>客户应收、回款与对账记录</p></div><ReceiptText :size="19" /></div><div class="module-empty"><ReceiptText :size="30" /><strong>财务账单尚未接入</strong><p>当前阶段没有可用的客户账单 API,页面不会使用原型中的演示金额。</p></div></section>
|
||||
<section v-else-if="activeTab === 'logs'" class="detail-panel module-panel"><div class="panel-title"><div><h2>操作日志</h2><p>客户档案与状态变更记录</p></div><History :size="19" /></div><div class="module-empty"><History :size="30" /><strong>操作日志尚未接入</strong><p>等待审计服务提供按租户查询的只读接口后展示真实操作记录。</p></div></section>
|
||||
<section v-else class="detail-panel module-panel"><CustomerBillingRulesPanel :customer-id="tenantId" :customer-name="customer.tenantName" :customer-code="customer.tenantCode" /></section>
|
||||
<section v-else class="detail-panel module-panel"><CustomerBillingRulesPanel :customer-id="tenantId" :customer-name="customer.tenantName" :customer-code="customer.tenantCode" /><CustomerOverseasStorageFeePanel :customer-id="tenantId" :customer-name="customer.tenantName" /></section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Edit, RefreshCw, Search } from '@lucide/vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import { getApiErrorDetails } from '@/shared/api/http'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { listOverseasStorageFees, updateCustomerOverseasStorageFees, type OverseasStorageFeeConfig, type OverseasStorageFeeTier } from '../overseasStorageFee'
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const loading = ref(false)
|
||||
const error = ref<ReturnType<typeof getApiErrorDetails> | null>(null)
|
||||
const rows = ref<OverseasStorageFeeConfig[]>([])
|
||||
const page = ref(1)
|
||||
const size = ref(20)
|
||||
const total = ref(0)
|
||||
const keyword = ref('')
|
||||
const dialogOpen = ref(false)
|
||||
const saving = ref(false)
|
||||
const editing = ref<OverseasStorageFeeConfig | null>(null)
|
||||
const form = reactive({ currency: 'USD', version: 0, tiers: [] as OverseasStorageFeeTier[] })
|
||||
|
||||
const storageTiers = computed(() => form.tiers.filter((tier) => tier.feeType === 'STORAGE'))
|
||||
const returnTiers = computed(() => form.tiers.filter((tier) => tier.feeType === 'RETURN_STORAGE'))
|
||||
|
||||
function clone(tiers: OverseasStorageFeeTier[]) { return tiers.map((tier) => ({ ...tier })) }
|
||||
function label(tier: OverseasStorageFeeTier) { return tier.endDay === null ? `${tier.startDay}天以上` : `${tier.startDay}-${tier.endDay}天` }
|
||||
function summary(row: OverseasStorageFeeConfig, feeType: OverseasStorageFeeTier['feeType']) { return row.tiers.filter((tier) => tier.feeType === feeType).map((tier) => `${label(tier)} ${tier.unitPrice}`).join(' / ') }
|
||||
async function load() {
|
||||
loading.value = true; error.value = null
|
||||
try { const result = await listOverseasStorageFees(page.value, size.value, keyword.value.trim() || undefined); rows.value = result.records; total.value = Number(result.total) }
|
||||
catch (cause) { error.value = getApiErrorDetails(cause, '海外仓阶梯费用加载失败,请稍后重试') }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function search() { page.value = 1; void load() }
|
||||
function openEdit(row: OverseasStorageFeeConfig) { editing.value = row; form.currency = row.currency || 'USD'; form.version = row.version; form.tiers = clone(row.tiers); dialogOpen.value = true }
|
||||
async function save() {
|
||||
if (!editing.value || storageTiers.value.length !== 5 || returnTiers.value.length !== 4) { ElMessage.error('费用配置必须包含仓租5档和退货仓储费4档'); return }
|
||||
saving.value = true
|
||||
try { await updateCustomerOverseasStorageFees(editing.value.customerId, { customerId: editing.value.customerId, customerName: editing.value.customerName, currency: form.currency, version: form.version, tiers: form.tiers.map(({ feeType, billingUnit, startDay, endDay, unitPrice }) => ({ feeType, billingUnit, startDay, endDay, unitPrice })) }); dialogOpen.value = false; ElMessage.success('海外仓费用已保存'); await load() }
|
||||
catch (cause) { ElMessage.error(getApiErrorDetails(cause, '保存失败,请刷新后重试').message) }
|
||||
finally { saving.value = false }
|
||||
}
|
||||
onMounted(() => { void load() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="business-page overseas-fees-page">
|
||||
<section class="page-header"><div><h1>海外仓阶梯费用</h1><p>维护客户仓租和退货仓储费,初始化后可调整天数、价格和币种。</p></div><el-button :loading="loading" @click="load"><RefreshCw :size="16" />刷新</el-button></section>
|
||||
<section v-if="error" class="surface-panel error-state" role="alert"><div><strong>{{ error.message }}</strong><ApiErrorMeta :details="error" /></div><el-button type="primary" @click="load">重新加载</el-button></section>
|
||||
<section v-else class="surface-panel">
|
||||
<div class="toolbar"><el-input v-model="keyword" clearable placeholder="搜索客户名称" @keyup.enter="search"><template #prefix><Search :size="16" /></template></el-input><el-button type="primary" @click="search">查询</el-button></div>
|
||||
<el-table :data="rows" v-loading="loading" row-key="id" empty-text="暂无海外仓阶梯费用配置">
|
||||
<el-table-column label="操作" width="100" fixed="left"><template #default="{ row }"><el-button v-if="access.can('platform:customer:edit')" link type="primary" @click="openEdit(row)"><Edit :size="15" />编辑</el-button></template></el-table-column>
|
||||
<el-table-column prop="customerName" label="客户" min-width="180" /><el-table-column prop="customerId" label="客户ID" min-width="180" />
|
||||
<el-table-column prop="currency" label="币种" width="90" /><el-table-column label="仓租" min-width="260"><template #default="{ row }">{{ summary(row as OverseasStorageFeeConfig, 'STORAGE') }}</template></el-table-column>
|
||||
<el-table-column label="退货仓储费" min-width="260"><template #default="{ row }">{{ summary(row as OverseasStorageFeeConfig, 'RETURN_STORAGE') }}</template></el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination"><el-pagination v-model:current-page="page" v-model:page-size="size" :total="total" layout="total, sizes, prev, pager, next" @current-change="load" @size-change="search" /></div>
|
||||
</section>
|
||||
<AppDialog v-model="dialogOpen" title="编辑海外仓阶梯费用" width="min(1120px, calc(100vw - 28px))" :confirm-loading="saving" @confirm="save">
|
||||
<el-form label-position="top"><el-form-item label="币种"><el-select v-model="form.currency" style="width:180px"><el-option label="USD" value="USD" /><el-option label="CNY" value="CNY" /><el-option label="EUR" value="EUR" /></el-select></el-form-item><div class="groups"><div v-for="(group, index) in [storageTiers, returnTiers]" :key="index"><h3>{{ index === 0 ? '仓租(CBM/天)' : '退货仓储费(托/天)' }}</h3><el-table :data="group" size="small"><el-table-column label="开始天数"><template #default="{ row }"><el-input-number v-model="row.startDay" :min="1" :controls="false" /></template></el-table-column><el-table-column label="结束天数"><template #default="{ row }"><el-input-number v-if="row.endDay !== null" v-model="row.endDay" :min="1" :controls="false" /><span v-else>以上</span></template></el-table-column><el-table-column label="价格"><template #default="{ row }"><el-input-number v-model="row.unitPrice" :min="0" :precision="6" :controls="false" /></template></el-table-column></el-table></div></div></el-form>
|
||||
</AppDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-header,.toolbar,.pagination { display:flex; align-items:center; justify-content:space-between; gap:12px; }.page-header { margin-bottom:16px; }.page-header h1 { margin:0; font-size:22px; }.page-header p { margin:6px 0 0; color:var(--cc-muted); font-size:13px; }.toolbar { justify-content:flex-start; padding:16px; border-bottom:1px solid var(--cc-border); }.toolbar :deep(.el-input) { width:min(320px,100%); }.pagination { justify-content:flex-end; padding:16px; }.error-state { display:flex; justify-content:space-between; padding:20px; color:#b42318; }.groups { display:grid; grid-template-columns:1fr 1fr; gap:20px; }.groups h3 { margin:0 0 10px; font-size:15px; } @media (max-width:720px) { .groups { grid-template-columns:1fr; }.page-header { align-items:flex-start; flex-direction:column; } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user