Files
shanxia2.0/miniprogram/components/db-ui/db-pound-qrcode-popup.vue
T
2026-09-26 11:59:39 +08:00

328 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>