This commit is contained in:
2026-09-30 14:01:58 +08:00
parent f14e1a2ea3
commit 8c51e9c1cf
109 changed files with 1525 additions and 651 deletions
@@ -45,13 +45,18 @@ watch([page, pageSize], load); onMounted(load)
</script>
<template><AdminPage class="admin-density"><ListPageLayout aria-label="汇率管理">
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" ariaLabel="汇率月份筛选" preference-key="overseas-exchange-rates-query" :loading="loading" @search="search" /></template>
<template #metrics><dl class="rate-summary density-detail-overview"><div v-for="item in [{label:'最新维护汇率',value:summary.latestUsdToCnyAverage??'—'},{label:'汇率月份',value:summary.latestMonth||'—'},{label:'最后更新人',value:summary.updatedBy||'—'},{label:'最后更新时间',value:summary.updatedAt||'—'}]" :key="item.label"><dt>{{ item.label }}</dt><dd>{{ item.value }}</dd></div></dl></template>
<template #state><LoadErrorState v-if="error" title="月度汇率加载失败" :message="error.message" @retry="load" /></template>
<section class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="month" preference-key="overseas-exchange-rates-table" empty-text="暂无月度汇率">
<section><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="month" preference-key="overseas-exchange-rates-table" empty-text="暂无月度汇率">
<template #toolbar-start><h1>汇率管理</h1></template>
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="汇率月份筛选" preference-key="overseas-exchange-rates-query" :loading="loading" @search="search" /></template>
<template #notices><LoadErrorState v-if="error" title="月度汇率加载失败" :message="error.message" @retry="load" /></template>
<template #before-results><dl class="rate-summary density-detail-overview"><div v-for="item in [{label:'最新维护汇率',value:summary.latestUsdToCnyAverage??'—'},{label:'汇率月份',value:summary.latestMonth||'—'},{label:'最后更新人',value:summary.updatedBy||'—'},{label:'最后更新时间',value:summary.updatedAt||'—'}]" :key="item.label"><dt>{{ item.label }}</dt><dd>{{ item.value }}</dd></div></dl></template>
<template #toolbar-actions><el-button v-if="access.can(permission.exchangeRateCreate)" type="primary" @click="openCreate"><Plus :size="16" />更新月度汇率</el-button><el-button v-if="access.can(permission.exchangeRateExport)" :loading="exporting" @click="exportCsv"><Download :size="16" />导出</el-button></template>
<template #actions="{ row }"><div class="density-actions"><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.exchangeRateEdit)" @click="openEdit(row as MonthlyExchangeRate)">编辑</el-button><el-button class="density-action" v-if="access.can(permission.exchangeRateStatus)" :data-tone="(row as MonthlyExchangeRate).status === 'ACTIVE' ? 'danger' : 'success'" @click="toggle(row as MonthlyExchangeRate)">{{ (row as MonthlyExchangeRate).status === 'ACTIVE' ? '停用' : '启用' }}</el-button></div></template>
<template #actions="{ row }"><div class="density-actions row-actions admin-text-actions"><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.exchangeRateEdit)" @click="openEdit(row as MonthlyExchangeRate)">编辑</el-button><el-button class="density-action" v-if="access.can(permission.exchangeRateStatus)" :data-tone="(row as MonthlyExchangeRate).status === 'ACTIVE' ? 'danger' : 'success'" @click="toggle(row as MonthlyExchangeRate)">{{ (row as MonthlyExchangeRate).status === 'ACTIVE' ? '停用' : '启用' }}</el-button></div></template>
<template #conversion="{ row }">1 USD = {{ (row as MonthlyExchangeRate).usdToCnyAverage }} CNY</template><template #status="{ row }"><el-tag :type="(row as MonthlyExchangeRate).status === 'ACTIVE' ? 'success' : 'info'">{{ (row as MonthlyExchangeRate).status === 'ACTIVE' ? '启用' : '停用' }}</el-tag></template>
</DataTable></section>
<AppDialog class="admin-density-dialog" v-model="editorOpen" :title="editingMonth ? '编辑月度汇率' : '更新月度汇率'" width="min(560px, calc(100vw - 28px))" :confirm-loading="saving" @confirm="save"><el-form class="density-form" label-position="top"><el-form-item label="汇率月份"><el-date-picker v-model="form.month" type="month" value-format="YYYY-MM" :disabled="!!editingMonth" /></el-form-item><el-form-item label="美元兑人民币平均汇率"><el-input-number v-model="form.usdToCnyAverage" :min="0.00000001" :precision="8" placeholder="请输入美元兑人民币月均汇率" /><p>汇率方向:1 USD = X CNY</p></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" placeholder="请输入备注(选填)" /></el-form-item></el-form></AppDialog>
@@ -54,11 +54,16 @@ async function exportCsv(){exporting.value=true;try{const {page:_,size:__,...q}=
watch([page,pageSize],load);onMounted(()=>{void Promise.all([load(),loadOptions()])})
</script>
<template><AdminPage class="admin-density"><ListPageLayout aria-label="海外仓报价列表">
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" ariaLabel="海外仓报价筛选" preference-key="overseas-quotes-query" :loading="loading" @search="search" /></template>
<template #state><LoadErrorState v-if="error" title="海外仓报价加载失败" :message="error.message" @retry="load" /></template>
<section class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-quotes-table" row-key="id" empty-text="暂无海外仓报价">
<section><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-quotes-table" row-key="id" empty-text="暂无海外仓报价">
<template #toolbar-start><h1>海外仓报价</h1></template>
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="海外仓报价筛选" preference-key="overseas-quotes-query" :loading="loading" @search="search" /></template>
<template #notices><LoadErrorState v-if="error" title="海外仓报价加载失败" :message="error.message" @retry="load" /></template>
<template #toolbar-actions><el-button v-if="access.can(permission.quoteCreate)" type="primary" @click="openCreate"><Plus :size="16"/>新增报价</el-button><el-button v-if="access.can(permission.quoteExport)" :loading="exporting" @click="exportCsv"><Download :size="16"/>导出CSV</el-button></template>
<template #actions="{row}"><div class="density-actions"><el-button data-tone="neutral" class="density-action" @click="openDetail(row as OverseasWarehouseQuote)">详情</el-button><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.quoteEdit)" @click="openEdit(row as OverseasWarehouseQuote)">编辑</el-button><el-button class="density-action" v-if="access.can(permission.quoteStatus)" :data-tone="(row as OverseasWarehouseQuote).status==='ACTIVE'?'danger':'success'" @click="toggle(row as OverseasWarehouseQuote)">{{(row as OverseasWarehouseQuote).status==='ACTIVE'?'停用':'启用'}}</el-button></div></template>
<template #actions="{row}"><div class="density-actions row-actions admin-text-actions"><el-button data-tone="neutral" class="density-action" @click="openDetail(row as OverseasWarehouseQuote)">详情</el-button><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.quoteEdit)" @click="openEdit(row as OverseasWarehouseQuote)">编辑</el-button><el-button class="density-action" v-if="access.can(permission.quoteStatus)" :data-tone="(row as OverseasWarehouseQuote).status==='ACTIVE'?'danger':'success'" @click="toggle(row as OverseasWarehouseQuote)">{{(row as OverseasWarehouseQuote).status==='ACTIVE'?'停用':'启用'}}</el-button></div></template>
<template #status="{row}"><el-tag :type="(row as OverseasWarehouseQuote).status==='ACTIVE'?'success':'info'">{{(row as OverseasWarehouseQuote).status==='ACTIVE'?'启用':'停用'}}</el-tag></template>
</DataTable></section>
<AppDialog class="admin-density-dialog" v-model="detailOpen" title="海外仓报价详情" width="min(980px, calc(100vw - 28px))" :show-confirm="false"><dl v-if="detail" class="detail-grid density-fields"><div v-for="item in [['报价名称',detail.summary.name],['海外仓',detail.summary.warehouseName],['服务商',detail.summary.providerName],['币种',detail.summary.currency],['生效日期',detail.summary.effectiveStart],['失效日期',detail.summary.effectiveEnd],['版本',detail.summary.versionCode],['状态',detail.summary.status]]" :key="item[0]"><dt>{{item[0]}}</dt><dd>{{item[1]}}</dd></div></dl><h3>仓储作业费用</h3><el-table :data="detail?.warehouseFees||[]" border><el-table-column prop="item" label="费用项目"/><el-table-column prop="billingUnit" label="计费单位"/><el-table-column prop="unitPrice" label="单价"/><el-table-column prop="minimumCharge" label="最低收费"/><el-table-column prop="remark" label="备注"/></el-table><h3>中转派送价格</h3><el-table :data="detail?.transitPrices||[]" border><el-table-column prop="fbaWarehouseCode" label="目的FBA仓"/><el-table-column prop="deliveryMethod" label="车型/提货方式"/><el-table-column prop="estimatedMiles" label="预估迈数"/><el-table-column prop="price" label="报价"/><el-table-column prop="minimumCharge" label="最低收费"/></el-table></AppDialog>
@@ -24,6 +24,11 @@ function openStatus(row:VehicleDetail){statusRow.value=row;statusTarget.value=ro
async function exportCsv(){exporting.value=true;try{const{page:_,size:__,...q}=params();saveCsv(await exportVehicles(q),'vehicles.csv')}catch(e){ElMessage.error(masterDataErrorMessage(e,'车辆导出失败'))}finally{exporting.value=false}}
watch([page,pageSize],load);onMounted(load)
</script>
<template><AdminPage class="admin-density"><ListPageLayout aria-label="车辆列表"><template #toolbar><div class="query-panel"><div class="status-tabs density-tabs" role="tablist" aria-label="车辆归属"><button v-for="tab in tabs" :key="tab[1]" role="tab" :aria-selected="ownership===tab[1]" :class="['status-tab',{active:ownership===tab[1]}]" @click="selectOwnership(tab[1])"><span class="density-tab-icon" aria-hidden="true">{{ tab[1] === 'OWNED' ? '🚚' : '🚛' }}</span>{{tab[0]}}</button></div><QueryToolbar v-model="filters" :fields="fields" ariaLabel="车辆筛选" preference-key="overseas-vehicles-query" :loading="loading" @search="search"/></div></template><template #state><LoadErrorState v-if="error" title="车辆加载失败" :message="error.message" @retry="load"/></template><section class="surface-panel vehicle-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-vehicles-table" row-key="id" empty-text="暂无车辆"><template #toolbar-actions><el-button v-if="access.can(permission.vehicleCreate)" type="primary" @click="openCreate"><Plus :size="16"/>添加车辆</el-button><el-button v-if="access.can(permission.vehicleExport)" :loading="exporting" @click="exportCsv"><Download :size="16"/>导出CSV</el-button></template><template #actions="{row}"><div class="density-actions"><el-button data-tone="neutral" class="density-action" @click="router.push({name:'overseas-master-data-vehicle-detail',params:{vehicleId:(row as VehicleDetail).id}})">查看</el-button><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.vehicleEdit)" @click="openEdit(row as VehicleDetail)">编辑</el-button><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.vehicleStatus)" @click="openStatus(row as VehicleDetail)">状态</el-button></div></template><template #status="{row}"><el-tag>{{vehicleStatusLabels[(row as VehicleDetail).status]}}</el-tag></template><template #region="{row}">{{(row as VehicleDetail).region==='OVERSEAS'?'海外':'国内'}}</template></DataTable></section>
<template><AdminPage class="admin-density"><ListPageLayout aria-label="车辆列表"><section class="vehicle-panel"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-vehicles-table" row-key="id" empty-text="暂无车辆">
<template #toolbar-start><h1>车辆列表</h1></template>
<template #filters><div class="query-panel integrated-query-group"><div class="status-tabs density-tabs" role="tablist" aria-label="车辆归属"><button v-for="tab in tabs" :key="tab[1]" role="tab" :aria-selected="ownership===tab[1]" :class="['status-tab',{active:ownership===tab[1]}]" @click="selectOwnership(tab[1])"><span class="density-tab-icon" aria-hidden="true">{{ tab[1] === 'OWNED' ? '🚚' : '🚛' }}</span>{{tab[0]}}</button></div><QueryToolbar v-model="filters" :fields="fields" ariaLabel="车辆筛选" preference-key="overseas-vehicles-query" :loading="loading" @search="search"/></div></template>
<template #notices><LoadErrorState v-if="error" title="车辆加载失败" :message="error.message" @retry="load"/></template>
<template #toolbar-actions><el-button v-if="access.can(permission.vehicleCreate)" type="primary" @click="openCreate"><Plus :size="16"/>添加车辆</el-button><el-button v-if="access.can(permission.vehicleExport)" :loading="exporting" @click="exportCsv"><Download :size="16"/>导出CSV</el-button></template><template #actions="{row}"><div class="density-actions row-actions admin-text-actions"><el-button data-tone="neutral" class="density-action" @click="router.push({name:'overseas-master-data-vehicle-detail',params:{vehicleId:(row as VehicleDetail).id}})">查看</el-button><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.vehicleEdit)" @click="openEdit(row as VehicleDetail)">编辑</el-button><el-button data-tone="neutral" class="density-action" v-if="access.can(permission.vehicleStatus)" @click="openStatus(row as VehicleDetail)">状态</el-button></div></template><template #status="{row}"><el-tag>{{vehicleStatusLabels[(row as VehicleDetail).status]}}</el-tag></template><template #region="{row}">{{(row as VehicleDetail).region==='OVERSEAS'?'海外':'国内'}}</template></DataTable></section>
<AppDialog class="admin-density-dialog" v-model="editorOpen" :title="editingId?'编辑车辆':'添加车辆'" width="min(760px, calc(100vw - 28px))" :confirm-loading="saving" @confirm="save"><el-form class="density-form" label-position="top"><div class="form-grid"><el-form-item label="车辆归属"><el-select v-model="form.ownership"><el-option label="自有车辆" value="OWNED"/><el-option label="外派车辆" value="DISPATCHED"/></el-select></el-form-item><el-form-item label="车牌号"><el-input v-model="form.plateNumber" placeholder="请输入车牌号" /></el-form-item><el-form-item label="车型"><el-input v-model="form.model" placeholder="请输入车辆型号" /></el-form-item><el-form-item label="油耗(L/100km)"><el-input-number v-model="form.fuelConsumptionPer100Km" :min="0.01" :precision="2" placeholder="请输入百公里油耗" /></el-form-item><el-form-item label="当前公里码(km)"><el-input-number v-model="form.mileageKm" :min="0" placeholder="请输入当前里程" /></el-form-item><el-form-item label="联系电话"><el-input v-model="form.phone" placeholder="请输入联系电话" /></el-form-item><el-form-item label="司机 / 外派单位"><el-input v-model="form.driverOrDispatchCompany" placeholder="请输入司机或外派单位" /></el-form-item><el-form-item label="区域"><el-select v-model="form.region"><el-option label="国内" value="DOMESTIC"/><el-option label="海外" value="OVERSEAS"/></el-select></el-form-item><el-form-item v-if="!editingId" label="状态"><el-select v-model="form.status"><el-option v-for="s in ['IN_SERVICE','DISPATCHED','MAINTENANCE','INACTIVE','RETURNED']" :key="s" :label="vehicleStatusLabels[s as keyof typeof vehicleStatusLabels]" :value="s"/></el-select></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" placeholder="请输入备注(选填)" /></el-form-item></div></el-form></AppDialog><AppDialog class="admin-density-dialog" v-model="statusOpen" title="变更车辆状态" width="min(480px, calc(100vw - 28px))" :confirm-loading="saving" @confirm="submitStatus"><el-select v-model="statusTarget" style="width:100%"><el-option v-for="s in ['IN_SERVICE','DISPATCHED','MAINTENANCE','INACTIVE','RETURNED']" :key="s" :label="vehicleStatusLabels[s as keyof typeof vehicleStatusLabels]" :value="s"/></el-select></AppDialog></ListPageLayout></AdminPage></template>
<style scoped>.vehicle-panel{min-width:0;overflow:hidden}.query-panel{overflow:hidden;border:1px solid var(--cc-border);border-radius:var(--admin-radius);background:#fff}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--admin-space-5) var(--admin-space-6)}</style>
<style scoped>.vehicle-panel{min-width:0;overflow:hidden}.query-panel{min-width:0}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--admin-space-5) var(--admin-space-6)}</style>
+3 -3
View File
@@ -6,19 +6,19 @@ export const overseasMasterDataRoutes = [
path: 'overseas-master-data/quotes',
name: 'overseas-master-data-quotes',
component: () => import('./pages/OverseasQuotesView.vue'),
meta: { title: '海外仓报价', description: '维护海外合作仓仓储作业与中转派送报价', permission: permission.quoteList },
meta: { showPageHeading: false, title: '海外仓报价', description: '维护海外合作仓仓储作业与中转派送报价', permission: permission.quoteList },
},
{
path: 'overseas-master-data/exchange-rates',
name: 'overseas-master-data-exchange-rates',
component: () => import('./pages/ExchangeRatesView.vue'),
meta: { title: '汇率管理', description: '维护美元兑人民币月平均汇率', permission: permission.exchangeRateList },
meta: { showPageHeading: false, title: '汇率管理', description: '维护美元兑人民币月平均汇率', permission: permission.exchangeRateList },
},
{
path: 'overseas-master-data/vehicles',
name: 'overseas-master-data-vehicles',
component: () => import('./pages/VehiclesView.vue'),
meta: { title: '车辆列表', description: '查询国内与海外自有、外派车辆档案', permission: permission.vehicleList },
meta: { showPageHeading: false, title: '车辆列表', description: '查询国内与海外自有、外派车辆档案', permission: permission.vehicleList },
},
{
path: 'overseas-master-data/vehicles/:vehicleId',