优化
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Download, Pencil, Plus, Power } from '@lucide/vue'
|
||||
import { Download, Plus } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
@@ -38,21 +39,21 @@ function search() { if (page.value === 1) void load(); else page.value = 1 }
|
||||
function openCreate() { editingMonth.value = ''; Object.assign(form, { month: '', usdToCnyAverage: 0, remark: '', recordVersion: 0 }); editorOpen.value = true }
|
||||
async function openEdit(row: MonthlyExchangeRate) { try { const value = await getExchangeRate(row.month); editingMonth.value = value.month; Object.assign(form, { month: value.month, usdToCnyAverage: Number(value.usdToCnyAverage), remark: value.remark || '', recordVersion: value.recordVersion }); editorOpen.value = true } catch (cause) { ElMessage.error(masterDataErrorMessage(cause, '汇率详情加载失败')) } }
|
||||
async function save() { if (!form.month || form.usdToCnyAverage <= 0) { ElMessage.warning('请选择月份并填写大于 0 的平均汇率'); return } saving.value = true; try { if (editingMonth.value) await updateExchangeRate(editingMonth.value, { recordVersion: form.recordVersion, usdToCnyAverage: form.usdToCnyAverage, remark: form.remark || null }); else await createExchangeRate({ month: form.month, usdToCnyAverage: form.usdToCnyAverage, remark: form.remark || null }); ElMessage.success(editingMonth.value ? '月度汇率已更新' : '月度汇率已创建'); editorOpen.value = false; await load() } catch (cause) { ElMessage.error(masterDataErrorMessage(cause, '月度汇率保存失败')) } finally { saving.value = false } }
|
||||
async function toggle(row: MonthlyExchangeRate) { const next: MasterDataStatus = row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'; try { await ElMessageBox.confirm(`确认${next === 'ACTIVE' ? '启用' : '停用'} ${row.month} 汇率吗?`, '汇率状态', { type: 'warning' }); await changeExchangeRateStatus(row.month, next, row.recordVersion, null); ElMessage.success('汇率状态已更新'); await load() } catch (cause) { if (cause !== 'cancel' && cause !== 'close') ElMessage.error(masterDataErrorMessage(cause, '汇率状态更新失败')) } }
|
||||
async function toggle(row: MonthlyExchangeRate) { const next: MasterDataStatus = row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'; try { await ElMessageBox.confirm(`确认${next === 'ACTIVE' ? '启用' : '停用'} ${row.month} 汇率吗?`, '汇率状态', { customClass: 'admin-density-confirm', type: 'warning' }); await changeExchangeRateStatus(row.month, next, row.recordVersion, null); ElMessage.success('汇率状态已更新'); await load() } catch (cause) { if (cause !== 'cancel' && cause !== 'close') ElMessage.error(masterDataErrorMessage(cause, '汇率状态更新失败')) } }
|
||||
async function exportCsv() { exporting.value = true; try { const { page: _page, size: _size, ...query } = params(); saveCsv(await exportExchangeRates(query), 'monthly-exchange-rates.csv') } catch (cause) { ElMessage.error(masterDataErrorMessage(cause, '汇率导出失败')) } finally { exporting.value = false } }
|
||||
watch([page, pageSize], load); onMounted(load)
|
||||
</script>
|
||||
|
||||
<template><AdminPage><ListPageLayout aria-label="汇率管理">
|
||||
<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><section class="rate-summary"><article v-for="item in [{label:'最新维护汇率',value:summary.latestUsdToCnyAverage??'—'},{label:'汇率月份',value:summary.latestMonth||'—'},{label:'最后更新人',value:summary.updatedBy||'—'},{label:'最后更新时间',value:summary.updatedAt||'—'}]" :key="item.label" class="surface-panel"><span>{{ item.label }}</span><strong>{{ item.value }}</strong></article></section></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="暂无月度汇率">
|
||||
<template #toolbar-start><div><h2>美元兑人民币月平均汇率</h2><p>统一口径:1 USD = X CNY</p></div></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 }"><el-button v-if="access.can(permission.exchangeRateEdit)" link type="primary" @click="openEdit(row as MonthlyExchangeRate)"><Pencil :size="15" />编辑</el-button><el-button v-if="access.can(permission.exchangeRateStatus)" link :type="(row as MonthlyExchangeRate).status === 'ACTIVE' ? 'danger' : 'success'" @click="toggle(row as MonthlyExchangeRate)"><Power :size="15" />{{ (row as MonthlyExchangeRate).status === 'ACTIVE' ? '停用' : '启用' }}</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 #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 v-model="editorOpen" :title="editingMonth ? '编辑月度汇率' : '更新月度汇率'" width="min(560px, calc(100vw - 28px))" :confirm-loading="saving" @confirm="save"><el-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>
|
||||
<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>
|
||||
</ListPageLayout></AdminPage></template>
|
||||
<style scoped>.rate-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}.rate-summary article{padding:16px 18px}.rate-summary span{display:block;color:var(--cc-muted);font-size:12px}.rate-summary strong{display:block;margin-top:8px;font-size:22px}.table-panel{min-width:0;overflow:hidden}</style>
|
||||
<style scoped>.table-panel{min-width:0;overflow:hidden}</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { flushPromises, shallowMount, type VueWrapper } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getVehicle } from '../api/overseasMasterData'
|
||||
import ExchangeRatesView from './ExchangeRatesView.vue'
|
||||
import OverseasQuotesView from './OverseasQuotesView.vue'
|
||||
import VehicleDetailView from './VehicleDetailView.vue'
|
||||
@@ -26,5 +27,15 @@ function mountView(component: object): VueWrapper { return shallowMount(componen
|
||||
describe('overseas master data real page mounts', () => {
|
||||
beforeEach(() => push.mockReset())
|
||||
it.each([OverseasQuotesView, ExchangeRatesView, VehiclesView])('mounts a real API-backed list page', async (component) => { const wrapper = mountView(component); await flushPromises(); expect(wrapper.find('[data-test="data-table"]').exists()).toBe(true) })
|
||||
it('mounts API-backed vehicle detail without fabricating fulfillment history', async () => { const wrapper = mountView(VehicleDetailView); await flushPromises(); expect(wrapper.text()).toContain('CA-TEST'); expect(wrapper.text()).toContain('当前车辆主数据接口明确不包含派送履约记录') })
|
||||
it('keeps the business title empty on detail failure instead of exposing the route ID', async () => {
|
||||
vi.mocked(getVehicle).mockRejectedValueOnce(new Error('加载失败'))
|
||||
const wrapper = mountView(VehicleDetailView)
|
||||
await flushPromises()
|
||||
expect(wrapper.get('.density-detail-title').text()).toBe('—')
|
||||
expect(wrapper.text()).not.toContain('VH-TEST-001')
|
||||
expect(wrapper.find('.density-fields').exists()).toBe(false)
|
||||
expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('mounts API-backed vehicle detail without fabricating fulfillment history', async () => { const wrapper = mountView(VehicleDetailView); await flushPromises(); expect(wrapper.get('.density-detail-title').text()).toBe('CA-TEST'); expect(wrapper.get('.density-fields').findAll('dt')).toHaveLength(11); expect(wrapper.get('.density-fields').text()).toContain('在役'); expect(wrapper.text()).not.toContain('IN_SERVICE'); expect(wrapper.text()).toContain('当前车辆主数据接口明确不包含派送履约记录') })
|
||||
})
|
||||
|
||||
@@ -42,8 +42,8 @@ describe('overseas master data real contract pages', () => {
|
||||
for (const label of ['自有车辆', '外派车辆', '司机 / 外派单位']) expect(vehicles).toContain(label)
|
||||
expect(vehicles).toContain("key:'region'")
|
||||
expect(vehicles).toContain("placeholder:'全部区域'")
|
||||
expect(vehicles).toContain('class="status-tabs"')
|
||||
expect(vehicles).toContain("'status-tab',{'is-active':ownership===tab[1]}")
|
||||
expect(vehicles).toContain('class="status-tabs density-tabs"')
|
||||
expect(vehicles).toContain("'status-tab',{active:ownership===tab[1]}")
|
||||
expect(vehicles).not.toContain('<el-segmented')
|
||||
expect(vehicles).not.toContain('aria-label="车辆区域"')
|
||||
})
|
||||
|
||||
@@ -64,6 +64,20 @@ describe('overseas master data failed writes preserve editor context', () => {
|
||||
vi.mocked(api.getVehicle).mockResolvedValue(vehicle)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[OverseasQuotesView, ['详情', '编辑', '停用']],
|
||||
[VehiclesView, ['查看', '编辑', '状态']],
|
||||
[ExchangeRatesView, ['编辑', '停用']],
|
||||
])('keeps all available actions visible without an unnecessary overflow menu', async (component, labels) => {
|
||||
const wrapper = mountView(component)
|
||||
await flushPromises()
|
||||
const actions = wrapper.get('.density-actions')
|
||||
const expected = component === OverseasQuotesView ? ['详情', '编辑', '启用'] : labels
|
||||
expect(actions.findAll('button').map(button => button.text().trim())).toEqual(expected)
|
||||
expect(actions.findAll('button').every(button => button.classes().includes('density-action'))).toBe(true)
|
||||
expect(actions.find('el-dropdown-stub').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps quote edit open, sends only update fields, and shows A0409 request ID', async () => {
|
||||
vi.mocked(api.updateQuote).mockRejectedValue(new ApiResponseError('记录版本已变化', 'A0409', 200, null, 'req-quote-409'))
|
||||
const wrapper = mountView(OverseasQuotesView)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Download, Eye, Pencil, Plus, Power } from '@lucide/vue'
|
||||
import { Download, Plus } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
@@ -48,19 +49,19 @@ function fill(d:OverseasWarehouseQuoteDetail){const s=d.summary;Object.assign(fo
|
||||
async function openCreate(){editingId.value='';resetForm();if(!options.value.length)await loadOptions();editorOpen.value=true}
|
||||
async function openEdit(row:OverseasWarehouseQuote){try{const d=await getQuote(row.id);editingId.value=row.id;fill(d);editorOpen.value=true}catch(e){ElMessage.error(masterDataErrorMessage(e,'报价详情加载失败'))}}
|
||||
async function save(){if(!form.name||!form.warehouseId||!form.providerName||!form.effectiveStart||!form.effectiveEnd||!form.warehouseFees.length){ElMessage.warning('请完整填写报价必填信息');return} if(form.effectiveEnd<form.effectiveStart){ElMessage.warning('失效日期不能早于生效日期');return} saving.value=true;try{const common={name:form.name,providerName:form.providerName,currency:form.currency as 'USD'|'CNY',effectiveStart:form.effectiveStart,effectiveEnd:form.effectiveEnd,warehouseFees:form.warehouseFees,transitPrices:form.transitPrices,remark:form.remark||null};if(editingId.value)await updateQuote(editingId.value,{...common,recordVersion:form.recordVersion});else await createQuote({...common,warehouseId:form.warehouseId,versionCode:form.versionCode});ElMessage.success(editingId.value?'报价已更新':'报价已创建');editorOpen.value=false;await load()}catch(e){ElMessage.error(masterDataErrorMessage(e,'报价保存失败'))}finally{saving.value=false}}
|
||||
async function toggle(row:OverseasWarehouseQuote){const next:MasterDataStatus=row.status==='ACTIVE'?'INACTIVE':'ACTIVE';try{await ElMessageBox.confirm(`确认${next==='ACTIVE'?'启用':'停用'}“${row.name}”吗?`,'报价状态',{type:'warning'});await changeQuoteStatus(row.id,next,row.recordVersion);ElMessage.success('报价状态已更新');await load()}catch(e){if(e!=='cancel'&&e!=='close')ElMessage.error(masterDataErrorMessage(e,'报价状态更新失败'))}}
|
||||
async function toggle(row:OverseasWarehouseQuote){const next:MasterDataStatus=row.status==='ACTIVE'?'INACTIVE':'ACTIVE';try{await ElMessageBox.confirm(`确认${next==='ACTIVE'?'启用':'停用'}“${row.name}”吗?`,'报价状态',{customClass:'admin-density-confirm',type:'warning'});await changeQuoteStatus(row.id,next,row.recordVersion);ElMessage.success('报价状态已更新');await load()}catch(e){if(e!=='cancel'&&e!=='close')ElMessage.error(masterDataErrorMessage(e,'报价状态更新失败'))}}
|
||||
async function exportCsv(){exporting.value=true;try{const {page:_,size:__,...q}=params();saveCsv(await exportQuotes(q),'overseas-quotes.csv')}catch(e){ElMessage.error(masterDataErrorMessage(e,'报价导出失败'))}finally{exporting.value=false}}
|
||||
watch([page,pageSize],load);onMounted(()=>{void Promise.all([load(),loadOptions()])})
|
||||
</script>
|
||||
<template><AdminPage><ListPageLayout aria-label="海外仓报价列表">
|
||||
<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="暂无海外仓报价">
|
||||
<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}"><el-button link type="primary" @click="openDetail(row as OverseasWarehouseQuote)"><Eye :size="15"/>详情</el-button><el-button v-if="access.can(permission.quoteEdit)" link type="primary" @click="openEdit(row as OverseasWarehouseQuote)"><Pencil :size="15"/>编辑</el-button><el-button v-if="access.can(permission.quoteStatus)" link :type="(row as OverseasWarehouseQuote).status==='ACTIVE'?'danger':'success'" @click="toggle(row as OverseasWarehouseQuote)"><Power :size="15"/>{{(row as OverseasWarehouseQuote).status==='ACTIVE'?'停用':'启用'}}</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 #status="{row}"><el-tag :type="(row as OverseasWarehouseQuote).status==='ACTIVE'?'success':'info'">{{(row as OverseasWarehouseQuote).status==='ACTIVE'?'启用':'停用'}}</el-tag></template>
|
||||
</DataTable></section>
|
||||
<AppDialog v-model="detailOpen" title="海外仓报价详情" width="min(980px, calc(100vw - 28px))" :show-confirm="false"><div v-if="detail" class="detail-grid"><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]"><span>{{item[0]}}</span><strong>{{item[1]}}</strong></div></div><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>
|
||||
<AppDialog v-model="editorOpen" :title="editingId?'编辑海外仓报价':'新增海外仓报价'" width="min(1180px, calc(100vw - 28px))" :confirm-loading="saving" confirm-text="保存报价" @confirm="save"><el-form label-position="top"><div class="form-grid"><el-form-item label="报价名称"><el-input v-model="form.name" placeholder="请输入报价名称" /></el-form-item><el-form-item label="海外仓"><el-select v-model="form.warehouseId" :disabled="!!editingId"><el-option v-for="o in options" :key="o.warehouseId" :label="`${o.warehouseName} · ${o.warehouseCode}`" :value="o.warehouseId"/></el-select></el-form-item><el-form-item label="服务商"><el-input v-model="form.providerName" placeholder="请输入服务商名称" /></el-form-item><el-form-item label="币种"><el-select v-model="form.currency"><el-option label="USD" value="USD"/><el-option label="CNY" value="CNY"/></el-select></el-form-item><el-form-item label="生效日期"><el-date-picker v-model="form.effectiveStart" type="date" value-format="YYYY-MM-DD"/></el-form-item><el-form-item label="失效日期"><el-date-picker v-model="form.effectiveEnd" type="date" value-format="YYYY-MM-DD"/></el-form-item><el-form-item label="报价版本"><el-input v-model="form.versionCode" :disabled="!!editingId" placeholder="请输入版本号" /></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" placeholder="请输入备注(选填)" /></el-form-item></div><h3>仓储作业费用 <el-button @click="form.warehouseFees.push({item:'',billingUnit:'',unitPrice:0,minimumCharge:null,remark:null})">+ 添加费用</el-button></h3><div v-for="(f,i) in form.warehouseFees" :key="i" class="line-grid"><el-input v-model="f.item" placeholder="请输入费用项目"/><el-input v-model="f.billingUnit" placeholder="请输入计费单位"/><el-input-number v-model="f.unitPrice" :min="0" placeholder="请输入单价" /><el-input-number v-model="f.minimumCharge" :min="0" placeholder="请输入最低收费" /><el-button type="danger" plain @click="form.warehouseFees.splice(i,1)">删除</el-button></div><h3>中转派送价格 <el-button @click="form.transitPrices.push({fbaWarehouseCode:'',deliveryMethod:'',estimatedMiles:null,price:0,minimumCharge:null,remark:null})">+ 添加派送价格</el-button></h3><div v-for="(t,i) in form.transitPrices" :key="i" class="line-grid"><el-input v-model="t.fbaWarehouseCode" placeholder="请输入目的 FBA 仓"/><el-input v-model="t.deliveryMethod" placeholder="请输入车型或提货方式"/><el-input-number v-model="t.price" :min="0" placeholder="请输入报价" /><el-input-number v-model="t.minimumCharge" :min="0" placeholder="请输入最低收费" /><el-button type="danger" plain @click="form.transitPrices.splice(i,1)">删除</el-button></div></el-form></AppDialog>
|
||||
<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>
|
||||
<AppDialog class="admin-density-dialog" v-model="editorOpen" :title="editingId?'编辑海外仓报价':'新增海外仓报价'" width="min(1180px, calc(100vw - 28px))" :confirm-loading="saving" confirm-text="保存报价" @confirm="save"><el-form class="density-form" label-position="top"><div class="form-grid"><el-form-item label="报价名称"><el-input v-model="form.name" placeholder="请输入报价名称" /></el-form-item><el-form-item label="海外仓"><el-select v-model="form.warehouseId" :disabled="!!editingId"><el-option v-for="o in options" :key="o.warehouseId" :label="`${o.warehouseName} · ${o.warehouseCode}`" :value="o.warehouseId"/></el-select></el-form-item><el-form-item label="服务商"><el-input v-model="form.providerName" placeholder="请输入服务商名称" /></el-form-item><el-form-item label="币种"><el-select v-model="form.currency"><el-option label="USD" value="USD"/><el-option label="CNY" value="CNY"/></el-select></el-form-item><el-form-item label="生效日期"><el-date-picker v-model="form.effectiveStart" type="date" value-format="YYYY-MM-DD"/></el-form-item><el-form-item label="失效日期"><el-date-picker v-model="form.effectiveEnd" type="date" value-format="YYYY-MM-DD"/></el-form-item><el-form-item label="报价版本"><el-input v-model="form.versionCode" :disabled="!!editingId" placeholder="请输入版本号" /></el-form-item><el-form-item label="备注"><el-input v-model="form.remark" placeholder="请输入备注(选填)" /></el-form-item></div><h3>仓储作业费用 <el-button @click="form.warehouseFees.push({item:'',billingUnit:'',unitPrice:0,minimumCharge:null,remark:null})">+ 添加费用</el-button></h3><div v-for="(f,i) in form.warehouseFees" :key="i" class="line-grid"><el-input v-model="f.item" placeholder="请输入费用项目"/><el-input v-model="f.billingUnit" placeholder="请输入计费单位"/><el-input-number v-model="f.unitPrice" :min="0" placeholder="请输入单价" /><el-input-number v-model="f.minimumCharge" :min="0" placeholder="请输入最低收费" /><el-button data-tone="danger" class="density-action" @click="form.warehouseFees.splice(i,1)">删除</el-button></div><h3>中转派送价格 <el-button @click="form.transitPrices.push({fbaWarehouseCode:'',deliveryMethod:'',estimatedMiles:null,price:0,minimumCharge:null,remark:null})">+ 添加派送价格</el-button></h3><div v-for="(t,i) in form.transitPrices" :key="i" class="line-grid"><el-input v-model="t.fbaWarehouseCode" placeholder="请输入目的 FBA 仓"/><el-input v-model="t.deliveryMethod" placeholder="请输入车型或提货方式"/><el-input-number v-model="t.price" :min="0" placeholder="请输入报价" /><el-input-number v-model="t.minimumCharge" :min="0" placeholder="请输入最低收费" /><el-button data-tone="danger" class="density-action" @click="form.transitPrices.splice(i,1)">删除</el-button></div></el-form></AppDialog>
|
||||
</ListPageLayout></AdminPage></template>
|
||||
<style scoped>.table-panel{min-width:0;overflow:hidden}.form-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px 16px}.line-grid{display:grid;grid-template-columns:2fr 1.2fr 1fr 1fr auto;gap:8px;margin:8px 0}.detail-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--cc-border);border:1px solid var(--cc-border)}.detail-grid>div{padding:12px;background:#fff}.detail-grid span{display:block;color:var(--cc-muted);font-size:12px}.detail-grid strong{display:block;margin-top:4px}h3{margin:18px 0 10px;font-size:14px}</style>
|
||||
<style scoped>.detail-grid.density-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }.table-panel{min-width:0;overflow:hidden}.form-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--admin-space-5) var(--admin-space-6)}.line-grid{display:grid;grid-template-columns:2fr 1.2fr 1fr 1fr auto;gap:var(--admin-space-3);margin:var(--admin-space-3) 0}h3{margin:var(--admin-space-6) 0 var(--admin-space-4);font-size:var(--admin-font-body)}</style>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import { vehicleStatusLabels } from '../presentation'
|
||||
import { ArrowLeft } from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'; import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'; import FormSection from '@/shared/components/FormSection.vue'; import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
@@ -8,5 +11,5 @@ const route=useRoute(),router=useRouter(),detail=ref<VehicleDetail|null>(null),l
|
||||
async function load(){loading.value=true;error.value=null;try{detail.value=await getVehicle(String(route.params.vehicleId||''))}catch(e){detail.value=null;error.value=getApiErrorDetails(e,'车辆详情加载失败')}finally{loading.value=false}}
|
||||
function back(){void router.push({name:'overseas-master-data-vehicles'})} onMounted(load)
|
||||
</script>
|
||||
<template><AdminPage width="comfortable"><DetailPageLayout aria-label="车辆详情"><template #header><div class="heading"><div><h1>车辆详情</h1><p>{{detail?.plateNumber||String(route.params.vehicleId||'—')}} <el-tag v-if="detail">{{detail.status}}</el-tag></p></div><el-button @click="back"><ArrowLeft :size="16"/>返回列表</el-button></div></template><LoadErrorState v-if="error" title="车辆详情加载失败" :message="error.message" @retry="load"/><section v-loading="loading" class="surface-panel detail-surface"><FormSection title="车辆信息"><dl v-if="detail" class="detail-grid"><div v-for="item in [['车牌号',detail.plateNumber],['车型',detail.model],['油耗(L/100km)',detail.fuelConsumptionPer100Km],['当前公里码(km)',detail.mileageKm],['司机 / 外派单位',detail.driverOrDispatchCompany||'—'],['联系电话',detail.phone||'—'],['区域',detail.region==='OVERSEAS'?'海外':'国内'],['车辆归属',detail.ownership==='OWNED'?'自有车辆':'外派车辆'],['状态',detail.status],['更新时间',detail.updatedAt],['备注',detail.remark||'—']]" :key="item[0]"><dt>{{item[0]}}</dt><dd>{{item[1]}}</dd></div></dl></FormSection><FormSection title="派送订单明细"><p class="contract-note">当前车辆主数据接口明确不包含派送履约记录。</p></FormSection><FormSection title="派送更换记录" :bordered="false"><p class="contract-note">当前车辆主数据接口明确不包含车辆更换历史。</p></FormSection></section></DetailPageLayout></AdminPage></template>
|
||||
<style scoped>.heading{display:flex;align-items:center;justify-content:space-between;gap:16px}.heading h1{margin:0;font-size:20px}.heading p{margin:4px 0 0;color:var(--cc-muted)}.detail-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;margin:0;background:var(--cc-border);border:1px solid var(--cc-border)}.detail-grid>div{padding:12px;background:#fff}.detail-grid dt{color:var(--cc-muted);font-size:12px}.detail-grid dd{margin:5px 0 0;overflow-wrap:anywhere}.contract-note{margin:0;padding:16px;color:var(--cc-muted);text-align:center}</style>
|
||||
<template><AdminPage class="admin-density" ><DetailPageLayout class="density-detail-layout surface-panel" aria-label="车辆详情"><template #header><div class="density-detail-header"><div class="density-detail-heading"><el-button link class="density-back" @click="back"><ArrowLeft :size="16" />返回列表</el-button><h1 class="density-detail-title">{{ detail?.plateNumber || '—' }}</h1><StatusBadge v-if="detail" :status="vehicleStatusLabels[detail.status]" /></div></div></template><LoadErrorState v-if="error" title="车辆详情加载失败" :message="error.message" @retry="load"/><section v-loading="loading" class="detail-surface"><FormSection class="density-form-section" title="车辆信息"><dl v-if="detail" class="detail-grid density-fields"><div v-for="item in [['车牌号',detail.plateNumber],['车型',detail.model],['油耗(L/100km)',detail.fuelConsumptionPer100Km],['当前公里码(km)',detail.mileageKm],['司机 / 外派单位',detail.driverOrDispatchCompany||'—'],['联系电话',detail.phone||'—'],['区域',detail.region==='OVERSEAS'?'海外':'国内'],['车辆归属',detail.ownership==='OWNED'?'自有车辆':'外派车辆'],['状态',vehicleStatusLabels[detail.status]],['更新时间',detail.updatedAt],['备注',detail.remark||'—']]" :key="item[0]" :class="{ 'wide-field': ['司机 / 外派单位', '备注'].includes(String(item[0])) }"><dt>{{item[0]}}</dt><dd>{{item[1]}}</dd></div></dl></FormSection><FormSection class="density-form-section" title="派送订单明细"><p class="contract-note">当前车辆主数据接口明确不包含派送履约记录。</p></FormSection><FormSection class="density-form-section" title="派送更换记录" :bordered="false"><p class="contract-note">当前车辆主数据接口明确不包含车辆更换历史。</p></FormSection></section></DetailPageLayout></AdminPage></template>
|
||||
<style scoped>.detail-grid .wide-field { grid-column: span 2; }.contract-note{margin:0;padding:var(--admin-space-6);color:var(--cc-muted);text-align:center}</style>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { vehicleStatusLabels } from '../presentation'
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Download, Eye, Pencil, Plus, Power } from '@lucide/vue'
|
||||
import { Download, Plus } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'; import AppDialog from '@/shared/components/AppDialog.vue'; import DataTable,{type DataTableColumn} from '@/shared/components/DataTable.vue'; import ListPageLayout from '@/shared/components/ListPageLayout.vue'; import QueryToolbar,{type QueryField,type QueryModel} from '@/shared/components/QueryToolbar.vue'; import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
@@ -9,7 +11,7 @@ import { changeVehicleStatus,createVehicle,exportVehicles,getVehicle,listVehicle
|
||||
import { masterDataErrorMessage } from '../apiError'
|
||||
const router=useRouter(),access=useAccessPreviewStore(),rows=ref<VehicleDetail[]>([]),total=ref(0),page=ref(1),pageSize=ref(20),loading=ref(false),saving=ref(false),exporting=ref(false),error=ref<ApiErrorDetails|null>(null),editorOpen=ref(false),editingId=ref(''),statusOpen=ref(false),statusRow=ref<VehicleDetail|null>(null),statusTarget=ref<VehicleStatus>('IN_SERVICE')
|
||||
const ownership=ref<VehicleOwnership>('OWNED'),tabs:[string,VehicleOwnership][]=[['自有车辆','OWNED'],['外派车辆','DISPATCHED']]
|
||||
const filters=ref<QueryModel>({keyword:'',region:'',status:''});const fields:QueryField[]=[{key:'keyword',type:'input',label:'车辆关键词',placeholder:'车牌 / 司机 / 车型',width:220},{key:'region',type:'select',label:'区域',placeholder:'全部区域',width:140,options:[{label:'国内',value:'DOMESTIC'},{label:'海外',value:'OVERSEAS'}]},{key:'status',type:'select',label:'状态',placeholder:'全部状态',width:140,options:[['在役','IN_SERVICE'],['外派中','DISPATCHED'],['维修中','MAINTENANCE'],['停用','INACTIVE'],['已归还','RETURNED']].map(([label,value])=>({label,value}))}]
|
||||
const filters=ref<QueryModel>({keyword:'',region:'',status:''});const fields:QueryField[]=[{key:'keyword',type:'input',label:'车辆关键词',placeholder:'车牌 / 司机 / 车型',width:220},{key:'region',type:'select',label:'区域',placeholder:'全部区域',width:140,options:[{label:'国内',value:'DOMESTIC'},{label:'海外',value:'OVERSEAS'}]},{key:'status',type:'select',label:'状态',placeholder:'全部状态',width:140,options:Object.entries(vehicleStatusLabels).map(([value,label])=>({label,value}))}]
|
||||
const columns:DataTableColumn[]=[{key:'actions',label:'操作',width:250,fixed:'left',slotName:'actions'},{key:'plateNumber',prop:'plateNumber',label:'车牌',minWidth:130},{key:'model',prop:'model',label:'车型',minWidth:180},{key:'fuelConsumptionPer100Km',prop:'fuelConsumptionPer100Km',label:'油耗(L/100km)',minWidth:130,align:'right'},{key:'mileageKm',prop:'mileageKm',label:'当前公里码(km)',minWidth:140,align:'right'},{key:'driverOrDispatchCompany',prop:'driverOrDispatchCompany',label:'司机 / 外派单位',minWidth:160},{key:'phone',prop:'phone',label:'联系电话',minWidth:145},{key:'status',label:'状态',width:100,slotName:'status'},{key:'region',label:'区域',width:100,slotName:'region'}]
|
||||
const form=reactive({plateNumber:'',model:'',fuelConsumptionPer100Km:0,mileageKm:0,driverOrDispatchCompany:'',phone:'',region:'OVERSEAS' as VehicleRegion,ownership:'OWNED' as VehicleOwnership,status:'IN_SERVICE' as VehicleStatus,remark:'',recordVersion:0})
|
||||
function params(){return{page:page.value,size:pageSize.value,keyword:String(filters.value.keyword||'')||undefined,ownership:ownership.value,region:(filters.value.region||undefined) as VehicleRegion|undefined,status:(filters.value.status||undefined) as VehicleStatus|undefined}}
|
||||
@@ -22,6 +24,6 @@ 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><ListPageLayout aria-label="车辆列表"><template #toolbar><div class="query-panel"><div class="status-tabs" role="tablist" aria-label="车辆归属"><button v-for="tab in tabs" :key="tab[1]" role="tab" :aria-selected="ownership===tab[1]" :class="['status-tab',{'is-active':ownership===tab[1]}]" @click="selectOwnership(tab[1])">{{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}"><el-button link type="primary" @click="router.push({name:'overseas-master-data-vehicle-detail',params:{vehicleId:(row as VehicleDetail).id}})"><Eye :size="15"/>查看</el-button><el-button v-if="access.can(permission.vehicleEdit)" link type="primary" @click="openEdit(row as VehicleDetail)"><Pencil :size="15"/>编辑</el-button><el-button v-if="access.can(permission.vehicleStatus)" link type="primary" @click="openStatus(row as VehicleDetail)"><Power :size="15"/>状态</el-button></template><template #status="{row}"><el-tag>{{(row as VehicleDetail).status}}</el-tag></template><template #region="{row}">{{(row as VehicleDetail).region==='OVERSEAS'?'海外':'国内'}}</template></DataTable></section>
|
||||
<AppDialog v-model="editorOpen" :title="editingId?'编辑车辆':'添加车辆'" width="min(760px, calc(100vw - 28px))" :confirm-loading="saving" @confirm="save"><el-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="s" :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 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="s" :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:8px;background:#fff}.status-tabs{display:flex;border-bottom:1px solid var(--cc-border);background:#f9fafb}.status-tab{min-height:44px;padding:0 18px;border:0;background:transparent;color:var(--cc-muted)}.status-tab.is-active{border-bottom:3px solid var(--cc-primary);color:var(--cc-primary);font-weight:600}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 16px}</style>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { VehicleStatus } from './types'
|
||||
|
||||
export const vehicleStatusLabels: Record<VehicleStatus, string> = {
|
||||
IN_SERVICE: '在役',
|
||||
DISPATCHED: '外派中',
|
||||
MAINTENANCE: '维修中',
|
||||
INACTIVE: '停用',
|
||||
RETURNED: '已归还',
|
||||
}
|
||||
Reference in New Issue
Block a user