1117 lines
37 KiB
Vue
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>
|