211 lines
6.3 KiB
Vue
211 lines
6.3 KiB
Vue
<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> |