11
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user