|
|
@@ -59,6 +59,12 @@
|
|
|
>
|
|
|
今日
|
|
|
</el-button>
|
|
|
+ <el-button
|
|
|
+ :type="dateFilterType === 'tomorrow' ? 'primary' : 'default'"
|
|
|
+ @click="setDateFilter('tomorrow')"
|
|
|
+ >
|
|
|
+ 明日
|
|
|
+ </el-button>
|
|
|
<el-button
|
|
|
:type="dateFilterType === 'yesterday' ? 'primary' : 'default'"
|
|
|
@click="setDateFilter('yesterday')"
|
|
|
@@ -86,19 +92,21 @@
|
|
|
v-model:page-size="pageSize"
|
|
|
@current-change="handlePageChange"
|
|
|
@size-change="handleSizeChange"
|
|
|
+ @sort-change="handleSortChange"
|
|
|
>
|
|
|
- <el-table-column prop="planNo" label="计划号" min-width="200" />
|
|
|
- <el-table-column prop="batchNo" label="出入库单号" min-width="140" />
|
|
|
- <el-table-column prop="date" label="日期" min-width="120" />
|
|
|
- <el-table-column prop="partnerCompany" label="往来单位" min-width="150" />
|
|
|
- <el-table-column prop="businessType" label="业务类型" min-width="100">
|
|
|
+ <el-table-column prop="id" label="id" min-width="80" sortable="custom" :sort-order="getSortOrder('id')" />
|
|
|
+ <el-table-column prop="planNo" label="计划号" min-width="200" sortable="custom" :sort-order="getSortOrder('planNo')" />
|
|
|
+ <el-table-column prop="batchNo" label="出入库单号" min-width="140" sortable="custom" :sort-order="getSortOrder('batchNo')" />
|
|
|
+ <el-table-column prop="date" label="日期" min-width="120" sortable="custom" :sort-order="getSortOrder('date')" />
|
|
|
+ <el-table-column prop="partnerCompany" label="往来单位" min-width="150" sortable="custom" :sort-order="getSortOrder('partnerCompany')" />
|
|
|
+ <el-table-column prop="businessType" label="业务类型" min-width="100" sortable="custom" :sort-order="getSortOrder('businessType')">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="row.businessType === '入库' ? 'success' : 'warning'">
|
|
|
{{ row.businessType }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="status" label="状态" min-width="100">
|
|
|
+ <el-table-column prop="status" label="状态" min-width="100" sortable="custom" :sort-order="getSortOrder('status')">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag
|
|
|
:type="
|
|
|
@@ -113,41 +121,41 @@
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="product" label="产品" min-width="120" />
|
|
|
- <el-table-column prop="vehicleNo" label="车牌号" min-width="120" />
|
|
|
- <el-table-column prop="quantity" label="数量(吨)" min-width="120" />
|
|
|
- <el-table-column prop="netWeight" label="净重(吨)" min-width="120">
|
|
|
+ <el-table-column prop="product" label="产品" min-width="120" sortable="custom" :sort-order="getSortOrder('product')" />
|
|
|
+ <el-table-column prop="vehicleNo" label="车牌号" min-width="120" sortable="custom" :sort-order="getSortOrder('vehicleNo')" />
|
|
|
+ <el-table-column prop="quantity" label="数量(吨)" min-width="120" sortable="custom" :sort-order="getSortOrder('quantity')" />
|
|
|
+ <el-table-column prop="netWeight" label="净重(吨)" min-width="120" sortable="custom" :sort-order="getSortOrder('netWeight')">
|
|
|
<template #default="{ row }">
|
|
|
{{ row.netWeight !== undefined && row.netWeight !== null ? (row.netWeight / 1000).toFixed(2) : '--' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="grossWeight" label="毛重(吨)" min-width="120">
|
|
|
+ <el-table-column prop="grossWeight" label="毛重(吨)" min-width="120" sortable="custom" :sort-order="getSortOrder('grossWeight')">
|
|
|
<template #default="{ row }">
|
|
|
{{ row.grossWeight !== undefined && row.grossWeight !== null ? (row.grossWeight / 1000).toFixed(2) : '--' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="grossWeightTime" label="毛重时间" min-width="160">
|
|
|
+ <el-table-column prop="grossWeightTime" label="毛重时间" min-width="160" sortable="custom" :sort-order="getSortOrder('grossWeightTime')">
|
|
|
<template #default="{ row }">
|
|
|
{{ row.grossWeightTime || '--' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="tareWeight" label="皮重(吨)" min-width="120">
|
|
|
+ <el-table-column prop="tareWeight" label="皮重(吨)" min-width="120" sortable="custom" :sort-order="getSortOrder('tareWeight')">
|
|
|
<template #default="{ row }">
|
|
|
{{ row.tareWeight !== undefined && row.tareWeight !== null ? (row.tareWeight / 1000).toFixed(2) : '--' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="tareWeightTime" label="皮重时间" min-width="160">
|
|
|
+ <el-table-column prop="tareWeightTime" label="皮重时间" min-width="160" sortable="custom" :sort-order="getSortOrder('tareWeightTime')">
|
|
|
<template #default="{ row }">
|
|
|
{{ row.tareWeightTime || '--' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="deduction" label="扣杂(吨)" min-width="120">
|
|
|
+ <el-table-column prop="deduction" label="扣杂(吨)" min-width="120" sortable="custom" :sort-order="getSortOrder('deduction')">
|
|
|
<template #default="{ row }">
|
|
|
{{ row.deduction !== undefined && row.deduction !== null ? (row.deduction / 1000).toFixed(2) : '--' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="remarks" label="备注" min-width="150" show-overflow-tooltip />
|
|
|
- <el-table-column label="操作" width="360" fixed="right">
|
|
|
+ <el-table-column prop="remarks" label="备注" min-width="150" show-overflow-tooltip sortable="custom" :sort-order="getSortOrder('remarks')" />
|
|
|
+ <el-table-column label="操作" width="420" fixed="right">
|
|
|
<template #default="{ row }">
|
|
|
<el-button link type="primary" @click="handleView(row)">
|
|
|
<el-icon><View /></el-icon>
|
|
|
@@ -166,7 +174,21 @@
|
|
|
<el-icon><Plus /></el-icon>
|
|
|
{{ row.hasWeighingSlip ? '已生成地磅单' : '生成地磅单' }}
|
|
|
</el-button>
|
|
|
- <el-button link type="danger" @click="handleDelete(row.id)">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ @click="handleCopyFillLink(row)"
|
|
|
+ >
|
|
|
+ <el-icon><Link /></el-icon>
|
|
|
+ 磅单二维码
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="danger"
|
|
|
+ :disabled="!isRecordToday(row)"
|
|
|
+ :title="isRecordToday(row) ? '' : '仅支持删除当天的记录'"
|
|
|
+ @click="handleDelete(row)"
|
|
|
+ >
|
|
|
<el-icon><Delete /></el-icon>
|
|
|
删除
|
|
|
</el-button>
|
|
|
@@ -220,9 +242,6 @@
|
|
|
<el-form-item label="产品" prop="product">
|
|
|
<el-select
|
|
|
v-model="formData.product"
|
|
|
- filterable
|
|
|
- remote
|
|
|
- :remote-method="loadCargos"
|
|
|
placeholder="请选择产品"
|
|
|
style="width: 100%"
|
|
|
@focus="loadCargos('')"
|
|
|
@@ -235,7 +254,7 @@
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="数量(吨)" prop="quantity">
|
|
|
+ <el-form-item label="数量(吨)" prop="quantity" class="quantity-red">
|
|
|
<el-input-number
|
|
|
v-model="formData.quantity"
|
|
|
:min="0"
|
|
|
@@ -244,7 +263,24 @@
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="车牌号" prop="vehicleNo">
|
|
|
- <el-input v-model="formData.vehicleNo" placeholder="请输入车牌号" />
|
|
|
+ <el-select
|
|
|
+ v-model="formData.vehicleNo"
|
|
|
+ filterable
|
|
|
+ remote
|
|
|
+ allow-create
|
|
|
+ default-first-option
|
|
|
+ :remote-method="loadVehicles"
|
|
|
+ placeholder="请选择或输入车牌号"
|
|
|
+ style="width: 100%"
|
|
|
+ @focus="loadVehicles('')"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in vehicleOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.vehicleNo"
|
|
|
+ :value="item.vehicleNo"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="计划号" prop="planNo">
|
|
|
<el-input v-model="formData.planNo" placeholder="请输入计划号" :readonly="isPlanNoReadonly" />
|
|
|
@@ -323,17 +359,48 @@
|
|
|
<el-button @click="relatedModalOpen = false">关闭</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 磅单二维码弹窗:供截图保存,不依赖 HTTPS;遮罩深黑避免截到背后内容 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="qrcodeModalOpen"
|
|
|
+ title="山峡出入库磅单二维码"
|
|
|
+ width="400px"
|
|
|
+ class="qrcode-dialog"
|
|
|
+ modal-class="qrcode-dialog-overlay"
|
|
|
+ @close="handleQrcodeDialogClose"
|
|
|
+ >
|
|
|
+ <div v-if="captureRow" class="qrcode-dialog-body pound-slip-capture-inline">
|
|
|
+ <div class="capture-card">
|
|
|
+ <div class="capture-title">出入库单信息(已锁定)</div>
|
|
|
+ <div class="capture-row"><span class="label">日期:</span><span class="value">{{ captureRow.date }}</span></div>
|
|
|
+ <div class="capture-row"><span class="label">往来单位:</span><span class="value">{{ captureRow.partnerCompany }}</span></div>
|
|
|
+ <div class="capture-row"><span class="label">业务类型:</span><span class="value">{{ captureRow.businessType }}</span></div>
|
|
|
+ <div class="capture-row"><span class="label">产品:</span><span class="value">{{ captureRow.product }}</span></div>
|
|
|
+ <div class="capture-row"><span class="label">数量(吨):</span><span class="value">{{ captureRow.quantity }}</span></div>
|
|
|
+ <div class="capture-row"><span class="label">车牌号:</span><span class="value vehicle-no">{{ captureRow.vehicleNo }}</span></div>
|
|
|
+ <div v-if="captureRow.remarks" class="capture-row"><span class="label">备注:</span><span class="value">{{ captureRow.remarks }}</span></div>
|
|
|
+ </div>
|
|
|
+ <div class="capture-card capture-qrcode">
|
|
|
+ <div class="capture-id">{{ captureRow.businessType === '出库' ? '出库单ID:' : '入库单ID:' }}{{ captureRow.id }}</div>
|
|
|
+ <div class="capture-qr-wrap">
|
|
|
+ <img v-if="captureQrObjectUrl" :src="captureQrObjectUrl" alt="二维码" class="capture-qr-img" />
|
|
|
+ </div>
|
|
|
+ <div class="capture-desc">出入库单标识(扫码核验)</div>
|
|
|
+ </div>
|
|
|
+ <div class="qrcode-dialog-hint">请截图保存上方二维码及磅单信息</div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
- import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
+ import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
- import { Plus, Document, Search, Edit, Delete, View } from '@element-plus/icons-vue'
|
|
|
+ import { Plus, Document, Search, Edit, Delete, View, Link } from '@element-plus/icons-vue'
|
|
|
import { inventoryApi, type InventoryRecord, type InventoryCreate } from '@/api/inventoryApi'
|
|
|
import { planApi, type Plan } from '@/api/plansApi'
|
|
|
import { weighingApi, type WeighingRecord } from '@/api/weighingApi'
|
|
|
- import { companyApi, cargoApi, type Company, type Cargo } from '@/api/masterDataApi'
|
|
|
+ import { companyApi, cargoApi, vehicleApi, type Company, type Cargo, type Vehicle } from '@/api/masterDataApi'
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
import dayjs from 'dayjs'
|
|
|
import ArtTable from '@/components/Table/ArtTable.vue'
|
|
|
@@ -344,11 +411,14 @@
|
|
|
const loading = ref(false)
|
|
|
const saving = ref(false)
|
|
|
const tableData = ref<InventoryRecord[]>([])
|
|
|
+ const allRecords = ref<InventoryRecord[]>([]) // 全量数据,用于排序后分页
|
|
|
const total = ref(0)
|
|
|
const currentPage = ref(1)
|
|
|
const pageSize = ref(10)
|
|
|
const searchKeyword = ref('')
|
|
|
- const dateFilterType = ref<'all' | 'today' | 'yesterday' | 'week'>('all')
|
|
|
+ const dateFilterType = ref<'all' | 'today' | 'tomorrow' | 'yesterday' | 'week'>('all')
|
|
|
+ const sortProp = ref<string>('date')
|
|
|
+ const sortOrder = ref<'ascending' | 'descending' | null>('descending')
|
|
|
|
|
|
// 日期范围筛选
|
|
|
const dateRange = ref<[string, string]>([
|
|
|
@@ -360,6 +430,7 @@
|
|
|
const editingRecord = ref<InventoryRecord | null>(null)
|
|
|
const companyOptions = ref<Company[]>([])
|
|
|
const cargoOptions = ref<Cargo[]>([])
|
|
|
+ const vehicleOptions = ref<Vehicle[]>([])
|
|
|
const relatedModalOpen = ref(false)
|
|
|
const loadingRelated = ref(false)
|
|
|
const relatedData = ref<{ plan: Plan | null, weighing: WeighingRecord | null }>({ plan: null, weighing: null })
|
|
|
@@ -367,6 +438,11 @@
|
|
|
// 标志位:是否是从计划页面跳转过来的(用于控制是否预填表单)
|
|
|
const isFromPlanJump = ref(false)
|
|
|
|
|
|
+ // 磅单二维码弹窗:当前展示的记录与二维码图(Object URL)
|
|
|
+ const qrcodeModalOpen = ref(false)
|
|
|
+ const captureRow = ref<InventoryRecord | null>(null)
|
|
|
+ const captureQrObjectUrl = ref<string>('')
|
|
|
+
|
|
|
const formRef = ref()
|
|
|
// formData初始化时保持干净,不依赖路由参数
|
|
|
const formData = reactive<InventoryCreate>({
|
|
|
@@ -374,7 +450,7 @@
|
|
|
partnerCompany: '',
|
|
|
businessType: '入库',
|
|
|
product: '',
|
|
|
- quantity: 0,
|
|
|
+ quantity: 32,
|
|
|
planNo: '',
|
|
|
vehicleNo: '',
|
|
|
remarks: '',
|
|
|
@@ -409,8 +485,72 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 加载车牌号列表(模糊检索)
|
|
|
+ const loadVehicles = async (keyword: string) => {
|
|
|
+ try {
|
|
|
+ vehicleOptions.value = await vehicleApi.list(keyword || undefined)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载车牌号列表失败', error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存前确保车牌号已写入车牌号表(仅当车牌号不在基础资料中时才创建,避免重复插入)
|
|
|
+ const ensureVehicleExists = async (vehicleNo: string) => {
|
|
|
+ if (!vehicleNo?.trim()) return
|
|
|
+ const trimmed = vehicleNo.trim()
|
|
|
+ const existsInOptions = vehicleOptions.value.some(
|
|
|
+ (v) => v.vehicleNo.trim().toLowerCase() === trimmed.toLowerCase()
|
|
|
+ )
|
|
|
+ if (existsInOptions) return // 下拉选中的车牌已存在,无需插入
|
|
|
+ try {
|
|
|
+ await vehicleApi.create({ vehicleNo: trimmed, remarks: null })
|
|
|
+ } catch (err: any) {
|
|
|
+ if (err.response?.status !== 409) throw err
|
|
|
+ // 409 表示已存在,忽略
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 从路由参数填充表单的逻辑已移至handleAdd函数中,由isFromPlanJump标志位控制
|
|
|
|
|
|
+ const getSortOrder = (prop: string): 'ascending' | 'descending' | null => {
|
|
|
+ return sortProp.value === prop ? sortOrder.value : null
|
|
|
+ }
|
|
|
+
|
|
|
+ const compare = (a: InventoryRecord, b: InventoryRecord, prop: string, order: 'ascending' | 'descending'): number => {
|
|
|
+ const av = (a as any)[prop]
|
|
|
+ const bv = (b as any)[prop]
|
|
|
+ const aEmpty = av === undefined || av === null
|
|
|
+ const bEmpty = bv === undefined || bv === null
|
|
|
+ const isNum = typeof av === 'number' || typeof bv === 'number'
|
|
|
+ const aVal = aEmpty ? (isNum ? Infinity : '') : av
|
|
|
+ const bVal = bEmpty ? (isNum ? Infinity : '') : bv
|
|
|
+ let cmp = 0
|
|
|
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
|
+ cmp = aVal - bVal
|
|
|
+ } else {
|
|
|
+ cmp = String(aVal).localeCompare(String(bVal), 'zh-CN')
|
|
|
+ }
|
|
|
+ return order === 'ascending' ? cmp : -cmp
|
|
|
+ }
|
|
|
+
|
|
|
+ const applySortAndPage = () => {
|
|
|
+ let list = [...allRecords.value]
|
|
|
+ if (sortProp.value && sortOrder.value) {
|
|
|
+ list.sort((a, b) => compare(a, b, sortProp.value, sortOrder.value!))
|
|
|
+ }
|
|
|
+ total.value = list.length
|
|
|
+ const start = (currentPage.value - 1) * pageSize.value
|
|
|
+ const end = start + pageSize.value
|
|
|
+ tableData.value = list.slice(start, end)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleSortChange = (payload: { prop: string; order: string | null }) => {
|
|
|
+ sortProp.value = payload.prop || ''
|
|
|
+ sortOrder.value = payload.order === 'ascending' || payload.order === 'descending' ? payload.order : null
|
|
|
+ currentPage.value = 1
|
|
|
+ applySortAndPage()
|
|
|
+ }
|
|
|
+
|
|
|
const loadData = async () => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
@@ -429,6 +569,10 @@
|
|
|
if (dateFilterType.value === 'today') {
|
|
|
params.dateFrom = today
|
|
|
params.dateTo = today
|
|
|
+ } else if (dateFilterType.value === 'tomorrow') {
|
|
|
+ const t = dayjs().add(1, 'day').format('YYYY-MM-DD')
|
|
|
+ params.dateFrom = t
|
|
|
+ params.dateTo = t
|
|
|
} else if (dateFilterType.value === 'yesterday') {
|
|
|
const y = dayjs().subtract(1, 'day').format('YYYY-MM-DD')
|
|
|
params.dateFrom = y
|
|
|
@@ -444,11 +588,8 @@
|
|
|
}
|
|
|
|
|
|
const records = await inventoryApi.list(params)
|
|
|
- total.value = records.length
|
|
|
- // 根据当前页和每页大小进行数据切片
|
|
|
- const start = (currentPage.value - 1) * pageSize.value
|
|
|
- const end = start + pageSize.value
|
|
|
- tableData.value = records.slice(start, end)
|
|
|
+ allRecords.value = records
|
|
|
+ applySortAndPage()
|
|
|
} catch (error) {
|
|
|
ElMessage.error('加载数据失败')
|
|
|
} finally {
|
|
|
@@ -461,7 +602,7 @@
|
|
|
loadData()
|
|
|
}
|
|
|
|
|
|
- const setDateFilter = (type: 'all' | 'today' | 'yesterday' | 'week') => {
|
|
|
+ const setDateFilter = (type: 'all' | 'today' | 'tomorrow' | 'yesterday' | 'week') => {
|
|
|
dateFilterType.value = type
|
|
|
currentPage.value = 1
|
|
|
loadData()
|
|
|
@@ -475,13 +616,13 @@
|
|
|
|
|
|
const handlePageChange = (page: number) => {
|
|
|
currentPage.value = page
|
|
|
- loadData()
|
|
|
+ applySortAndPage()
|
|
|
}
|
|
|
|
|
|
const handleSizeChange = (size: number) => {
|
|
|
pageSize.value = size
|
|
|
currentPage.value = 1
|
|
|
- loadData()
|
|
|
+ applySortAndPage()
|
|
|
}
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
@@ -495,7 +636,7 @@
|
|
|
partnerCompany: useRouteParams ? (route.query.customerName as string) || '' : '',
|
|
|
businessType: useRouteParams ? (route.query.businessType as '入库' | '出库') || '入库' : '入库',
|
|
|
product: useRouteParams ? (route.query.product as string) || '' : '',
|
|
|
- quantity: 0,
|
|
|
+ quantity: 32,
|
|
|
planNo: useRouteParams ? (route.query.planNo as string) || '' : '',
|
|
|
vehicleNo: '',
|
|
|
remarks: '',
|
|
|
@@ -508,6 +649,7 @@
|
|
|
// 加载下拉框数据
|
|
|
loadCompanies('')
|
|
|
loadCargos('')
|
|
|
+ loadVehicles('')
|
|
|
modalOpen.value = true
|
|
|
}
|
|
|
|
|
|
@@ -540,17 +682,28 @@
|
|
|
// 加载下拉框数据
|
|
|
loadCompanies('')
|
|
|
loadCargos('')
|
|
|
+ loadVehicles('')
|
|
|
modalOpen.value = true
|
|
|
}
|
|
|
|
|
|
- const handleDelete = (id: number) => {
|
|
|
+ /** 判断是否为当天记录(仅当天可删除) */
|
|
|
+ const isRecordToday = (row: InventoryRecord) => {
|
|
|
+ const dateStr = row.date || ''
|
|
|
+ return dateStr === dayjs().format('YYYY-MM-DD')
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleDelete = (row: InventoryRecord) => {
|
|
|
+ if (!isRecordToday(row)) {
|
|
|
+ ElMessage.warning('仅支持删除当天的记录,历史数据不可删除')
|
|
|
+ return
|
|
|
+ }
|
|
|
ElMessageBox.confirm('确定要删除这条记录吗?', '确认删除', {
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning'
|
|
|
})
|
|
|
.then(async () => {
|
|
|
- await inventoryApi.delete(id)
|
|
|
+ await inventoryApi.delete(row.id)
|
|
|
ElMessage.success('删除成功')
|
|
|
loadData()
|
|
|
})
|
|
|
@@ -582,6 +735,45 @@
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ const getH5FillBaseUrl = () => {
|
|
|
+ const envBase = import.meta.env.VITE_H5_BASE_URL
|
|
|
+ if (envBase) return envBase.replace(/\/$/, '')
|
|
|
+ return window.location.origin + (window.location.pathname || '')
|
|
|
+ }
|
|
|
+
|
|
|
+ const getFillLinkUrl = (inventoryId: number) => `${getH5FillBaseUrl()}/#/inventory/fill/${inventoryId}`
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成二维码图片地址:内容为出入库单 ID(与 H5 填写页保持一致)
|
|
|
+ */
|
|
|
+ const getQrImageUrl = (inventoryId: number) =>
|
|
|
+ `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(String(inventoryId))}`
|
|
|
+
|
|
|
+ /** 打开磅单二维码弹窗,供用户截图保存(不依赖 HTTPS) */
|
|
|
+ const handleCopyFillLink = async (row: InventoryRecord) => {
|
|
|
+ const qrImageUrl = getQrImageUrl(row.id)
|
|
|
+ try {
|
|
|
+ captureRow.value = row
|
|
|
+ captureQrObjectUrl.value = ''
|
|
|
+ qrcodeModalOpen.value = true
|
|
|
+ await nextTick()
|
|
|
+ const res = await fetch(qrImageUrl, { mode: 'cors' })
|
|
|
+ if (!res.ok) throw new Error('二维码图片加载失败')
|
|
|
+ const blob = await res.blob()
|
|
|
+ captureQrObjectUrl.value = URL.createObjectURL(blob)
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.error(e?.message || '加载二维码失败')
|
|
|
+ qrcodeModalOpen.value = false
|
|
|
+ captureRow.value = null
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleQrcodeDialogClose = () => {
|
|
|
+ if (captureQrObjectUrl.value) URL.revokeObjectURL(captureQrObjectUrl.value)
|
|
|
+ captureRow.value = null
|
|
|
+ captureQrObjectUrl.value = ''
|
|
|
+ }
|
|
|
+
|
|
|
const handleSubmit = async () => {
|
|
|
if (!formRef.value) return
|
|
|
|
|
|
@@ -589,6 +781,11 @@
|
|
|
if (valid) {
|
|
|
saving.value = true
|
|
|
try {
|
|
|
+ // 新车牌号先写入车牌号表
|
|
|
+ if (formData.vehicleNo?.trim()) {
|
|
|
+ await ensureVehicleExists(formData.vehicleNo.trim())
|
|
|
+ }
|
|
|
+
|
|
|
// 清理数据:将空字符串转换为 undefined,移除 undefined 字段
|
|
|
const cleanData = (data: any) => {
|
|
|
const cleaned: any = {}
|
|
|
@@ -605,8 +802,16 @@
|
|
|
|
|
|
if (editingRecord.value) {
|
|
|
const updateData = cleanData(formData)
|
|
|
+ // 备注清空时也要提交,否则后端不会更新该字段(exclude_unset 会忽略未传字段)
|
|
|
+ updateData.remarks = formData.remarks === '' ? null : (formData.remarks || undefined)
|
|
|
await inventoryApi.update(editingRecord.value.id, updateData)
|
|
|
ElMessage.success('更新成功')
|
|
|
+ modalOpen.value = false
|
|
|
+ if (route.query.from === 'plan') {
|
|
|
+ router.replace('/plans')
|
|
|
+ } else {
|
|
|
+ loadData()
|
|
|
+ }
|
|
|
} else {
|
|
|
// 创建时,必需字段不能为空
|
|
|
const createData: InventoryCreate = {
|
|
|
@@ -622,9 +827,13 @@
|
|
|
}
|
|
|
await inventoryApi.create(createData)
|
|
|
ElMessage.success('保存成功')
|
|
|
+ modalOpen.value = false
|
|
|
+ if (route.query.from === 'plan') {
|
|
|
+ router.replace('/plans')
|
|
|
+ } else {
|
|
|
+ loadData()
|
|
|
+ }
|
|
|
}
|
|
|
- modalOpen.value = false
|
|
|
- loadData()
|
|
|
} catch (error: any) {
|
|
|
const errorMessage = error.response?.data?.detail || error.response?.data?.message || '操作失败'
|
|
|
ElMessage.error(errorMessage)
|
|
|
@@ -641,6 +850,10 @@
|
|
|
// 确保标志位被重置,避免下次新增时意外使用预填数据
|
|
|
isFromPlanJump.value = false
|
|
|
formRef.value?.resetFields()
|
|
|
+ // 若本次是从计划管理跳转过来的,关闭弹窗后返回计划管理
|
|
|
+ if (route.query.from === 'plan') {
|
|
|
+ router.replace('/plans')
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const handleExport = async () => {
|
|
|
@@ -661,6 +874,10 @@
|
|
|
if (dateFilterType.value === 'today') {
|
|
|
params.dateFrom = today
|
|
|
params.dateTo = today
|
|
|
+ } else if (dateFilterType.value === 'tomorrow') {
|
|
|
+ const t = dayjs().add(1, 'day').format('YYYY-MM-DD')
|
|
|
+ params.dateFrom = t
|
|
|
+ params.dateTo = t
|
|
|
} else if (dateFilterType.value === 'yesterday') {
|
|
|
const y = dayjs().subtract(1, 'day').format('YYYY-MM-DD')
|
|
|
params.dateFrom = y
|
|
|
@@ -680,10 +897,14 @@
|
|
|
ElMessage.warning('没有数据可导出')
|
|
|
return
|
|
|
}
|
|
|
+ let list = [...records]
|
|
|
+ if (sortProp.value && sortOrder.value) {
|
|
|
+ list.sort((a, b) => compare(a, b, sortProp.value, sortOrder.value!))
|
|
|
+ }
|
|
|
const headers = ['日期', '往来单位', '业务类型', '产品', '数量(吨)', '车牌号', '计划号', '备注']
|
|
|
const csvContent = [
|
|
|
headers.join(','),
|
|
|
- ...records.map((r) =>
|
|
|
+ ...list.map((r) =>
|
|
|
[
|
|
|
r.date,
|
|
|
r.partnerCompany,
|
|
|
@@ -783,5 +1004,198 @@
|
|
|
.dialog-content::-webkit-scrollbar-thumb:hover {
|
|
|
background: #a8a8a8;
|
|
|
}
|
|
|
+
|
|
|
+ // 新增/编辑弹窗中数量字段红色字体
|
|
|
+ :deep(.quantity-red .el-form-item__label) {
|
|
|
+ color: var(--el-color-danger);
|
|
|
+ }
|
|
|
+ :deep(.quantity-red .el-input__inner),
|
|
|
+ :deep(.quantity-red .el-input-number__increase),
|
|
|
+ :deep(.quantity-red .el-input-number__decrease) {
|
|
|
+ color: var(--el-color-danger);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 磅单二维码弹窗:顶部/底部/内容区上下边距缩小 */
|
|
|
+ :deep(.qrcode-dialog.el-dialog) {
|
|
|
+ .el-dialog__header {
|
|
|
+ padding: 8px 16px 6px;
|
|
|
+ }
|
|
|
+ .el-dialog__body {
|
|
|
+ padding: 6px 16px;
|
|
|
+ }
|
|
|
+ .el-dialog__footer {
|
|
|
+ padding: 6px 16px 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .qrcode-dialog-body {
|
|
|
+ max-height: 70vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+ .qrcode-dialog-hint {
|
|
|
+ margin-top: 6px;
|
|
|
+ padding: 5px 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ text-align: center;
|
|
|
+ background: #f3f4f6;
|
|
|
+ border-radius: 6px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline {
|
|
|
+ background: #f5f7fa;
|
|
|
+ padding: 6px 10px;
|
|
|
+ font-family: inherit;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-header {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ padding: 4px 10px;
|
|
|
+ background: #fff;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ padding: 8px 10px;
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-title {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-row .label {
|
|
|
+ color: #6b7280;
|
|
|
+ min-width: 82px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-row .value {
|
|
|
+ color: #111827;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-row .vehicle-no {
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-qrcode {
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-id {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-qr-wrap {
|
|
|
+ padding: 8px 0;
|
|
|
+ min-height: 168px;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-qr-img {
|
|
|
+ width: 168px;
|
|
|
+ height: 168px;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ .pound-slip-capture-inline .capture-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 保留原屏外截图样式(若有其他地方引用可删) */
|
|
|
+ .pound-slip-capture {
|
|
|
+ position: fixed;
|
|
|
+ left: -9999px;
|
|
|
+ top: 0;
|
|
|
+ width: 360px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ padding: 16px;
|
|
|
+ font-family: inherit;
|
|
|
+ .capture-header {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ padding: 16px;
|
|
|
+ background: #fff;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
|
+ }
|
|
|
+ .capture-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding: 16px;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
|
+ }
|
|
|
+ .capture-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+ .capture-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ .label {
|
|
|
+ color: #6b7280;
|
|
|
+ min-width: 90px;
|
|
|
+ }
|
|
|
+ .value {
|
|
|
+ color: #111827;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ .vehicle-no {
|
|
|
+ font-weight: 700;
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .capture-qrcode {
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ .capture-id {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+ .capture-qr-wrap {
|
|
|
+ padding: 16px 0;
|
|
|
+ min-height: 200px;
|
|
|
+ }
|
|
|
+ .capture-qr-img {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ .capture-desc {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+</style>
|
|
|
+
|
|
|
+<!-- 磅单二维码弹窗遮罩深黑(遮罩在 body 下,需全局样式) -->
|
|
|
+<style>
|
|
|
+.qrcode-dialog-overlay {
|
|
|
+ background: rgba(0, 0, 0, 0.92) !important;
|
|
|
+}
|
|
|
</style>
|
|
|
|