This commit is contained in:
2026-08-07 11:35:11 +08:00
commit 750418e3b7
31 changed files with 4675 additions and 0 deletions
+211
View File
@@ -0,0 +1,211 @@
<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>