328 lines
8.0 KiB
Vue
328 lines
8.0 KiB
Vue
<template>
|
||
<view v-if="show" class="pound-qr-mask" @click="close">
|
||
<view class="pound-qr-panel" @click.stop>
|
||
<view class="pound-qr-panel__close" @click.stop="close">×</view>
|
||
<view class="pound-qr-panel__title">{{ title }}</view>
|
||
<view v-if="loading" class="pound-qr-panel__loading">加载中...</view>
|
||
<view v-else-if="row" class="pound-slip-capture-inline">
|
||
<view class="capture-card">
|
||
<view class="capture-row capture-row-plate">
|
||
<view class="vehicle-plate">
|
||
<view class="vehicle-plate-dot vehicle-plate-dot-lt" />
|
||
<view class="vehicle-plate-dot vehicle-plate-dot-lb" />
|
||
<text class="vehicle-plate-text">{{ row.vehicleNo || '未填车牌' }}</text>
|
||
<view class="vehicle-plate-dot vehicle-plate-dot-rt" />
|
||
<view class="vehicle-plate-dot vehicle-plate-dot-rb" />
|
||
</view>
|
||
</view>
|
||
<view class="capture-row capture-row-large">
|
||
<text class="label">往来单位:</text>
|
||
<text class="value">{{ row.partnerCompany || '--' }}</text>
|
||
</view>
|
||
<view class="capture-row capture-row-large">
|
||
<text class="label">物料名称:</text>
|
||
<text class="value">{{ productLine }}</text>
|
||
</view>
|
||
<view v-if="remarksText" class="capture-row">
|
||
<text class="label">备注:</text>
|
||
<text class="value">{{ remarksText }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="capture-card capture-qrcode">
|
||
<view class="capture-id">
|
||
{{ row.date || '' }}
|
||
{{ row.businessType === '出库' ? '出库单ID:' : '入库单ID:' }}{{ row.id }}
|
||
</view>
|
||
<view class="capture-qr-wrap">
|
||
<image v-if="qrPath" class="capture-qr-img" :src="qrPath" mode="aspectFit" />
|
||
</view>
|
||
<view class="capture-desc">出入库单标识(扫码核验)</view>
|
||
</view>
|
||
</view>
|
||
<!-- 隐藏 canvas,供 uqrcodejs 绘制 -->
|
||
<canvas
|
||
canvas-id="poundQrCanvas"
|
||
id="poundQrCanvas"
|
||
class="pound-qr-canvas"
|
||
:style="{ width: canvasSize + 'px', height: canvasSize + 'px' }"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, getCurrentInstance, ref, watch } from 'vue'
|
||
import db from '@/utils/dbRequest'
|
||
import {
|
||
formatPoundQrProductLine,
|
||
normalizePoundQrRemarks,
|
||
poundQrDialogTitle
|
||
} from '@/utils/dbPoundQr'
|
||
import { makeQrTempFile } from '@/utils/makeQrTempFile'
|
||
|
||
const props = defineProps({
|
||
show: Boolean,
|
||
inventoryId: { type: String, default: '' },
|
||
/** 列表行快照(可选);缺字段时走公开磅单接口补全 */
|
||
snapshot: { type: Object, default: null }
|
||
})
|
||
const emit = defineEmits(['close', 'update:show'])
|
||
|
||
const loading = ref(false)
|
||
const row = ref(null)
|
||
const qrPath = ref('')
|
||
let loadSeq = 0
|
||
const canvasSize = 200
|
||
const instance = getCurrentInstance()
|
||
|
||
const title = computed(() => poundQrDialogTitle(row.value?.businessType))
|
||
const productLine = computed(() => {
|
||
if (!row.value) return ''
|
||
const unit = row.value.quantityUnit || row.value.quantity_unit || '吨'
|
||
return formatPoundQrProductLine(row.value, unit)
|
||
})
|
||
const remarksText = computed(() => (row.value ? normalizePoundQrRemarks(row.value) : ''))
|
||
|
||
function close() {
|
||
emit('update:show', false)
|
||
emit('close')
|
||
}
|
||
|
||
function reset() {
|
||
loadSeq += 1
|
||
row.value = null
|
||
qrPath.value = ''
|
||
loading.value = false
|
||
}
|
||
|
||
function unwrapData(data) {
|
||
if (!data || typeof data !== 'object') return data
|
||
if (data.code != null && data.data !== undefined) return data.data
|
||
return data
|
||
}
|
||
|
||
async function fetchPoundQr(id) {
|
||
const raw = await db.get(`/public/db/inventory/${id}/pound-qr`, undefined, {
|
||
showError: false
|
||
})
|
||
return unwrapData(raw)
|
||
}
|
||
|
||
async function load() {
|
||
const seq = ++loadSeq
|
||
const id = String(props.inventoryId || '').trim()
|
||
if (!id) {
|
||
uni.showToast({ title: '缺少单据ID', icon: 'none' })
|
||
close()
|
||
return
|
||
}
|
||
loading.value = true
|
||
row.value = null
|
||
qrPath.value = ''
|
||
try {
|
||
let data = props.snapshot && String(props.snapshot.id) === id ? { ...props.snapshot } : null
|
||
// 快照已含数量即可展示;否则走公开接口(主包组件不可依赖 db 分包 inventoryApi)
|
||
if (!data || data.quantity == null) {
|
||
data = await fetchPoundQr(id)
|
||
}
|
||
if (!data || !data.id) throw new Error('单据不存在')
|
||
if (seq !== loadSeq) return
|
||
row.value = data
|
||
await new Promise((r) => setTimeout(r, 50))
|
||
const proxy = instance?.proxy
|
||
const path = await makeQrTempFile({
|
||
text: data.id,
|
||
canvasId: 'poundQrCanvas',
|
||
componentInstance: proxy,
|
||
size: canvasSize
|
||
})
|
||
if (seq !== loadSeq) return
|
||
qrPath.value = path
|
||
} catch (e) {
|
||
if (seq !== loadSeq) return
|
||
uni.showToast({ title: e?.message || '加载二维码失败', icon: 'none' })
|
||
close()
|
||
} finally {
|
||
if (seq === loadSeq) loading.value = false
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => [props.show, props.inventoryId],
|
||
([show]) => {
|
||
if (show) load()
|
||
else reset()
|
||
}
|
||
)
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.pound-qr-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1100;
|
||
background: rgba(0, 0, 0, 0.92);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px 8px;
|
||
box-sizing: border-box;
|
||
}
|
||
.pound-qr-panel {
|
||
width: 100%;
|
||
max-width: 360px;
|
||
max-height: 90vh;
|
||
overflow: auto;
|
||
background: #f5f7fa;
|
||
border-radius: 12px;
|
||
position: relative;
|
||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
|
||
}
|
||
.pound-qr-panel__close {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
z-index: 2;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 22px;
|
||
line-height: 1;
|
||
color: #94a3b8;
|
||
font-weight: 400;
|
||
}
|
||
.pound-qr-panel__title {
|
||
text-align: left;
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
padding: 14px 44px 12px 16px;
|
||
border-bottom: 1px solid #d1fae5;
|
||
background: linear-gradient(90deg, #ecfdf5 0%, #f5f7fa 55%);
|
||
}
|
||
.pound-qr-panel__loading {
|
||
text-align: center;
|
||
color: #9ca3af;
|
||
padding: 40px 0;
|
||
font-size: 14px;
|
||
}
|
||
.pound-qr-canvas {
|
||
position: fixed;
|
||
left: -9999px;
|
||
top: 0;
|
||
width: 200px;
|
||
height: 200px;
|
||
}
|
||
|
||
.pound-slip-capture-inline {
|
||
background: #f5f7fa;
|
||
padding: 4px 4px 12px;
|
||
margin: 0 3px 10px;
|
||
}
|
||
.capture-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
margin: 10px 5px;
|
||
padding: 6px 8px;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||
}
|
||
.capture-row {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-bottom: 4px;
|
||
font-size: 13px;
|
||
}
|
||
.capture-row .label {
|
||
color: #1e293b;
|
||
min-width: 82px;
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
}
|
||
.capture-row .value {
|
||
color: #1e293b;
|
||
flex: 1;
|
||
font-size: 13px;
|
||
}
|
||
.capture-row-large .value {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
}
|
||
.capture-row-plate {
|
||
width: 100%;
|
||
margin-bottom: 8px;
|
||
}
|
||
.vehicle-plate {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
padding: 10px 16px;
|
||
box-sizing: border-box;
|
||
background: #f5d000;
|
||
border: 3px solid #fff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 0 0 2px #fff;
|
||
}
|
||
.vehicle-plate-text {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #000;
|
||
letter-spacing: 1px;
|
||
white-space: nowrap;
|
||
}
|
||
.vehicle-plate-dot {
|
||
position: absolute;
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: #fff;
|
||
}
|
||
.vehicle-plate-dot-lt {
|
||
left: 6px;
|
||
top: 8px;
|
||
}
|
||
.vehicle-plate-dot-lb {
|
||
left: 6px;
|
||
bottom: 8px;
|
||
}
|
||
.vehicle-plate-dot-rt {
|
||
right: 6px;
|
||
top: 8px;
|
||
}
|
||
.vehicle-plate-dot-rb {
|
||
right: 6px;
|
||
bottom: 8px;
|
||
}
|
||
.capture-qrcode {
|
||
text-align: center;
|
||
}
|
||
.capture-id {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
margin-bottom: 6px;
|
||
word-break: break-all;
|
||
padding: 0 4px;
|
||
}
|
||
.capture-qr-wrap {
|
||
padding: 8px 0;
|
||
min-height: 200px;
|
||
}
|
||
.capture-qr-img {
|
||
width: 200px;
|
||
height: 200px;
|
||
display: inline-block;
|
||
}
|
||
.capture-desc {
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
color: #64748b;
|
||
margin-top: 4px;
|
||
padding-bottom: 8px;
|
||
}
|
||
</style>
|