Files
shanxia2.0/web-admin/src/views/db/weighing/index.vue
T
2026-06-21 12:41:39 +08:00

1117 lines
37 KiB
Vue

<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="timeRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
@change="handleTimeRangeChange"
/>
</el-form-item>
<el-form-item label="出入库单号">
<el-input v-model="filters.batchNo" placeholder="出入库单号" clearable @keyup.enter="handleSearch" />
</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-input v-model="filters.supplier" placeholder="供货单位" clearable @keyup.enter="handleSearch" />
</el-form-item>
</div>
<div v-show="filterExpanded" id="db-weigh-filter-extra" class="filter-extra">
<div class="filter-grid">
<el-form-item label="收货单位">
<el-input v-model="filters.consignee" placeholder="收货单位" clearable @keyup.enter="handleSearch" />
</el-form-item>
</div>
</div>
<div class="filter-actions">
<div class="filter-actions-start">
<button
type="button"
class="btn-link btn-filter-toggle"
:aria-expanded="filterExpanded"
aria-controls="db-weigh-filter-extra"
@click="filterExpanded = !filterExpanded"
>
{{ filterExpanded ? '收起更多' : '展开更多' }}
<el-icon>
<ArrowUpBold v-if="filterExpanded" />
<ArrowDownBold v-else />
</el-icon>
</button>
</div>
<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="paginatedData"
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="inventoryDate"
label="日期"
min-width="120"
align="center"
header-align="center"
>
<template #default="{ row }">
{{ getRecordDate(row) }}
</template>
</el-table-column>
<el-table-column
prop="planNo"
label="计划单号"
min-width="140"
align="center"
header-align="center"
show-overflow-tooltip
/>
<el-table-column
prop="batchNo"
label="出入库单号"
min-width="140"
align="center"
header-align="center"
show-overflow-tooltip
/>
<el-table-column
prop="vehicleNo"
label="车牌号"
min-width="120"
align="center"
header-align="center"
/>
<el-table-column
prop="cargoName"
label="货物名称"
min-width="150"
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
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 label="单据状态" min-width="100" align="center" header-align="center">
<template #default="{ row }">
<el-tag :type="row.isVoided === 1 ? 'danger' : 'success'" size="small">
{{ row.isVoided === 1 ? '已作废' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="supplier"
label="供货单位"
min-width="150"
align="center"
header-align="center"
show-overflow-tooltip
/>
<el-table-column
prop="consignee"
label="收货单位"
min-width="150"
align="center"
header-align="center"
show-overflow-tooltip
/>
<el-table-column label="本次量(吨)" min-width="110" align="right" header-align="center">
<template #default="{ row }">
{{ formatKgToTon(row.currentQuantity) }}
</template>
</el-table-column>
<el-table-column label="净重(吨)" min-width="110" align="right" header-align="center">
<template #default="{ row }">
{{ formatKgToTon(row.netWeight) }}
</template>
</el-table-column>
<el-table-column
prop="grossWeight"
label="毛重(kg)"
min-width="110"
align="right"
header-align="center"
/>
<el-table-column
prop="grossWeightTime"
label="毛重时间"
min-width="160"
align="center"
header-align="center"
/>
<el-table-column
prop="tareWeight"
label="皮重(kg)"
min-width="110"
align="right"
header-align="center"
/>
<el-table-column
prop="tareWeightTime"
label="皮重时间"
min-width="160"
align="center"
header-align="center"
/>
<el-table-column prop="deduction" label="扣杂(kg)" min-width="110" align="right" header-align="center">
<template #default="{ row }">
{{ row.deduction || 0 }}
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="280"
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
v-if="row.isVoided === 0"
class="btn-op-item op-warn"
:disabled="!isWeighingRecordToday(row)"
:title="isWeighingRecordToday(row) ? '' : '仅支持作废当天的地磅单'"
@click="handleVoid(row)"
>
<el-icon><Close /></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="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</section>
<el-dialog
v-model="modalOpen"
:title="editingRecord ? '修改地磅单' : '新增地磅单'"
width="800px"
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">
<PlVehicleSelect v-model="formData.vehicleNo" />
</el-form-item>
<el-form-item label="产品" prop="productId">
<DbCrmProductSelect
v-model:product-id="formData.productId"
v-model:product-name="formData.cargoName"
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="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="计划号" prop="planNo">
<el-input v-model="formData.planNo" placeholder="请输入计划号" />
</el-form-item>
<el-form-item label="出入库单号" prop="batchNo">
<el-input v-model="formData.batchNo" placeholder="出入库单号" readonly />
</el-form-item>
<el-form-item label="本次量(吨)">
<el-input :value="currentQuantityInTons" placeholder="本次量(吨)" readonly />
</el-form-item>
<el-form-item label="供货单位" prop="supplier">
<DbCrmCustomerSelect v-model="formData.supplier" placeholder="请输入供货单位搜索" />
</el-form-item>
<el-form-item label="扣杂(kg)" prop="deduction">
<el-input-number
v-model="formData.deduction"
:min="0"
:precision="2"
:controls="false"
placeholder="请输入扣杂"
style="width: 100%"
@change="calculateNetWeight"
/>
</el-form-item>
<el-form-item label="收货单位" prop="consignee">
<DbCrmCustomerSelect v-model="formData.consignee" placeholder="请输入收货单位搜索" />
</el-form-item>
<el-form-item label="毛重(kg)" prop="grossWeight">
<el-input-number
v-model="formData.grossWeight"
:min="0"
:precision="2"
:controls="false"
placeholder="请输入毛重"
style="width: 100%"
@change="handleGrossWeightChange"
/>
</el-form-item>
<el-form-item label="毛重时间" prop="grossWeightTime">
<el-date-picker
v-model="formData.grossWeightTime"
type="datetime"
placeholder="选择时间"
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="皮重(kg)" prop="tareWeight">
<el-input-number
v-model="formData.tareWeight"
:min="0"
:precision="2"
:controls="false"
placeholder="请输入皮重"
style="width: 100%"
@change="handleTareWeightChange"
/>
</el-form-item>
<el-form-item label="皮重时间" prop="tareWeightTime">
<el-date-picker
v-model="formData.tareWeightTime"
type="datetime"
placeholder="选择时间"
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="净重(kg)">
<el-input
:value="formData.grossWeight > 0 && formData.tareWeight > 0 ? netWeight.toFixed(2) : '--'"
disabled
/>
<el-alert v-if="isNetInvalid" type="error" :closable="false" style="margin-top: 8px">
净重必须大于0
</el-alert>
</el-form-item>
<el-form-item label="备注" prop="remarks" class="form-col-span-2">
<el-input v-model="formData.remarks" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleModalClose">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button type="primary" :loading="saving" :disabled="isNetInvalid" @click="handleSubmit">
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
<DbRelatedViewDialog
v-model:visible="relatedModalOpen"
:loading="loadingRelated"
:sections="relatedViewSections"
/>
</div>
</template>
<script setup lang="ts">
/**
* UI 模式 P02 · 复杂业务列表
* 参考:/#/crm/quote · src/views/crm/quote/index.vue
*/
import type { TableColumnCtx } from 'element-plus'
import {
ArrowDownBold,
ArrowUpBold,
Check,
Close,
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 DbRelatedViewDialog from '@/components/business/db/DbRelatedViewDialog.vue'
import { buildWeighingRelatedSections } from '@/components/business/db/db-related-view'
import PlVehicleSelect from '@/components/business/pl/PlVehicleSelect.vue'
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
import { $modal } from '@/utils/modal'
import { inventoryApi } from '@/api/db/inventory'
import { planApi } from '@/api/db/plans'
import {
weighingApi,
type WeighingCreate,
type WeighingListParams,
type WeighingRecord,
type WeighingRelatedData
} from '@/api/db/weighing'
defineOptions({ name: 'DbWeighing' })
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const exportLoading = ref(false)
const saving = ref(false)
const allData = ref<WeighingRecord[]>([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const searchKeyword = ref('')
const selectedRows = ref<WeighingRecord[]>([])
const activeDateShortcut = ref<'all' | 'today' | 'tomorrow' | 'yesterday' | 'week' | ''>('today')
const filters = reactive({
batchNo: '',
supplier: '',
consignee: '',
businessType: '' as '' | '入库' | '出库'
})
const filterExpanded = ref(false)
const businessTypeOptions: Array<'入库' | '出库'> = ['入库', '出库']
const pad2 = (n: number) => String(n).padStart(2, '0')
const formatYmd = (d: Date) =>
`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`
const formatYmdHms = (d: Date) =>
`${formatYmd(d)} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
const timeRange = ref<[string, string] | null>([formatYmd(new Date()), formatYmd(new Date())])
const modalOpen = ref(false)
const editingRecord = ref<WeighingRecord | null>(null)
const relatedModalOpen = ref(false)
const loadingRelated = ref(false)
const relatedData = ref<WeighingRelatedData>({ plan: null, inventory: null })
const relatedViewSections = computed(() => buildWeighingRelatedSections(relatedData.value))
const formRef = ref()
const formData = reactive<
WeighingCreate & {
cargoName: string
specification: string
}
>({
vehicleNo: '',
planNo: '',
productId: '',
cargoName: '',
specification: '',
businessType: '入库',
batchNo: '',
supplier: '',
consignee: '',
grossWeight: 0,
grossWeightTime: '',
tareWeight: 0,
tareWeightTime: '',
deduction: 0,
netWeight: 0,
remarks: '',
purchasePlanId: undefined,
inboundOutboundId: undefined,
weighingPoint: '山峡化工',
plannedQuantity: undefined,
currentQuantity: undefined,
executedQuantity: undefined
})
const formRules = {
vehicleNo: [{ required: true, message: '请选择车牌号', trigger: 'change' }],
productId: [{ required: true, message: '请选择 CRM 产品', trigger: 'change' }],
supplier: [{ required: true, message: '请输入供货单位', trigger: 'blur' }],
consignee: [{ required: true, message: '请输入收货单位', trigger: 'blur' }]
}
const netWeight = computed(() => {
const gross = formData.grossWeight ?? 0
const tare = formData.tareWeight ?? 0
const deduction = formData.deduction ?? 0
return Number((gross - tare - deduction).toFixed(2))
})
const currentQuantityInTons = computed(() => {
if (formData.currentQuantity !== undefined && formData.currentQuantity !== null) {
return (formData.currentQuantity / 1000).toFixed(2)
}
return '--'
})
const isNetInvalid = computed(() => {
const hasGrossWeight = formData.grossWeight && formData.grossWeight > 0
const hasTareWeight = formData.tareWeight && formData.tareWeight > 0
if (hasGrossWeight && hasTareWeight) {
return netWeight.value <= 0
}
return false
})
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return allData.value.slice(start, end)
})
const formatKgToTon = (kg: number | undefined | null) => {
if (kg === undefined || kg === null) return '--'
return (kg / 1000).toFixed(2)
}
const getRecordDate = (row: WeighingRecord) =>
row.inventoryDate ||
(row.grossWeightTime ? row.grossWeightTime.split(' ')[0] : '') ||
(row.createdAt ? row.createdAt.split(' ')[0] : '') ||
''
const buildListQueryParams = (): WeighingListParams => {
const params: WeighingListParams = {}
const keyword = searchKeyword.value.trim()
if (keyword) params.q = keyword
if (timeRange.value?.[0] && timeRange.value?.[1]) {
params.dateFrom = timeRange.value[0]
params.dateTo = timeRange.value[1]
}
if (filters.batchNo.trim()) params.batchNo = filters.batchNo.trim()
if (filters.supplier.trim()) params.supplier = filters.supplier.trim()
if (filters.consignee.trim()) params.consignee = filters.consignee.trim()
if (filters.businessType) params.businessType = filters.businessType
return params
}
const getSummary = (param: { columns: TableColumnCtx<WeighingRecord>[] }) => {
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.label === '本次量(吨)') {
const totalCurrent = paginatedData.value.reduce(
(sum, row) => sum + (row.currentQuantity || 0) / 1000,
0
)
sums[index] = totalCurrent.toFixed(2)
return
}
if (column.label === '净重(吨)') {
const totalNet = paginatedData.value.reduce(
(sum, row) => sum + (row.netWeight || 0) / 1000,
0
)
sums[index] = totalNet.toFixed(2)
return
}
sums[index] = ''
})
return sums
}
const calculateNetWeight = () => {
formData.netWeight = netWeight.value
}
const handleGrossWeightChange = () => {
calculateNetWeight()
if (formData.grossWeight && formData.grossWeight > 0 && !formData.grossWeightTime) {
formData.grossWeightTime = formatYmdHms(new Date())
}
}
const handleTareWeightChange = () => {
calculateNetWeight()
if (formData.tareWeight && formData.tareWeight > 0 && !formData.tareWeightTime) {
formData.tareWeightTime = formatYmdHms(new Date())
}
}
const loadData = async () => {
loading.value = true
try {
const records = await weighingApi.list(buildListQueryParams())
allData.value = records
.slice()
.sort((a, b) => getRecordDate(b).localeCompare(getRecordDate(a)))
total.value = allData.value.length
const maxPage = Math.ceil(total.value / pageSize.value) || 1
if (currentPage.value > maxPage) {
currentPage.value = 1
}
} catch (error) {
console.error('加载地磅单列表失败', error)
} finally {
loading.value = false
}
}
const handleSearch = () => {
currentPage.value = 1
loadData()
}
const handleQuickSearch = () => {
handleSearch()
}
const handleQuickSearchClear = () => {
searchKeyword.value = ''
handleQuickSearch()
}
const handleSelectionChange = (selection: WeighingRecord[]) => {
selectedRows.value = selection
}
const handleResetFilters = () => {
filters.batchNo = ''
filters.supplier = ''
filters.consignee = ''
filters.businessType = ''
searchKeyword.value = ''
timeRange.value = [formatYmd(new Date()), formatYmd(new Date())]
activeDateShortcut.value = 'today'
currentPage.value = 1
loadData()
}
const handleDateShortcut = (type: 'all' | 'today' | 'tomorrow' | 'yesterday' | 'week') => {
activeDateShortcut.value = type
const today = new Date()
if (type === 'all') {
timeRange.value = null
} else if (type === 'today') {
const d = formatYmd(today)
timeRange.value = [d, d]
} else if (type === 'tomorrow') {
const t = new Date(today)
t.setDate(t.getDate() + 1)
const d = formatYmd(t)
timeRange.value = [d, d]
} else if (type === 'yesterday') {
const y = new Date(today)
y.setDate(y.getDate() - 1)
const d = formatYmd(y)
timeRange.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)
timeRange.value = [formatYmd(monday), formatYmd(sunday)]
}
handleSearch()
}
const handleTimeRangeChange = () => {
activeDateShortcut.value = ''
currentPage.value = 1
}
const handlePageChange = (page: number) => {
currentPage.value = page
}
const handleSizeChange = (size: number) => {
pageSize.value = size
currentPage.value = 1
}
const resetFormData = () => {
Object.assign(formData, {
vehicleNo: '',
planNo: '',
productId: '',
cargoName: '',
specification: '',
businessType: '入库',
batchNo: '',
supplier: '',
consignee: '',
grossWeight: 0,
grossWeightTime: '',
tareWeight: 0,
tareWeightTime: '',
deduction: 0,
netWeight: 0,
remarks: '',
purchasePlanId: undefined,
inboundOutboundId: undefined,
weighingPoint: '山峡化工',
plannedQuantity: undefined,
currentQuantity: undefined,
executedQuantity: undefined
})
}
const handleAdd = () => {
editingRecord.value = null
resetFormData()
modalOpen.value = true
}
const handleView = async (record: WeighingRecord) => {
loadingRelated.value = true
try {
relatedData.value = await weighingApi.getRelated(record.id)
relatedModalOpen.value = true
} catch (error) {
console.error('加载关联数据失败', error)
} finally {
loadingRelated.value = false
}
}
const handleEdit = (record: WeighingRecord) => {
editingRecord.value = record
Object.assign(formData, {
vehicleNo: record.vehicleNo,
planNo: record.planNo || '',
productId: record.productId || '',
cargoName: record.cargoName,
specification: record.specification || '',
businessType: record.businessType,
batchNo: record.batchNo || '',
supplier: record.supplier,
consignee: record.consignee,
grossWeight: record.grossWeight,
grossWeightTime: record.grossWeightTime,
tareWeight: record.tareWeight,
tareWeightTime: record.tareWeightTime,
deduction: record.deduction || 0,
netWeight: record.netWeight,
remarks: record.remarks || '',
purchasePlanId: record.purchasePlanId,
inboundOutboundId: record.inboundOutboundId,
weighingPoint: record.weighingPoint || '山峡化工',
plannedQuantity: record.plannedQuantity,
currentQuantity: record.currentQuantity,
executedQuantity: record.executedQuantity
})
modalOpen.value = true
}
const isWeighingRecordToday = (row: WeighingRecord) => {
return getRecordDate(row) === formatYmd(new Date())
}
const handleVoid = async (row: WeighingRecord) => {
if (!isWeighingRecordToday(row)) {
$modal.msgWarning('仅支持作废当天的地磅单,历史数据不可作废')
return
}
try {
await $modal.confirm('确定要作废这条地磅单吗?')
await weighingApi.void(row.id)
$modal.msgSuccess('作废成功')
loadData()
} catch {
// 用户取消或请求失败
}
}
const handleSubmit = async () => {
if (!formRef.value || isNetInvalid.value) return
await formRef.value.validate(async (valid: boolean) => {
if (!valid) return
saving.value = true
try {
const hasGrossWeight = formData.grossWeight && formData.grossWeight > 0
const hasTareWeight = formData.tareWeight && formData.tareWeight > 0
if (hasGrossWeight && hasTareWeight) {
formData.netWeight = netWeight.value
} else {
formData.netWeight = 0
}
const submitData: WeighingCreate = {
vehicleNo: formData.vehicleNo,
planNo: formData.planNo || undefined,
productId: formData.productId,
businessType: formData.businessType,
batchNo: formData.batchNo || undefined,
supplier: formData.supplier,
consignee: formData.consignee,
grossWeight: formData.grossWeight ?? 0,
grossWeightTime: formData.grossWeightTime || '',
tareWeight: formData.tareWeight ?? 0,
tareWeightTime: formData.tareWeightTime || '',
deduction: formData.deduction ?? 0,
netWeight: formData.netWeight,
plannedQuantity: formData.plannedQuantity || undefined,
executedQuantity: formData.executedQuantity || undefined,
purchasePlanId: formData.purchasePlanId || undefined,
inboundOutboundId: formData.inboundOutboundId || undefined,
weighingPoint: formData.weighingPoint || '山峡化工',
remarks: formData.remarks || undefined,
currentQuantity: formData.currentQuantity || undefined
}
if (editingRecord.value) {
await weighingApi.update(editingRecord.value.id, {
productId: formData.productId,
vehicleNo: submitData.vehicleNo,
planNo: submitData.planNo,
businessType: submitData.businessType,
batchNo: submitData.batchNo,
supplier: submitData.supplier,
consignee: submitData.consignee,
grossWeight: submitData.grossWeight,
grossWeightTime: submitData.grossWeightTime,
tareWeight: submitData.tareWeight,
tareWeightTime: submitData.tareWeightTime,
deduction: submitData.deduction,
netWeight: submitData.netWeight,
plannedQuantity: submitData.plannedQuantity,
executedQuantity: submitData.executedQuantity,
purchasePlanId: submitData.purchasePlanId,
inboundOutboundId: submitData.inboundOutboundId,
weighingPoint: submitData.weighingPoint,
remarks: submitData.remarks,
currentQuantity: submitData.currentQuantity
})
$modal.msgSuccess('更新成功')
} else {
await weighingApi.create(submitData)
$modal.msgSuccess('保存成功')
}
modalOpen.value = false
loadData()
} catch (error) {
console.error('保存地磅单失败', error)
} finally {
saving.value = false
}
})
}
const handleModalClose = () => {
modalOpen.value = false
editingRecord.value = null
formRef.value?.resetFields()
}
const handleExport = async () => {
exportLoading.value = true
try {
const exportRecords = await weighingApi.list(buildListQueryParams())
if (exportRecords.length === 0) {
$modal.msgWarning('没有数据可导出')
return
}
const headers = [
'车牌号',
'货物名称',
'业务类型',
'毛重(kg)',
'皮重(kg)',
'净重(kg)',
'毛重时间',
'皮重时间',
'供货单位',
'收货单位'
]
const csvContent = [
headers.join(','),
...exportRecords.map((r) =>
[
r.vehicleNo,
r.cargoName,
r.businessType,
r.grossWeight,
r.tareWeight,
r.netWeight,
r.grossWeightTime,
r.tareWeightTime,
r.supplier,
r.consignee
].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
}
}
const clearInventoryRouteQuery = () => {
if (!route.query.inboundOutboundId) return
router.replace({ path: route.path })
}
const openAddFromInventoryQuery = async () => {
const inboundOutboundId = route.query.inboundOutboundId as string
if (!inboundOutboundId) return
const query = { ...route.query }
try {
const inventory = await inventoryApi.get(inboundOutboundId)
formData.vehicleNo = (query.vehicleNo as string) || inventory.vehicleNo
formData.productId = (query.productId as string) || inventory.productId || ''
formData.cargoName = inventory.product
formData.specification = inventory.specification || ''
const businessType = (query.businessType as '入库' | '出库') || inventory.businessType
formData.businessType = businessType
if (query.supplier) {
formData.supplier = query.supplier as string
} else {
formData.supplier =
businessType === '入库' ? inventory.partnerCompany : '安庆市山峡化工有限公司'
}
if (query.consignee) {
formData.consignee = query.consignee as string
} else {
formData.consignee =
businessType === '入库' ? '安庆市山峡化工有限公司' : inventory.partnerCompany
}
formData.planNo = (query.planNo as string) || inventory.planNo || ''
formData.batchNo = (query.batchNo as string) || inventory.batchNo || ''
formData.inboundOutboundId = inboundOutboundId
formData.weighingPoint = '山峡化工'
const quantityInTons = query.quantity ? Number(query.quantity) : inventory.quantity
formData.currentQuantity = quantityInTons * 1000
const purchasePlanId = (query.purchasePlanId as string) || inventory.purchasePlanId || ''
if (purchasePlanId) {
formData.purchasePlanId = purchasePlanId
try {
const plan = await planApi.get(purchasePlanId)
formData.plannedQuantity = plan.plannedQuantity * 1000
} catch (error) {
console.error('获取计划数据失败', error)
}
}
editingRecord.value = null
modalOpen.value = true
clearInventoryRouteQuery()
} catch (error) {
console.error('加载出入库单数据失败', error)
}
}
useDbInstantSearch(
() => ({
q: searchKeyword.value,
timeRange: timeRange.value,
batchNo: filters.batchNo,
businessType: filters.businessType,
supplier: filters.supplier,
consignee: filters.consignee
}),
handleSearch
)
onMounted(() => {
loadData()
})
onActivated(() => {
openAddFromInventoryQuery()
})
</script>