|
@@ -215,7 +215,20 @@
|
|
|
<el-row :gutter="16">
|
|
<el-row :gutter="16">
|
|
|
<el-col :span="24">
|
|
<el-col :span="24">
|
|
|
<div style="margin: 8px 0">
|
|
<div style="margin: 8px 0">
|
|
|
- <el-input ref="searchInputRef" v-model="searchText" placeholder="请扫描设备条码添加" style="width: 300px" @keyup.enter.prevent="handleScanSearch" />
|
|
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ ref="searchInputRef"
|
|
|
|
|
+ v-model="searchText"
|
|
|
|
|
+ class="device-code-input"
|
|
|
|
|
+ placeholder="设备编号(支持扫码枪/RFID批量录入)"
|
|
|
|
|
+ :disabled="loading"
|
|
|
|
|
+ autocomplete="off"
|
|
|
|
|
+ rows="1"
|
|
|
|
|
+ style="width: 300px; vertical-align: top;"
|
|
|
|
|
+ @input="handleDeviceInput"
|
|
|
|
|
+ @keydown="handleRFIDKeydown"
|
|
|
|
|
+ @blur="handleDeviceBlur"
|
|
|
|
|
+ @keydown.enter.prevent="handleDeviceEnter"
|
|
|
|
|
+ ></textarea>
|
|
|
<el-button type="primary" style="margin-left: 16px" @click="handleAdd">手动借用</el-button>
|
|
<el-button type="primary" style="margin-left: 16px" @click="handleAdd">手动借用</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<el-table :data="deviceList" border style="width: 100%">
|
|
<el-table :data="deviceList" border style="width: 100%">
|
|
@@ -412,7 +425,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, watch, defineProps, defineEmits, onMounted, computed, nextTick } from 'vue';
|
|
|
|
|
|
|
+import { ref, watch, defineProps, defineEmits, onMounted, onBeforeUnmount, computed, nextTick } from 'vue';
|
|
|
import SelectDeviceDialog from './SelectDeviceDialog/index.vue';
|
|
import SelectDeviceDialog from './SelectDeviceDialog/index.vue';
|
|
|
import SettlementDialog from './SettlementDialog.vue';
|
|
import SettlementDialog from './SettlementDialog.vue';
|
|
|
import AbnormalDialog from './AbnormalDialog/index.vue';
|
|
import AbnormalDialog from './AbnormalDialog/index.vue';
|
|
@@ -679,8 +692,23 @@ const searchText = ref('');
|
|
|
const deviceList = ref<DeviceListItem[]>([]);
|
|
const deviceList = ref<DeviceListItem[]>([]);
|
|
|
const accessoryInfo = ref('');
|
|
const accessoryInfo = ref('');
|
|
|
const remark = ref('');
|
|
const remark = ref('');
|
|
|
-// 领取模式下的“实借配件信息”本地输入,不回显历史
|
|
|
|
|
|
|
+// 领取模式下的"实借配件信息"本地输入,不回显历史
|
|
|
const collectAccessories = ref('');
|
|
const collectAccessories = ref('');
|
|
|
|
|
+// RFID高速扫码缓冲相关
|
|
|
|
|
+const rfidInputBuffer = ref('');
|
|
|
|
|
+let rfidInputTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
|
+const loading = ref(false);
|
|
|
|
|
+// 记录最后一次输入时间,用于检测是否是高速扫码输入
|
|
|
|
|
+let lastInputTime = 0;
|
|
|
|
|
+// 记录连续输入次数,用于判断是否是扫码输入
|
|
|
|
|
+let consecutiveInputCount = 0;
|
|
|
|
|
+// RFID读取规则配置(简化版,可根据需要扩展)
|
|
|
|
|
+const readRuleConfig = ref({
|
|
|
|
|
+ enableRFIDBuffer: true, // 是否启用RFID缓冲
|
|
|
|
|
+ rfidInputTimeout: 120, // RFID缓冲判定时间(ms)
|
|
|
|
|
+ separator: ' ', // 分隔符
|
|
|
|
|
+ minInputSpeed: 30, // 最小输入速度(ms),低于此值认为是扫码输入
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
// 归还设备相关数据
|
|
// 归还设备相关数据
|
|
|
const returnSearchText = ref('');
|
|
const returnSearchText = ref('');
|
|
@@ -910,65 +938,327 @@ const handleViewAbnormal = (index: number) => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// 处理扫码搜索
|
|
|
|
|
|
|
+// 清理RFID输入定时器
|
|
|
|
|
+const clearRFIDInputTimer = () => {
|
|
|
|
|
+ if (rfidInputTimer) {
|
|
|
|
|
+ clearTimeout(rfidInputTimer);
|
|
|
|
|
+ rfidInputTimer = null;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 提交RFID缓冲区内容
|
|
|
|
|
+const submitRFIDBuffer = () => {
|
|
|
|
|
+ if (!readRuleConfig.value.enableRFIDBuffer) return;
|
|
|
|
|
+ if (!rfidInputBuffer.value) return;
|
|
|
|
|
+ const bufferedValue = rfidInputBuffer.value;
|
|
|
|
|
+ rfidInputBuffer.value = '';
|
|
|
|
|
+ clearRFIDInputTimer();
|
|
|
|
|
+ consecutiveInputCount = 0; // 重置计数
|
|
|
|
|
+ lastInputTime = 0; // 重置时间
|
|
|
|
|
+ searchText.value = bufferedValue;
|
|
|
|
|
+ handleScanSearch();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 调度RFID缓冲区提交(延迟提交)
|
|
|
|
|
+const scheduleRFIDBufferSubmit = () => {
|
|
|
|
|
+ if (!readRuleConfig.value.enableRFIDBuffer) return;
|
|
|
|
|
+ clearRFIDInputTimer();
|
|
|
|
|
+ const delay = Math.max(readRuleConfig.value.rfidInputTimeout || 120, 30);
|
|
|
|
|
+ rfidInputTimer = setTimeout(() => {
|
|
|
|
|
+ submitRFIDBuffer();
|
|
|
|
|
+ }, delay);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理设备输入(用于同步 textarea 内容到缓冲区,作为备用方案防止第一个字符丢失)
|
|
|
|
|
+const handleDeviceInput = () => {
|
|
|
|
|
+ // 如果 textarea 有内容但缓冲区为空,可能是第一个字符通过默认行为输入了
|
|
|
|
|
+ // 这种情况发生在:第一个字符没有被拦截(因为判断逻辑),但后续字符是高速输入
|
|
|
|
|
+ if (rfidInputBuffer.value === '' && searchText.value && searchText.value.length > 0) {
|
|
|
|
|
+ // 同步到缓冲区,确保内容不丢失
|
|
|
|
|
+ rfidInputBuffer.value = searchText.value;
|
|
|
|
|
+ // 如果内容长度大于1,说明已经有多个字符,可能是高速输入
|
|
|
|
|
+ if (searchText.value.length > 1) {
|
|
|
|
|
+ lastInputTime = Date.now();
|
|
|
|
|
+ consecutiveInputCount = searchText.value.length;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 单个字符,初始化时间戳
|
|
|
|
|
+ lastInputTime = Date.now();
|
|
|
|
|
+ consecutiveInputCount = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (rfidInputBuffer.value && searchText.value !== rfidInputBuffer.value) {
|
|
|
|
|
+ // 如果缓冲区有内容但 textarea 内容不同,同步缓冲区内容到 textarea
|
|
|
|
|
+ // 这确保显示的内容与缓冲区一致
|
|
|
|
|
+ searchText.value = rfidInputBuffer.value;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 检测是否是高速扫码输入(通过输入速度判断)
|
|
|
|
|
+const isHighSpeedInput = (): boolean => {
|
|
|
|
|
+ const now = Date.now();
|
|
|
|
|
+ const timeSinceLastInput = lastInputTime > 0 ? now - lastInputTime : 0;
|
|
|
|
|
+ lastInputTime = now;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是第一次输入(lastInputTime 之前为 0),先假设可能是高速输入
|
|
|
|
|
+ // 等待第二个字符来判断
|
|
|
|
|
+ if (timeSinceLastInput === 0) {
|
|
|
|
|
+ return false; // 第一次输入,暂时返回 false,但会在 handleRFIDKeydown 中特殊处理
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果输入间隔很短(小于配置的最小输入速度),认为是高速扫码
|
|
|
|
|
+ if (timeSinceLastInput < readRuleConfig.value.minInputSpeed && timeSinceLastInput > 0) {
|
|
|
|
|
+ consecutiveInputCount++;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果输入间隔较长,重置计数
|
|
|
|
|
+ if (timeSinceLastInput > 200) {
|
|
|
|
|
+ consecutiveInputCount = 0;
|
|
|
|
|
+ // 如果之前有缓冲内容,先提交
|
|
|
|
|
+ if (rfidInputBuffer.value) {
|
|
|
|
|
+ submitRFIDBuffer();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理RFID键盘输入(高速扫码)
|
|
|
|
|
+const handleRFIDKeydown = (event: KeyboardEvent) => {
|
|
|
|
|
+ // 如果未启用RFID缓冲,直接返回,不拦截
|
|
|
|
|
+ if (!readRuleConfig.value.enableRFIDBuffer) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 不拦截修饰键组合(如 Ctrl+C, Ctrl+V 等)
|
|
|
|
|
+ if (event.ctrlKey || event.metaKey || event.altKey) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理回车键
|
|
|
|
|
+ if (event.key === 'Enter') {
|
|
|
|
|
+ // 如果有缓冲内容,提交缓冲
|
|
|
|
|
+ if (rfidInputBuffer.value) {
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ submitRFIDBuffer();
|
|
|
|
|
+ }
|
|
|
|
|
+ // 否则让默认行为处理(触发 handleDeviceEnter)
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理退格键
|
|
|
|
|
+ if (event.key === 'Backspace') {
|
|
|
|
|
+ // 如果有缓冲内容,处理缓冲
|
|
|
|
|
+ if (rfidInputBuffer.value) {
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ rfidInputBuffer.value = rfidInputBuffer.value.slice(0, -1);
|
|
|
|
|
+ searchText.value = rfidInputBuffer.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 否则让默认行为处理
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理普通字符输入
|
|
|
|
|
+ if (event.key.length === 1) {
|
|
|
|
|
+ // 检测是否是高速扫码输入
|
|
|
|
|
+ const isHighSpeed = isHighSpeedInput();
|
|
|
|
|
+
|
|
|
|
|
+ // 判断是否是第一次输入(缓冲区为空且计数为0且时间戳为0)
|
|
|
|
|
+ const isFirstInput = rfidInputBuffer.value === '' && consecutiveInputCount === 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是高速输入、已经有缓冲内容、或者第一次输入,使用缓冲模式
|
|
|
|
|
+ // 第一次输入时,先捕获到缓冲区,等待第二个字符来判断是否是高速输入
|
|
|
|
|
+ if (isHighSpeed || rfidInputBuffer.value || consecutiveInputCount > 0 || isFirstInput) {
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ event.stopPropagation(); // 阻止事件冒泡,避免影响其他元素
|
|
|
|
|
+ rfidInputBuffer.value += event.key;
|
|
|
|
|
+ searchText.value = rfidInputBuffer.value;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是第一次输入,初始化时间戳
|
|
|
|
|
+ if (isFirstInput) {
|
|
|
|
|
+ lastInputTime = Date.now();
|
|
|
|
|
+ consecutiveInputCount = 1;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ consecutiveInputCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scheduleRFIDBufferSubmit();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果是正常输入且之前有缓冲内容,先提交缓冲内容
|
|
|
|
|
+ if (rfidInputBuffer.value) {
|
|
|
|
|
+ submitRFIDBuffer();
|
|
|
|
|
+ // 提交后,当前字符走默认行为(正常输入)
|
|
|
|
|
+ // 注意:这里不阻止默认行为,让字符正常输入到 textarea
|
|
|
|
|
+ }
|
|
|
|
|
+ // 否则让默认行为处理(正常输入)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理输入框失焦
|
|
|
|
|
+const handleDeviceBlur = (event: FocusEvent) => {
|
|
|
|
|
+ // 如果焦点转移到其他输入元素(如表单输入框),不自动聚焦
|
|
|
|
|
+ const relatedTarget = event.relatedTarget as HTMLElement;
|
|
|
|
|
+ if (relatedTarget && (
|
|
|
|
|
+ relatedTarget.tagName === 'INPUT' ||
|
|
|
|
|
+ relatedTarget.tagName === 'TEXTAREA' ||
|
|
|
|
|
+ relatedTarget.closest('.el-input') ||
|
|
|
|
|
+ relatedTarget.closest('.el-textarea') ||
|
|
|
|
|
+ relatedTarget.closest('form')
|
|
|
|
|
+ )) {
|
|
|
|
|
+ // 如果有缓冲内容,先提交
|
|
|
|
|
+ if (rfidInputBuffer.value) {
|
|
|
|
|
+ submitRFIDBuffer();
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 其他情况(如点击按钮等),延迟后自动聚焦
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (searchInputRef.value && !loading.value) {
|
|
|
|
|
+ searchInputRef.value.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 10);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理回车键
|
|
|
|
|
+const handleDeviceEnter = (event: KeyboardEvent) => {
|
|
|
|
|
+ if (event.shiftKey) return;
|
|
|
|
|
+ if (readRuleConfig.value.enableRFIDBuffer) {
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ submitRFIDBuffer();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ event.preventDefault();
|
|
|
|
|
+ handleScanSearch();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 分割多个设备编号(支持分隔符)
|
|
|
|
|
+const splitDeviceCodes = (input: string): string[] => {
|
|
|
|
|
+ const trimmed = input.trim();
|
|
|
|
|
+ if (!trimmed) return [];
|
|
|
|
|
+
|
|
|
|
|
+ const { separator } = readRuleConfig.value;
|
|
|
|
|
+
|
|
|
|
|
+ // 优先处理换行符
|
|
|
|
|
+ if (/[\r\n]/.test(trimmed)) {
|
|
|
|
|
+ return trimmed.split(/[\r\n]+/).filter(code => code.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理配置的分隔符
|
|
|
|
|
+ if (separator && separator.trim() !== '') {
|
|
|
|
|
+ const escapedSeparator = separator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
+ const separatorPattern = new RegExp(escapedSeparator + '+', 'g');
|
|
|
|
|
+ return trimmed.split(separatorPattern).filter(code => code.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理空格分隔
|
|
|
|
|
+ if (/\s/.test(trimmed)) {
|
|
|
|
|
+ return trimmed.split(/\s+/).filter(code => code.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 单个设备编号
|
|
|
|
|
+ return [trimmed];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 处理扫码搜索(支持批量)
|
|
|
const handleScanSearch = async (event?: Event) => {
|
|
const handleScanSearch = async (event?: Event) => {
|
|
|
// 阻止默认行为,防止表单提交导致页面刷新
|
|
// 阻止默认行为,防止表单提交导致页面刷新
|
|
|
if (event) {
|
|
if (event) {
|
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
event.stopPropagation();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 如果启用了RFID缓冲,先提交缓冲区
|
|
|
|
|
+ if (readRuleConfig.value.enableRFIDBuffer && rfidInputBuffer.value) {
|
|
|
|
|
+ const bufferedValue = rfidInputBuffer.value;
|
|
|
|
|
+ rfidInputBuffer.value = '';
|
|
|
|
|
+ clearRFIDInputTimer();
|
|
|
|
|
+ searchText.value = bufferedValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (!searchText.value.trim()) return;
|
|
if (!searchText.value.trim()) return;
|
|
|
|
|
|
|
|
|
|
+ // 分割多个设备编号
|
|
|
|
|
+ const codes = splitDeviceCodes(searchText.value);
|
|
|
|
|
+ if (codes.length === 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ let successCount = 0;
|
|
|
|
|
+ let failCount = 0;
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- const res = await deviceApi.GetList({
|
|
|
|
|
- page: 1,
|
|
|
|
|
- limit: 1,
|
|
|
|
|
- code: searchText.value.trim(),
|
|
|
|
|
- status: 1
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (res.data && res.data.length > 0) {
|
|
|
|
|
- const device = res.data[0];
|
|
|
|
|
- if (device.available_quantity <= 0) {
|
|
|
|
|
- ElMessage.warning('该设备当前无可用数量');
|
|
|
|
|
- // 重新聚焦输入框
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- searchInputRef.value?.focus?.();
|
|
|
|
|
- });
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查是否已存在
|
|
|
|
|
- const existingDevice = deviceList.value.find(d => d.device_no === device.id);
|
|
|
|
|
- if (existingDevice) {
|
|
|
|
|
- existingDevice.borrow_count++;
|
|
|
|
|
- ElMessage.success('设备数量已更新');
|
|
|
|
|
- } else {
|
|
|
|
|
- // 添加新设备到列表
|
|
|
|
|
- deviceList.value.push({
|
|
|
|
|
- device_no: device.id,
|
|
|
|
|
- device_code: device.code,
|
|
|
|
|
- device_type: device.category_name,
|
|
|
|
|
- device_name: device.name || '',
|
|
|
|
|
- borrow_count: 1,
|
|
|
|
|
- brand: device.brand ||device.device_brand || '',
|
|
|
|
|
- device_specification: device.specification,
|
|
|
|
|
- model: device.model || '',
|
|
|
|
|
- warehouse: device.warehouse,
|
|
|
|
|
- available_quantity:device.available_quantity
|
|
|
|
|
- });
|
|
|
|
|
- ElMessage.success('设备已添加到列表');
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.warning('未找到对应的设备');
|
|
|
|
|
|
|
+ // 批量处理设备编号
|
|
|
|
|
+ for (const code of codes) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await deviceApi.GetList({
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ limit: 1,
|
|
|
|
|
+ code: code.trim(),
|
|
|
|
|
+ status: 1
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (res.data && res.data.length > 0) {
|
|
|
|
|
+ const device = res.data[0];
|
|
|
|
|
+ if (device.available_quantity <= 0) {
|
|
|
|
|
+ ElMessage.warning(`设备 ${code} 当前无可用数量`);
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否已存在
|
|
|
|
|
+ const existingDevice = deviceList.value.find(d => d.device_no === device.id);
|
|
|
|
|
+ if (existingDevice) {
|
|
|
|
|
+ existingDevice.borrow_count++;
|
|
|
|
|
+ successCount++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 添加新设备到列表
|
|
|
|
|
+ deviceList.value.push({
|
|
|
|
|
+ device_no: device.id,
|
|
|
|
|
+ device_code: device.code,
|
|
|
|
|
+ device_type: device.category_name,
|
|
|
|
|
+ device_name: device.name || '',
|
|
|
|
|
+ borrow_count: 1,
|
|
|
|
|
+ brand: device.brand ||device.device_brand || '',
|
|
|
|
|
+ device_specification: device.specification,
|
|
|
|
|
+ model: device.model || '',
|
|
|
|
|
+ warehouse: device.warehouse,
|
|
|
|
|
+ available_quantity:device.available_quantity
|
|
|
|
|
+ });
|
|
|
|
|
+ successCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.warning(`未找到设备编号: ${code}`);
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`处理设备编号 ${code} 失败:`, error);
|
|
|
|
|
+ failCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示批量处理结果
|
|
|
|
|
+ if (codes.length > 1) {
|
|
|
|
|
+ if (successCount > 0 && failCount === 0) {
|
|
|
|
|
+ ElMessage.success(`成功添加 ${successCount} 个设备`);
|
|
|
|
|
+ } else if (successCount > 0 && failCount > 0) {
|
|
|
|
|
+ ElMessage.warning(`成功添加 ${successCount} 个设备,失败 ${failCount} 个`);
|
|
|
|
|
+ } else if (failCount > 0) {
|
|
|
|
|
+ ElMessage.error(`添加失败,共 ${failCount} 个设备编号`);
|
|
|
}
|
|
}
|
|
|
|
|
+ } else if (successCount > 0) {
|
|
|
|
|
+ ElMessage.success('设备已添加到列表');
|
|
|
|
|
+ }
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
ElMessage.error('搜索设备失败');
|
|
ElMessage.error('搜索设备失败');
|
|
|
} finally {
|
|
} finally {
|
|
|
searchText.value = ''; // 清空搜索框
|
|
searchText.value = ''; // 清空搜索框
|
|
|
|
|
+ loading.value = false;
|
|
|
// 重新聚焦输入框,保持选中状态
|
|
// 重新聚焦输入框,保持选中状态
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
- searchInputRef.value?.focus?.();
|
|
|
|
|
|
|
+ if (searchInputRef.value) {
|
|
|
|
|
+ searchInputRef.value.focus();
|
|
|
|
|
+ searchInputRef.value.select();
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -1010,28 +1300,14 @@ const handleReturnScanSearch = async (event?: Event) => {
|
|
|
// 检查是否已存在
|
|
// 检查是否已存在
|
|
|
const existingDeviceIndex = returnDeviceList.value.findIndex(d => d.device_no === device.id);
|
|
const existingDeviceIndex = returnDeviceList.value.findIndex(d => d.device_no === device.id);
|
|
|
if (existingDeviceIndex !== -1) {
|
|
if (existingDeviceIndex !== -1) {
|
|
|
- // 如果设备已存在,更新其状态return_status.label = '已归还';
|
|
|
|
|
|
|
+ // 如果设备已存在,更新其状态
|
|
|
returnDeviceList.value[existingDeviceIndex].return_status = normalizedStatus;
|
|
returnDeviceList.value[existingDeviceIndex].return_status = normalizedStatus;
|
|
|
returnDeviceList.value[existingDeviceIndex].is_return = true;
|
|
returnDeviceList.value[existingDeviceIndex].is_return = true;
|
|
|
returnDeviceList.value[existingDeviceIndex].return_time = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
returnDeviceList.value[existingDeviceIndex].return_time = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
|
ElMessage.success(`设备 ${device.category_name} 状态已更新为已归还`);
|
|
ElMessage.success(`设备 ${device.category_name} 状态已更新为已归还`);
|
|
|
} else {
|
|
} else {
|
|
|
- // 添加新设备到列表
|
|
|
|
|
- returnDeviceList.value.push({
|
|
|
|
|
- device_no: device.id,
|
|
|
|
|
- device_code: device.code,
|
|
|
|
|
- device_type: device.category_name,
|
|
|
|
|
- device_name: device.name || '',
|
|
|
|
|
- borrow_count: 1,
|
|
|
|
|
- brand: device.brand||device.device_brand || '',
|
|
|
|
|
- model: device.model || '',
|
|
|
|
|
- warehouse: device.warehouse,
|
|
|
|
|
- return_status: normalizedStatus,
|
|
|
|
|
- is_return: true,
|
|
|
|
|
- return_time: device.return_times || dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
- });
|
|
|
|
|
- console.log("returnDeviceList.value:::",returnDeviceList.value);
|
|
|
|
|
- ElMessage.success('设备已添加到列表');
|
|
|
|
|
|
|
+ // 设备不存在于归还列表中,提示用户
|
|
|
|
|
+ ElMessage.warning(`设备 ${device.code || device.category_name} 不在当前归还列表中,请先通过手动归还添加该设备`);
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
ElMessage.warning('未找到对应的设备');
|
|
ElMessage.warning('未找到对应的设备');
|
|
@@ -1234,7 +1510,10 @@ function handleClick(tab: TabsPaneContext) {
|
|
|
// 当切换到设备清单标签页时,自动聚焦输入框
|
|
// 当切换到设备清单标签页时,自动聚焦输入框
|
|
|
if (tab.props.name === 'second') {
|
|
if (tab.props.name === 'second') {
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
- searchInputRef.value?.focus?.();
|
|
|
|
|
|
|
+ if (searchInputRef.value) {
|
|
|
|
|
+ searchInputRef.value.focus();
|
|
|
|
|
+ searchInputRef.value.select();
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
} else if (tab.props.name === 'third') {
|
|
} else if (tab.props.name === 'third') {
|
|
|
// 当切换到归还设备清单标签页时,自动聚焦输入框
|
|
// 当切换到归还设备清单标签页时,自动聚焦输入框
|
|
@@ -1352,7 +1631,10 @@ watch(
|
|
|
// 当对话框打开且当前标签页是设备清单时,自动聚焦输入框
|
|
// 当对话框打开且当前标签页是设备清单时,自动聚焦输入框
|
|
|
if (v && activeName.value === 'second' && isCollect.value) {
|
|
if (v && activeName.value === 'second' && isCollect.value) {
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
- searchInputRef.value?.focus?.();
|
|
|
|
|
|
|
+ if (searchInputRef.value) {
|
|
|
|
|
+ searchInputRef.value.focus();
|
|
|
|
|
+ searchInputRef.value.select();
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
} else if (v && activeName.value === 'third' && !isView.value) {
|
|
} else if (v && activeName.value === 'third' && !isView.value) {
|
|
|
// 当对话框打开且当前标签页是归还设备清单时,自动聚焦输入框
|
|
// 当对话框打开且当前标签页是归还设备清单时,自动聚焦输入框
|
|
@@ -1360,6 +1642,13 @@ watch(
|
|
|
returnSearchInputRef.value?.focus?.();
|
|
returnSearchInputRef.value?.focus?.();
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+ // 清理RFID缓冲状态
|
|
|
|
|
+ if (v) {
|
|
|
|
|
+ rfidInputBuffer.value = '';
|
|
|
|
|
+ clearRFIDInputTimer();
|
|
|
|
|
+ consecutiveInputCount = 0;
|
|
|
|
|
+ lastInputTime = 0;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
@@ -1585,6 +1874,11 @@ function onCancel() {
|
|
|
selectedDevices.value = [];
|
|
selectedDevices.value = [];
|
|
|
// 清理附件文件列表
|
|
// 清理附件文件列表
|
|
|
attachmentFileList.value = [];
|
|
attachmentFileList.value = [];
|
|
|
|
|
+ // 清理RFID缓冲
|
|
|
|
|
+ rfidInputBuffer.value = '';
|
|
|
|
|
+ clearRFIDInputTimer();
|
|
|
|
|
+ consecutiveInputCount = 0;
|
|
|
|
|
+ lastInputTime = 0;
|
|
|
// 关闭所有对话框
|
|
// 关闭所有对话框
|
|
|
showSelectDeviceDialog.value = false;
|
|
showSelectDeviceDialog.value = false;
|
|
|
showSettlementDialog.value = false;
|
|
showSettlementDialog.value = false;
|
|
@@ -1733,6 +2027,14 @@ function clickReturn(){
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 组件卸载时清理定时器和状态
|
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
|
+ clearRFIDInputTimer();
|
|
|
|
|
+ rfidInputBuffer.value = '';
|
|
|
|
|
+ consecutiveInputCount = 0;
|
|
|
|
|
+ lastInputTime = 0;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
/* formRef.value.validate((valid: boolean) => {
|
|
/* formRef.value.validate((valid: boolean) => {
|
|
|
if (valid) {
|
|
if (valid) {
|
|
|
form.value.items = form.value.items.map(item => ({
|
|
form.value.items = form.value.items.map(item => ({
|
|
@@ -1776,4 +2078,23 @@ function clickReturn(){
|
|
|
height: 178px;
|
|
height: 178px;
|
|
|
display: block;
|
|
display: block;
|
|
|
}
|
|
}
|
|
|
|
|
+ .device-code-input {
|
|
|
|
|
+ padding: 8px 12px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ resize: vertical;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ font-family: 'Microsoft YaHei', sans-serif;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ }
|
|
|
|
|
+ .device-code-input:focus {
|
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
|
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
|
|
|
|
+ }
|
|
|
|
|
+ .device-code-input:disabled {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ cursor: not-allowed;
|
|
|
|
|
+ }
|
|
|
</style>
|
|
</style>
|