提交
This commit is contained in:
1 parent
725774ef0c
commit
c3be9d1dce
176 files changed
+41925
-6342
No files matched your search
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="page-container db-page">
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">仪表盘</h2>
|
||||
</div>
|
||||
|
||||
<div class="db-dashboard-body" v-loading="loading">
|
||||
<div class="db-dashboard-stats">
|
||||
<div v-for="stat in statsList" :key="stat.title" class="db-stat-card">
|
||||
<p class="db-stat-title">{{ stat.title }}</p>
|
||||
<p class="db-stat-value">
|
||||
{{ stat.value }}<span class="db-stat-suffix">{{ stat.suffix }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" class="db-dashboard-charts">
|
||||
<el-col :xs="24" :lg="12">
|
||||
<DualBarChart title="今日入库量(按货物,t)" :data="inboundByCargo" />
|
||||
</el-col>
|
||||
<el-col :xs="24" :lg="12">
|
||||
<DualBarChart title="今日出库量(按货物,t)" :data="outboundByCargo" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16" class="db-dashboard-charts">
|
||||
<el-col :xs="24" :lg="12">
|
||||
<DualBarChart title="今日入库量(按往来单位+货物,t)" :data="inboundByPartnerCargo" />
|
||||
</el-col>
|
||||
<el-col :xs="24" :lg="12">
|
||||
<DualBarChart title="今日出库量(按往来单位+货物,t)" :data="outboundByPartnerCargo" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* UI 模式:统计仪表盘(Fresh Mint 面板 + 图表区,无独立 P 编号)
|
||||
* 参考:/#/demo/console · src/views/dashboard/console/index.vue
|
||||
*/
|
||||
import DualBarChart from '@/components/business/db/DualBarChart.vue'
|
||||
import {
|
||||
dashboardApi,
|
||||
type DashboardCargoStat,
|
||||
type DashboardPartnerCargoStat
|
||||
} from '@/api/db/dashboard'
|
||||
|
||||
defineOptions({ name: 'DbDashboard' })
|
||||
|
||||
const loading = ref(false)
|
||||
const inboundByCargo = ref<DashboardCargoStat[]>([])
|
||||
const outboundByCargo = ref<DashboardCargoStat[]>([])
|
||||
const inboundByPartnerCargo = ref<DashboardPartnerCargoStat[]>([])
|
||||
const outboundByPartnerCargo = ref<DashboardPartnerCargoStat[]>([])
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const [inCargo, outCargo, inPartner, outPartner] = await Promise.all([
|
||||
dashboardApi.getInboundByCargo(),
|
||||
dashboardApi.getOutboundByCargo(),
|
||||
dashboardApi.getInboundByPartnerCargo(),
|
||||
dashboardApi.getOutboundByPartnerCargo()
|
||||
])
|
||||
inboundByCargo.value = inCargo
|
||||
outboundByCargo.value = outCargo
|
||||
inboundByPartnerCargo.value = inPartner
|
||||
outboundByPartnerCargo.value = outPartner
|
||||
} catch (error) {
|
||||
console.error('获取仪表盘数据失败', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
const todayInboundVehicles = computed(() => {
|
||||
const cargoSet = new Set<string>()
|
||||
inboundByCargo.value.forEach((item) => {
|
||||
if (item.planned > 0) cargoSet.add(item.label)
|
||||
})
|
||||
return cargoSet.size
|
||||
})
|
||||
|
||||
const todayOutboundVehicles = computed(() => {
|
||||
const cargoSet = new Set<string>()
|
||||
outboundByCargo.value.forEach((item) => {
|
||||
if (item.planned > 0) cargoSet.add(item.label)
|
||||
})
|
||||
return cargoSet.size
|
||||
})
|
||||
|
||||
const todayInboundTotal = computed(() => {
|
||||
const planned = inboundByCargo.value.reduce((sum, item) => sum + item.planned, 0)
|
||||
const actual = inboundByCargo.value.reduce((sum, item) => sum + item.actual, 0)
|
||||
return { actual, planned }
|
||||
})
|
||||
|
||||
const todayOutboundTotal = computed(() => {
|
||||
const planned = outboundByCargo.value.reduce((sum, item) => sum + item.planned, 0)
|
||||
const actual = outboundByCargo.value.reduce((sum, item) => sum + item.actual, 0)
|
||||
return { actual, planned }
|
||||
})
|
||||
|
||||
const statsList = computed(() => [
|
||||
{ title: '今日入库', value: todayInboundVehicles.value, suffix: '车' },
|
||||
{
|
||||
title: '今日入库总量',
|
||||
value: `${todayInboundTotal.value.actual.toFixed(2)} / ${todayInboundTotal.value.planned.toFixed(2)}`,
|
||||
suffix: '吨'
|
||||
},
|
||||
{ title: '今日出库', value: todayOutboundVehicles.value, suffix: '车' },
|
||||
{
|
||||
title: '今日出库总量',
|
||||
value: `${todayOutboundTotal.value.actual.toFixed(2)} / ${todayOutboundTotal.value.planned.toFixed(2)}`,
|
||||
suffix: '吨'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,950 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">计划管理</h2>
|
||||
</div>
|
||||
|
||||
<div class="filter-block">
|
||||
<el-form :model="filters" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型">
|
||||
<el-select
|
||||
v-model="filters.businessType"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in businessTypeOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行状态">
|
||||
<el-select
|
||||
v-model="filters.executionStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in executionStatusOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="filter-actions-start" />
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" :loading="loading" @click="handleResetFilters">
|
||||
<el-icon><RefreshLeft /></el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="loading" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row db-list-toolbar-row">
|
||||
<div class="list-toolbar-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
<div
|
||||
class="filter-date-shortcuts list-toolbar-date-shortcuts"
|
||||
role="group"
|
||||
aria-label="日期快捷筛选"
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
class="btn-ghost date-shortcut-btn"
|
||||
:class="{ 'is-active': activeDateShortcut === 'all' }"
|
||||
@click="handleDateShortcut('all')"
|
||||
>
|
||||
全部
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
class="btn-ghost date-shortcut-btn"
|
||||
:class="{ 'is-active': activeDateShortcut === 'yesterday' }"
|
||||
@click="handleDateShortcut('yesterday')"
|
||||
>
|
||||
昨日
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
class="btn-ghost date-shortcut-btn"
|
||||
:class="{ 'is-active': activeDateShortcut === 'today' }"
|
||||
@click="handleDateShortcut('today')"
|
||||
>
|
||||
今日
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
class="btn-ghost date-shortcut-btn"
|
||||
:class="{ 'is-active': activeDateShortcut === 'tomorrow' }"
|
||||
@click="handleDateShortcut('tomorrow')"
|
||||
>
|
||||
明日
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
class="btn-ghost date-shortcut-btn"
|
||||
:class="{ 'is-active': activeDateShortcut === 'week' }"
|
||||
@click="handleDateShortcut('week')"
|
||||
>
|
||||
本周
|
||||
</el-button>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
class="db-toolbar-quick-search-input"
|
||||
placeholder="往来单位 / 计划号 / 产品"
|
||||
clearable
|
||||
@keyup.enter="handleQuickSearch"
|
||||
@clear="handleQuickSearchClear"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="db-quick-search-icon" @click="handleQuickSearch"><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增计划
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="exportLoading" @click="handleExport">
|
||||
<el-icon><Download /></el-icon>
|
||||
导出 Excel
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="loading" @click="loadData">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
show-summary
|
||||
:summary-method="getSummary"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="planNo"
|
||||
label="计划号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="businessType"
|
||||
label="业务类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.businessType === '入库' ? 'success' : 'warning'" size="small">
|
||||
{{ row.businessType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="customerName"
|
||||
label="往来单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="startDate"
|
||||
label="起始日期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="endDate"
|
||||
label="截止日期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="product"
|
||||
label="产品"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specification"
|
||||
label="规格"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="计划量(吨)"
|
||||
prop="plannedQuantity"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="已执行量(吨)" min-width="130" align="right" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ ((row.executedQuantity || 0) / 1000).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="executionStatus"
|
||||
label="执行状态"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getExecutionStatusType(getCorrectExecutionStatus(row))" size="small">
|
||||
{{ getCorrectExecutionStatus(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remarks"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="自动生成" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.autoGenerateEnabled"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="(val) => handleToggleAutoGenerate(row, Number(val))"
|
||||
/>
|
||||
<div v-if="row.autoGenerateEndMonth" class="db-auto-end-hint">
|
||||
结束:{{ row.autoGenerateEndMonth }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="360"
|
||||
align="center"
|
||||
header-align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="db-op-actions">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleGenerateInventory(row)">
|
||||
<el-icon><DocumentAdd /></el-icon>
|
||||
生成出入库单
|
||||
</el-button>
|
||||
<el-button class="btn-op-item danger" @click="handleDelete(row.id)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
v-show="total > 0"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadData"
|
||||
@size-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
v-model="modalOpen"
|
||||
:title="editingPlan ? '修改计划' : '新增计划'"
|
||||
width="760px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
class="dialog-form-layout"
|
||||
modal-class="order-module-dialog-mask"
|
||||
@close="handleModalClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-position="top"
|
||||
class="form-grid-2"
|
||||
scroll-to-error
|
||||
>
|
||||
<el-form-item label="计划号" prop="planNo">
|
||||
<el-input v-model="formData.planNo" placeholder="系统自动生成" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型" prop="businessType">
|
||||
<el-radio-group v-model="formData.businessType">
|
||||
<el-radio value="入库">入库</el-radio>
|
||||
<el-radio value="出库">出库</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="自动按月生成">
|
||||
<el-switch
|
||||
v-model="formData.autoGenerateEnabled"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束月份">
|
||||
<el-date-picker
|
||||
v-model="formData.autoGenerateEndMonth"
|
||||
type="month"
|
||||
placeholder="不选表示一直生成"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM"
|
||||
value-format="YYYY-MM"
|
||||
:disabled="!formData.autoGenerateEnabled"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="往来单位" prop="customerName">
|
||||
<DbCrmCustomerSelect v-model="formData.customerName" placeholder="请输入往来单位搜索" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品" prop="productId">
|
||||
<DbCrmProductSelect
|
||||
v-model:product-id="formData.productId"
|
||||
v-model:product-name="formData.product"
|
||||
v-model:specification="formData.specification"
|
||||
placeholder="请搜索并选择 CRM 产品"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格">
|
||||
<el-input
|
||||
v-model="formData.specification"
|
||||
readonly
|
||||
placeholder="选择产品后自动带出"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划开始时间" prop="startDate">
|
||||
<el-date-picker
|
||||
v-model="formData.startDate"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划结束时间" prop="endDate">
|
||||
<el-date-picker
|
||||
v-model="formData.endDate"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划量(吨)" prop="plannedQuantity">
|
||||
<el-input-number
|
||||
v-model="formData.plannedQuantity"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入计划吨位"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="formData.remarks"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="填写备注信息"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleModalClose">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<DbPlanRelatedViewDialog
|
||||
v-model:visible="relatedModalOpen"
|
||||
:loading="loadingRelated"
|
||||
:data="relatedData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* UI 模式 P02 · 复杂业务列表
|
||||
* 参考:/#/crm/quote · src/views/crm/quote/index.vue
|
||||
*/
|
||||
import type { TableColumnCtx } from 'element-plus'
|
||||
import {
|
||||
Check,
|
||||
Close,
|
||||
Delete,
|
||||
DocumentAdd,
|
||||
Download,
|
||||
Edit,
|
||||
Plus,
|
||||
Refresh,
|
||||
RefreshLeft,
|
||||
Search,
|
||||
View
|
||||
} from '@element-plus/icons-vue'
|
||||
import DbCrmCustomerSelect from '@/components/business/db/DbCrmCustomerSelect.vue'
|
||||
import DbCrmProductSelect from '@/components/business/db/DbCrmProductSelect.vue'
|
||||
import DbPlanRelatedViewDialog from '@/components/business/db/DbPlanRelatedViewDialog.vue'
|
||||
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
|
||||
import { $modal } from '@/utils/modal'
|
||||
import {
|
||||
planApi,
|
||||
type Plan,
|
||||
type PlanCreate,
|
||||
type PlanListParams,
|
||||
type PlanRelatedData,
|
||||
type PlanUpdate
|
||||
} from '@/api/db/plans'
|
||||
|
||||
defineOptions({ name: 'DbPlans' })
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const tableData = ref<Plan[]>([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const searchKeyword = ref('')
|
||||
const selectedRows = ref<Plan[]>([])
|
||||
const activeDateShortcut = ref<'all' | 'today' | 'tomorrow' | 'yesterday' | 'week' | ''>('')
|
||||
|
||||
const filters = reactive({
|
||||
businessType: '' as '' | '入库' | '出库',
|
||||
executionStatus: ''
|
||||
})
|
||||
|
||||
const businessTypeOptions: Array<'入库' | '出库'> = ['入库', '出库']
|
||||
const executionStatusOptions = ['待入库', '入库中', '已入库', '待出库', '出库中', '已出库']
|
||||
|
||||
const pad2 = (n: number) => String(n).padStart(2, '0')
|
||||
const formatYmd = (d: Date) =>
|
||||
`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
|
||||
|
||||
/** 默认筛选:当前月 1 日 ~ 当前月最后一天(对齐原地磅 art-design-pro) */
|
||||
const getCurrentMonthDateRange = (): [string, string] => {
|
||||
const now = new Date()
|
||||
return [
|
||||
formatYmd(new Date(now.getFullYear(), now.getMonth(), 1)),
|
||||
formatYmd(new Date(now.getFullYear(), now.getMonth() + 1, 0))
|
||||
]
|
||||
}
|
||||
|
||||
const dateRange = ref<[string, string] | []>(getCurrentMonthDateRange())
|
||||
|
||||
const modalOpen = ref(false)
|
||||
const editingPlan = ref<Plan | null>(null)
|
||||
const formRef = ref()
|
||||
const formData = reactive<
|
||||
PlanCreate & {
|
||||
product: string
|
||||
specification: string
|
||||
}
|
||||
>({
|
||||
planNo: '',
|
||||
planDate: formatYmd(new Date()),
|
||||
startDate: formatYmd(new Date()),
|
||||
endDate: '',
|
||||
businessType: '入库',
|
||||
productId: '',
|
||||
product: '',
|
||||
specification: '',
|
||||
plannedQuantity: 0,
|
||||
vehicleNo: '',
|
||||
customerName: '',
|
||||
remarks: '',
|
||||
autoGenerateEnabled: 0,
|
||||
autoGenerateEndMonth: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
businessType: [{ required: true, message: '请选择业务类型', trigger: 'change' }],
|
||||
productId: [{ required: true, message: '请选择 CRM 产品', trigger: 'change' }],
|
||||
plannedQuantity: [{ required: true, message: '请输入计划量', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const buildListQueryParams = (): PlanListParams => {
|
||||
const params: PlanListParams = {}
|
||||
const keyword = searchKeyword.value.trim()
|
||||
|
||||
if (keyword) params.q = keyword
|
||||
if (dateRange.value?.[0] && dateRange.value?.[1]) {
|
||||
params.startDateFrom = dateRange.value[0]
|
||||
params.startDateTo = dateRange.value[1]
|
||||
}
|
||||
if (filters.businessType) params.businessType = filters.businessType
|
||||
if (filters.executionStatus) params.executionStatus = filters.executionStatus
|
||||
return params
|
||||
}
|
||||
|
||||
const fetchFilteredPlans = async () => {
|
||||
return planApi.list(buildListQueryParams())
|
||||
}
|
||||
|
||||
const getCorrectExecutionStatus = (row: Plan): string => {
|
||||
if (!row.executionStatus) {
|
||||
return row.businessType === '入库' ? '待入库' : '待出库'
|
||||
}
|
||||
if (row.businessType === '入库') {
|
||||
if (
|
||||
row.executionStatus === '待出库' ||
|
||||
row.executionStatus === '出库中' ||
|
||||
row.executionStatus === '已出库'
|
||||
) {
|
||||
return (row.executedQuantity || 0) > 0 ? '入库中' : '待入库'
|
||||
}
|
||||
} else if (row.businessType === '出库') {
|
||||
if (
|
||||
row.executionStatus === '待入库' ||
|
||||
row.executionStatus === '入库中' ||
|
||||
row.executionStatus === '已入库'
|
||||
) {
|
||||
return (row.executedQuantity || 0) > 0 ? '出库中' : '待出库'
|
||||
}
|
||||
}
|
||||
return row.executionStatus
|
||||
}
|
||||
|
||||
const getExecutionStatusType = (
|
||||
status: string | undefined
|
||||
): 'success' | 'warning' | 'info' | 'primary' | 'danger' => {
|
||||
if (!status) return 'info'
|
||||
if (status === '已入库' || status === '已出库') return 'success'
|
||||
if (status === '入库中' || status === '出库中') return 'info'
|
||||
if (status === '待入库' || status === '待出库') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
const getSummary = (param: { columns: TableColumnCtx<Plan>[]; data: Plan[] }) => {
|
||||
const { columns } = param
|
||||
const sums: string[] = []
|
||||
|
||||
columns.forEach((column, index) => {
|
||||
if (column.type === 'selection') {
|
||||
sums[index] = ''
|
||||
return
|
||||
}
|
||||
if (column.type === 'index') {
|
||||
sums[index] = '合计'
|
||||
return
|
||||
}
|
||||
if (column.property === 'plannedQuantity') {
|
||||
const totalPlanned = tableData.value.reduce(
|
||||
(sum, row) => sum + (Number(row.plannedQuantity) || 0),
|
||||
0
|
||||
)
|
||||
sums[index] = totalPlanned.toString()
|
||||
return
|
||||
}
|
||||
if (column.label === '已执行量(吨)') {
|
||||
const totalExecuted = tableData.value.reduce(
|
||||
(sum, row) => sum + (row.executedQuantity || 0) / 1000,
|
||||
0
|
||||
)
|
||||
sums[index] = totalExecuted.toFixed(2)
|
||||
return
|
||||
}
|
||||
sums[index] = ''
|
||||
})
|
||||
|
||||
return sums
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const records = await fetchFilteredPlans()
|
||||
total.value = records.length
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
tableData.value = records.slice(start, end)
|
||||
} catch (error) {
|
||||
console.error('加载计划列表失败', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleQuickSearch = () => {
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleQuickSearchClear = () => {
|
||||
searchKeyword.value = ''
|
||||
handleQuickSearch()
|
||||
}
|
||||
|
||||
const handleSelectionChange = (selection: Plan[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
const handleResetFilters = () => {
|
||||
searchKeyword.value = ''
|
||||
filters.businessType = ''
|
||||
filters.executionStatus = ''
|
||||
dateRange.value = getCurrentMonthDateRange()
|
||||
activeDateShortcut.value = ''
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleDateShortcut = (type: 'all' | 'today' | 'tomorrow' | 'yesterday' | 'week') => {
|
||||
activeDateShortcut.value = type
|
||||
const today = new Date()
|
||||
|
||||
if (type === 'all') {
|
||||
dateRange.value = []
|
||||
} else if (type === 'today') {
|
||||
const d = formatYmd(today)
|
||||
dateRange.value = [d, d]
|
||||
} else if (type === 'tomorrow') {
|
||||
const t = new Date(today)
|
||||
t.setDate(t.getDate() + 1)
|
||||
const d = formatYmd(t)
|
||||
dateRange.value = [d, d]
|
||||
} else if (type === 'yesterday') {
|
||||
const y = new Date(today)
|
||||
y.setDate(y.getDate() - 1)
|
||||
const d = formatYmd(y)
|
||||
dateRange.value = [d, d]
|
||||
} else if (type === 'week') {
|
||||
const weekday = today.getDay() === 0 ? 7 : today.getDay()
|
||||
const monday = new Date(today)
|
||||
monday.setDate(today.getDate() - (weekday - 1))
|
||||
const sunday = new Date(monday)
|
||||
sunday.setDate(monday.getDate() + 6)
|
||||
dateRange.value = [formatYmd(monday), formatYmd(sunday)]
|
||||
}
|
||||
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
activeDateShortcut.value = ''
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
const resetFormData = () => {
|
||||
Object.assign(formData, {
|
||||
planNo: '',
|
||||
planDate: formatYmd(new Date()),
|
||||
startDate: formatYmd(new Date()),
|
||||
endDate: '',
|
||||
businessType: '入库',
|
||||
productId: '',
|
||||
product: '',
|
||||
specification: '',
|
||||
plannedQuantity: 0,
|
||||
vehicleNo: '',
|
||||
customerName: '',
|
||||
remarks: '',
|
||||
autoGenerateEnabled: 0,
|
||||
autoGenerateEndMonth: ''
|
||||
})
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
editingPlan.value = null
|
||||
resetFormData()
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record: Plan) => {
|
||||
editingPlan.value = record
|
||||
Object.assign(formData, {
|
||||
planNo: record.planNo || '',
|
||||
planDate: record.planDate,
|
||||
startDate: record.startDate || '',
|
||||
endDate: record.endDate || '',
|
||||
businessType: record.businessType,
|
||||
productId: record.productId || '',
|
||||
product: record.product,
|
||||
specification: record.specification || '',
|
||||
plannedQuantity: record.plannedQuantity,
|
||||
vehicleNo: record.vehicleNo || '',
|
||||
customerName: record.customerName || '',
|
||||
remarks: record.remarks || '',
|
||||
autoGenerateEnabled: record.autoGenerateEnabled ?? 0,
|
||||
autoGenerateEndMonth: record.autoGenerateEndMonth || ''
|
||||
})
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
const relatedData = ref<PlanRelatedData>({
|
||||
inventories: [],
|
||||
weighings: []
|
||||
})
|
||||
const relatedModalOpen = ref(false)
|
||||
const loadingRelated = ref(false)
|
||||
|
||||
const handleView = async (record: Plan) => {
|
||||
loadingRelated.value = true
|
||||
try {
|
||||
relatedData.value = await planApi.getRelated(record.id)
|
||||
relatedModalOpen.value = true
|
||||
} catch (error) {
|
||||
console.error('加载关联数据失败', error)
|
||||
} finally {
|
||||
loadingRelated.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await $modal.confirm('确定要删除这个计划吗?')
|
||||
await planApi.delete(id)
|
||||
$modal.msgSuccess('删除成功')
|
||||
loadData()
|
||||
} catch {
|
||||
// 用户取消或请求失败
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateInventory = (record: Plan) => {
|
||||
router.push({
|
||||
path: '/db/inventory',
|
||||
query: {
|
||||
planNo: record.planNo || '',
|
||||
product: record.product || '',
|
||||
productId: record.productId || '',
|
||||
specification: record.specification || '',
|
||||
businessType: record.businessType || '',
|
||||
customerName: record.customerName || '',
|
||||
purchasePlanId: record.id,
|
||||
from: 'plan'
|
||||
}
|
||||
})
|
||||
$modal.msgSuccess('已跳转到出入库页面,请填写详细信息')
|
||||
}
|
||||
|
||||
const handleToggleAutoGenerate = async (record: Plan, value: number) => {
|
||||
const normalizedValue = value === 1 ? 1 : 0
|
||||
try {
|
||||
await planApi.update(record.id, {
|
||||
autoGenerateEnabled: normalizedValue,
|
||||
autoGenerateEndMonth: record.autoGenerateEndMonth
|
||||
})
|
||||
$modal.msgSuccess(normalizedValue === 1 ? '已开启自动生成' : '已关闭自动生成')
|
||||
loadData()
|
||||
} catch (error) {
|
||||
console.error('更新自动生成失败', error)
|
||||
record.autoGenerateEnabled = normalizedValue === 1 ? 0 : 1
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
saving.value = true
|
||||
try {
|
||||
const payload: PlanCreate | PlanUpdate = editingPlan.value
|
||||
? {
|
||||
planNo: formData.planNo || undefined,
|
||||
planDate: formData.planDate,
|
||||
startDate: formData.startDate || undefined,
|
||||
endDate: formData.endDate || undefined,
|
||||
businessType: formData.businessType,
|
||||
productId: formData.productId,
|
||||
plannedQuantity: formData.plannedQuantity,
|
||||
vehicleNo: formData.vehicleNo || undefined,
|
||||
customerName: formData.customerName || undefined,
|
||||
remarks: formData.remarks || undefined,
|
||||
autoGenerateEnabled: formData.autoGenerateEnabled,
|
||||
autoGenerateEndMonth: formData.autoGenerateEndMonth || undefined
|
||||
}
|
||||
: {
|
||||
planNo: formData.planNo || undefined,
|
||||
planDate: formData.planDate,
|
||||
startDate: formData.startDate || undefined,
|
||||
endDate: formData.endDate || undefined,
|
||||
businessType: formData.businessType,
|
||||
productId: formData.productId,
|
||||
plannedQuantity: formData.plannedQuantity,
|
||||
vehicleNo: formData.vehicleNo || undefined,
|
||||
customerName: formData.customerName || undefined,
|
||||
remarks: formData.remarks || undefined,
|
||||
autoGenerateEnabled: formData.autoGenerateEnabled,
|
||||
autoGenerateEndMonth: formData.autoGenerateEndMonth || undefined
|
||||
}
|
||||
|
||||
if (editingPlan.value) {
|
||||
await planApi.update(editingPlan.value.id, payload as PlanUpdate)
|
||||
$modal.msgSuccess('更新成功')
|
||||
} else {
|
||||
await planApi.create(payload as PlanCreate)
|
||||
$modal.msgSuccess('创建成功')
|
||||
}
|
||||
modalOpen.value = false
|
||||
loadData()
|
||||
} catch (error) {
|
||||
console.error('保存计划失败', error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalClose = () => {
|
||||
modalOpen.value = false
|
||||
editingPlan.value = null
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const records = await fetchFilteredPlans()
|
||||
if (records.length === 0) {
|
||||
$modal.msgWarning('没有数据可导出')
|
||||
return
|
||||
}
|
||||
|
||||
const headers = [
|
||||
'计划号',
|
||||
'往来单位',
|
||||
'计划日期',
|
||||
'起始日期',
|
||||
'截止日期',
|
||||
'产品',
|
||||
'规格',
|
||||
'车牌号',
|
||||
'计划量',
|
||||
'已执行量',
|
||||
'执行状态',
|
||||
'备注'
|
||||
]
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...records.map((r) =>
|
||||
[
|
||||
r.planNo,
|
||||
r.customerName,
|
||||
r.planDate,
|
||||
r.startDate,
|
||||
r.endDate,
|
||||
r.product,
|
||||
r.specification,
|
||||
r.vehicleNo,
|
||||
r.plannedQuantity,
|
||||
r.executedQuantity || 0,
|
||||
r.executionStatus,
|
||||
r.remarks || ''
|
||||
].join(',')
|
||||
)
|
||||
].join('\n')
|
||||
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
const now = new Date()
|
||||
const stamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}_${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`
|
||||
a.download = `计划列表_${stamp}.csv`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
$modal.msgSuccess('导出成功')
|
||||
} catch (error) {
|
||||
console.error('导出失败', error)
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
useDbInstantSearch(
|
||||
() => ({
|
||||
q: searchKeyword.value,
|
||||
dateRange: dateRange.value,
|
||||
businessType: filters.businessType,
|
||||
executionStatus: filters.executionStatus
|
||||
}),
|
||||
handleSearch
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (!dateRange.value[0] || !dateRange.value[1]) {
|
||||
dateRange.value = getCurrentMonthDateRange()
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">车牌管理</h2>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row db-list-toolbar-row">
|
||||
<div class="list-toolbar-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
class="db-toolbar-quick-search-input"
|
||||
placeholder="车牌号 / 备注"
|
||||
clearable
|
||||
@keyup.enter="handleQuickSearch"
|
||||
@clear="handleQuickSearchClear"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="db-quick-search-icon" @click="handleQuickSearch"><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增车牌号
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="loading" @click="loadData">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="vehicleNo"
|
||||
label="车牌号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remarks"
|
||||
label="备注"
|
||||
min-width="240"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="200"
|
||||
align="center"
|
||||
header-align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="db-op-actions">
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item danger" @click="handleDelete(row.id)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
v-show="total > 0"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="applyPagination"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
v-model="modalOpen"
|
||||
:title="editingVehicle ? '修改车牌号' : '新增车牌号'"
|
||||
width="520px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
class="dialog-form-layout"
|
||||
modal-class="order-module-dialog-mask"
|
||||
@close="handleModalClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-position="top"
|
||||
class="form-grid-2"
|
||||
scroll-to-error
|
||||
>
|
||||
<el-form-item label="车牌号" prop="vehicleNo">
|
||||
<el-input v-model="formData.vehicleNo" placeholder="请输入车牌号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks" class="form-col-span-2">
|
||||
<el-input v-model="formData.remarks" type="textarea" :rows="3" placeholder="选填" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleModalClose">
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* UI 模式 P01 · 标准列表 CRUD
|
||||
* 参考:/#/crm/quote · src/views/crm/quote/index.vue
|
||||
*/
|
||||
import { Check, Close, Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { vehicleApi, type Vehicle, type VehicleCreate } from '@/api/db/vehicles'
|
||||
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
|
||||
import { $modal } from '@/utils/modal'
|
||||
|
||||
defineOptions({ name: 'DbVehicles' })
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const allRecords = ref<Vehicle[]>([])
|
||||
const tableData = ref<Vehicle[]>([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const searchKeyword = ref('')
|
||||
const selectedRows = ref<Vehicle[]>([])
|
||||
|
||||
const modalOpen = ref(false)
|
||||
const editingVehicle = ref<Vehicle | null>(null)
|
||||
const formRef = ref()
|
||||
const formData = reactive<VehicleCreate>({
|
||||
vehicleNo: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
vehicleNo: [{ required: true, message: '请输入车牌号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const applyPagination = () => {
|
||||
total.value = allRecords.value.length
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
tableData.value = allRecords.value.slice(start, end)
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const keyword = searchKeyword.value.trim()
|
||||
allRecords.value = await vehicleApi.list(keyword || undefined)
|
||||
const maxPage = Math.ceil(allRecords.value.length / pageSize.value) || 1
|
||||
if (currentPage.value > maxPage) {
|
||||
currentPage.value = 1
|
||||
}
|
||||
applyPagination()
|
||||
} catch (error) {
|
||||
console.error('加载车牌列表失败', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuickSearch = () => {
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleQuickSearchClear = () => {
|
||||
searchKeyword.value = ''
|
||||
handleQuickSearch()
|
||||
}
|
||||
|
||||
const handleSelectionChange = (selection: Vehicle[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1
|
||||
applyPagination()
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
editingVehicle.value = null
|
||||
Object.assign(formData, { vehicleNo: '', remarks: '' })
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record: Vehicle) => {
|
||||
editingVehicle.value = record
|
||||
Object.assign(formData, {
|
||||
vehicleNo: record.vehicleNo,
|
||||
remarks: record.remarks || ''
|
||||
})
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await $modal.confirm('确定要删除该车牌号吗?')
|
||||
await vehicleApi.delete(id)
|
||||
$modal.msgSuccess('删除成功')
|
||||
loadData()
|
||||
} catch {
|
||||
// 用户取消或请求失败
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
saving.value = true
|
||||
try {
|
||||
const payload: VehicleCreate = {
|
||||
vehicleNo: formData.vehicleNo.trim(),
|
||||
remarks: formData.remarks?.trim() || null
|
||||
}
|
||||
if (editingVehicle.value) {
|
||||
await vehicleApi.update(editingVehicle.value.id, payload)
|
||||
$modal.msgSuccess('修改成功')
|
||||
} else {
|
||||
await vehicleApi.create(payload)
|
||||
$modal.msgSuccess('新增成功')
|
||||
}
|
||||
modalOpen.value = false
|
||||
loadData()
|
||||
} catch (error) {
|
||||
console.error('保存车牌失败', error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleModalClose = () => {
|
||||
modalOpen.value = false
|
||||
editingVehicle.value = null
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
useDbInstantSearch(() => searchKeyword.value, handleQuickSearch)
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user