Files
smart_hospital_mobile/src/views/inventory/scan.vue
T
2026-08-07 11:35:11 +08:00

211 lines
6.3 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.
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useHospitalStore } from '../../stores/hospital'
import { useToast } from '../../composables/useToast'
import ScanButton from '../../components/ScanButton.vue'
import { ElMessageBox } from 'element-plus'
import type { Material } from '../../data/mockData'
const route = useRoute()
const router = useRouter()
const store = useHospitalStore()
const toast = useToast()
type Mode = 'inbound' | 'outbound'
const mode = ref<Mode>(((route.query.mode as Mode) || 'inbound') as Mode)
const scanning = ref(false)
const currentRfid = ref('')
const currentMaterial = ref<Material | undefined>()
const quantity = ref(1)
const department = ref('')
const reason = ref('')
const showDetail = ref(false)
const modeLabel = computed(() => (mode.value === 'inbound' ? '入库' : '出库'))
function switchMode(m: 'inbound' | 'outbound') {
mode.value = m
currentMaterial.value = undefined
currentRfid.value = ''
showDetail.value = false
}
function doScan() {
if (scanning.value) return
scanning.value = true
currentRfid.value = '扫描中…'
// 模拟扫码耗时
setTimeout(() => {
const mat = store.scanRfid()
scanning.value = false
if (mat) {
currentRfid.value = mat.rfid
currentMaterial.value = mat
showDetail.value = true
// 检查状态
if (mode.value === 'outbound' && mat.status !== 'in_stock') {
toast.warning(`该物资当前为「${mat.status === 'borrowed' ? '借出' : '维修'}」状态`)
}
} else {
currentRfid.value = '未识别到标签'
toast.error('未识别到 RFID 标签,请靠近重试')
}
}, 800)
}
async function doSubmit() {
if (!currentMaterial.value) return
const mat = currentMaterial.value
try {
if (mode.value === 'inbound') {
await ElMessageBox.confirm(
`确认入库 ${mat.name} × ${quantity.value}`,
'入库确认',
{ confirmButtonText: '确认入库', cancelButtonText: '取消' },
)
store.doInbound(mat, quantity.value, '当前用户', reason.value || '日常入库')
toast.success('入库成功')
} else {
if (!department.value.trim()) {
toast.warning('请填写领用科室')
return
}
await ElMessageBox.confirm(
`确认出库 ${mat.name} × ${quantity.value}${department.value}`,
'出库确认',
{ confirmButtonText: '确认出库', cancelButtonText: '取消' },
)
store.doOutbound(mat, quantity.value, '当前用户', department.value.trim())
toast.success('出库成功')
}
// 重置
currentMaterial.value = undefined
currentRfid.value = ''
quantity.value = 1
department.value = ''
reason.value = ''
showDetail.value = false
} catch (e: any) {
if (e?.message) toast.error(e.message)
}
}
function cancel() {
currentMaterial.value = undefined
currentRfid.value = ''
showDetail.value = false
}
function closeAndExit() {
router.back()
}
onMounted(() => {
// 自动触发一次扫描(演示效果)
// doScan()
})
</script>
<template>
<div class="scan-page">
<ScanButton
:rfid="currentRfid"
:scanning="scanning"
:mode="mode"
:show-mode-tabs="true"
@scan="doScan"
@mode-change="(m) => m === 'inbound' || m === 'outbound' ? switchMode(m) : null"
/>
<div v-if="!currentMaterial" class="m-card">
<div class="m-card-title"><span>📋 操作说明</span></div>
<div style="font-size: 13px; color: #646566; line-height: 1.8">
• 切换上方模式:<b>入库</b> 或 <b>出库</b><br />
• 按下 <b>扫码</b> 按钮模拟手持机读取 RFID 标签<br />
• 读取成功后核对物资信息并提交<br />
</div>
</div>
<div v-else class="m-card">
<div class="m-card-title">
<span>{{ modeLabel }}确认</span>
<span class="status-tag" :class="currentMaterial.status">
{{ currentMaterial.status === 'in_stock' ? '在库' : currentMaterial.status === 'borrowed' ? '已借出' : '维修中' }}
</span>
</div>
<div class="detail-row">
<span class="label">物资名称</span>
<span class="value">{{ currentMaterial.name }}</span>
</div>
<div class="detail-row">
<span class="label">RFID</span>
<span class="value rfid">{{ currentMaterial.rfid }}</span>
</div>
<div class="detail-row">
<span class="label">规格</span>
<span class="value">{{ currentMaterial.spec }}</span>
</div>
<div class="detail-row">
<span class="label">仓库/位置</span>
<span class="value">{{ currentMaterial.warehouse }} / {{ currentMaterial.location }}</span>
</div>
<div class="detail-row">
<span class="label">当前库存</span>
<span class="value">{{ currentMaterial.stock }} {{ currentMaterial.unit }}</span>
</div>
<div style="margin-top: 14px">
<div style="font-size: 13px; color: #646566; margin-bottom: 6px">
{{ modeLabel }}数量
</div>
<el-input-number
v-model="quantity"
:min="1"
:max="9999"
size="large"
style="width: 100%"
/>
</div>
<div v-if="mode === 'outbound'" style="margin-top: 14px">
<div style="font-size: 13px; color: #646566; margin-bottom: 6px">
领用科室 <span style="color: #ff4d4f">*</span>
</div>
<el-input
v-model="department"
placeholder="急诊科ICU"
size="large"
/>
</div>
<div v-if="mode === 'inbound'" style="margin-top: 14px">
<div style="font-size: 13px; color: #646566; margin-bottom: 6px">
入库备注(可选)
</div>
<el-input
v-model="reason"
placeholder="月度补货"
size="large"
/>
</div>
<div class="modal-actions">
<button class="btn-default" @click="cancel">取消</button>
<button
:class="mode === 'inbound' ? 'btn-primary' : 'btn-warning'"
@click="doSubmit"
>
确认{{ modeLabel }}
</button>
</div>
</div>
</div>
</template>
<style scoped>
.scan-page {
padding-bottom: 20px;
}
</style>