优化宇视摄像头

This commit is contained in:
zc
2026-03-24 14:58:30 +08:00
parent 443ee0038b
commit 57ba3a72b8
6 changed files with 448 additions and 2 deletions

View File

@@ -0,0 +1,250 @@
<template>
<a-modal
v-model:visible="visible"
title="新增整箱领取记录"
:mask-closable="false"
:esc-to-close="false"
:width="width >= 800 ? 800 : '90%'"
:style="{ height: '90vh', maxHeight: '900px' }"
draggable
@before-ok="save"
@close="reset"
>
<a-form ref="formRef" v-model="form" :rules="rules">
<a-form-item label="物料编码">
<a-input
ref="materialCode"
v-model="form.materialCode"
placeholder="请点击此处确保光标闪烁,且输入法为英文状态,使用扫码枪扫描物料编码"
@keydown="handleKeyDown"
/>
</a-form-item>
<a-form-item label="图片">
<div class="image-container">
<img
:src="imgData.imgUrl"
alt="图片"
style="width: 100%; height: 100%; object-fit: cover; border-radius: 4px;"
/>
<!-- 错误状态 -->
<div v-if="weighingPageStatus === 'error'" class="video-overlay error">
<icon-close-circle-fill style="color: #ff4d4f; font-size: 24px;" />
<span>连接异常</span>
<Button size="small" type="primary" @click="enterWeighPage">重试</Button>
</div>
<!-- 加载状态 -->
<div v-if="weighingPageStatus === 'entering'" class="video-overlay">
<Spin />
<span style="margin-left: 8px;">加载中...</span>
</div>
</div>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
import {type FormInstance, Message, Spin, Button, Icon} from '@arco-design/web-vue'
import { useWindowSize } from '@vueuse/core'
import { addFullWorkOrder } from '@/apis/fullWorkOrder/fullWorkOrder'
import {getCaptureImage, getEnterWeighPage, getLeaveWeighPage} from '@/apis/weightManage/ys'
import { useResetReactive } from '@/hooks'
const emit = defineEmits<{
(e: 'save-success'): void
}>()
const { width, height } = useWindowSize()
const dataId = ref('')
const visible = ref(false)
const materialCodeInput = ref<any>(null)
// 称重页面状态
const weighingPageStatus = ref<'idle' | 'entering' | 'entered' | 'error'>('idle')
const [form, resetForm] = useResetReactive({
materialCode: '',
imgUrl: ''
})
const formRef = ref<FormInstance>()
const rules: FormInstance['rules'] = {
materialCode: [
{ required: true, message: '物料编码为空' },
],
}
const imgData = reactive({
imgUrl: 'http://localhost:6609/file/ys/carousel.jpg', // 称重页面图片URL
baseUrl: 'http://localhost:6609/file/ys/carousel.jpg' // 基础URL
})
// 图片刷新定时器
let imageRefreshTimer: any = null
// 重置
const reset = () => {
resetForm()
}
// 保存
const save = async () => {
const isInvalid = await formRef.value?.validate()
if (isInvalid) return
try {
//手动抓图
const response = await getCaptureImage('fullOrder');
if (response) {
form.imgUrl = response.data;
} else {
Message.error('抓图失败');
return false;
}
await addFullWorkOrder(form)
Message.success('新增成功')
emit('save-success')
return true
} catch (error) {
return false
}
}
// 进入称重页面
const enterWeighPage = async () => {
weighingPageStatus.value = 'entering'
try {
await getEnterWeighPage()
weighingPageStatus.value = 'entered'
} catch (error) {
console.error('进入称重页面失败:', error)
weighingPageStatus.value = 'error'
}
}
// 离开称重页面
const leaveWeighPage = async () => {
try {
await getLeaveWeighPage()
} catch (error) {
console.error('离开称重页面失败:', error)
}
}
// 新增
const onAdd = async () => {
reset()
dataId.value = ''
visible.value = true
// 进入称重页面
await enterWeighPage()
// 聚焦到物料编码输入框
nextTick(() => {
if (materialCodeInput.value) {
materialCodeInput.value.focus()
}
})
}
// 记录上次输入时间,用于判断是否是扫码枪输入
let lastInputTime = 0
// 记录是否正在接收扫码输入
let isScanning = false
// 处理键盘按下事件
const handleKeyDown = (event: KeyboardEvent) => {
// 获取当前时间
const currentTime = Date.now()
// 计算时间差
const timeDiff = currentTime - lastInputTime
// 检查是否是回车键(扫码枪通常以回车键结束)
if (event.key === 'Enter') {
// 扫码结束,标记为不在扫描中
isScanning = false
// 不阻止默认行为,让表单可以正常提交
}
// 检查是否是新的扫码开始
// 当时间间隔大于300ms且不是修饰键且不是功能键时认为是新的扫码开始
else if (timeDiff > 300 && !event.ctrlKey && !event.altKey && !event.metaKey) {
form.materialCode = ''
// 标记为开始扫描
isScanning = true
}
// 更新上次输入时间
lastInputTime = currentTime
}
// 组件挂载时
onMounted(() => {
// 初始时不启动图片刷新
})
// 组件卸载时
onBeforeUnmount(() => {
// 清除图片刷新定时器
if (imageRefreshTimer) {
clearInterval(imageRefreshTimer)
imageRefreshTimer = null
}
// 离开称重页面
leaveWeighPage()
})
// 监听visible变化
watch(visible, async (newVal) => {
if (newVal) {
// 当弹窗打开时启动图片自动刷新每1.5秒更新一次
imageRefreshTimer = setInterval(() => {
// 添加时间戳参数,避免浏览器缓存
imgData.imgUrl = `${imgData.baseUrl}?t=${Date.now()}`
}, 1500)
} else {
// 当弹窗关闭时,退出称重页面并停止图片刷新
await leaveWeighPage()
if (imageRefreshTimer) {
clearInterval(imageRefreshTimer)
imageRefreshTimer = null
}
}
})
defineExpose({ onAdd })
</script>
<style scoped lang="scss">
.image-container {
position: relative;
width: 100%;
height: 100%;
border: 1px solid #e8e8e8;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
/* 视频覆盖层样式(用于错误和加载状态) */
.video-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
gap: 10px;
}
.video-overlay.error {
background: rgba(255, 77, 79, 0.2);
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<div class="gi_table_page">
<GiTable
title="整箱领取记录管理"
row-key="id"
:data="dataList"
:columns="columns"
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
:disabled-tools="['size']"
:disabled-column-keys="['name']"
@refresh="search"
>
<template #toolbar-left>
<a-input-search v-model="queryForm.orderNo" placeholder="请输入任务工单号" allow-clear @search="search" />
<a-input-search v-model="queryForm.materialCode" placeholder="请输入物料编码" allow-clear @search="search" />
<a-input-search v-model="queryForm.imgUrl" placeholder="请输入图片地址" allow-clear @search="search" />
<a-input-search v-model="queryForm.createUser" placeholder="请输入创建人" allow-clear @search="search" />
<a-range-picker
v-model="queryForm.createTime"
:show-time="true"
format="YYYY-MM-DD HH:mm:ss"
style="height: 32px"
:allow-clear="true"
@change="search"
/>
<a-button @click="reset">
<template #icon><icon-refresh /></template>
<template #default>重置</template>
</a-button>
</template>
<template #toolbar-right>
<a-button v-permission="['fullWorkOrder:fullWorkOrder:add']" type="primary" @click="onAdd">
<template #icon><icon-plus /></template>
<template #default>新增</template>
</a-button>
<a-button v-permission="['fullWorkOrder:fullWorkOrder:export']" @click="onExport">
<template #icon><icon-download /></template>
<template #default>导出</template>
</a-button>
</template>
<template #imgUrl="{ record }">
<a-image
width="60"
:src="record.imgUrl"
/>
</template>
<template #action="{ record }">
<a-space>
<a-link
v-permission="['fullWorkOrder:fullWorkOrder:delete']"
status="danger"
:disabled="record.disabled"
:title="record.disabled ? '不可删除' : '删除'"
@click="onDelete(record)"
>
删除
</a-link>
</a-space>
</template>
</GiTable>
<FullWorkOrderAddModal ref="FullWorkOrderAddModalRef" @save-success="search" />
</div>
</template>
<script setup lang="ts">
import FullWorkOrderAddModal from './FullWorkOrderAddModal.vue'
import { type FullWorkOrderResp, type FullWorkOrderQuery, deleteFullWorkOrder, exportFullWorkOrder, listFullWorkOrder } from '@/apis/fullWorkOrder/fullWorkOrder'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import { useDownload, useTable } from '@/hooks'
import { isMobile } from '@/utils'
import has from '@/utils/has'
defineOptions({ name: 'FullWorkOrder' })
const queryForm = reactive<FullWorkOrderQuery>({
orderNo: undefined,
materialCode: undefined,
imgUrl: undefined,
createUser: undefined,
createTime: undefined,
sort: ['id,desc']
})
const {
tableData: dataList,
loading,
pagination,
search,
handleDelete
} = useTable((page) => listFullWorkOrder({ ...queryForm, ...page }), { immediate: true })
const columns = ref<TableInstanceColumns[]>([
{ title: '标题', dataIndex: 'title', slotName: 'title' },
{ title: '任务工单号', dataIndex: 'orderNo', slotName: 'orderNo' },
{ title: '物料编码', dataIndex: 'materialCode', slotName: 'materialCode' },
{ title: '抓拍图', dataIndex: 'imgUrl', slotName: 'imgUrl' },
{ title: '创建人', dataIndex: 'createUserString', slotName: 'createUser' },
{ title: '创建时间', dataIndex: 'createTime', slotName: 'createTime' },
{ title: '修改人', dataIndex: 'updateUserString', slotName: 'updateUser', show: false },
{ title: '修改时间', dataIndex: 'updateTime', slotName: 'updateTime', show: false },
{
title: '操作',
dataIndex: 'action',
slotName: 'action',
width: 160,
align: 'center',
fixed: !isMobile() ? 'right' : undefined,
show: has.hasPermOr(['fullWorkOrder:fullWorkOrder:detail', 'fullWorkOrder:fullWorkOrder:update', 'fullWorkOrder:fullWorkOrder:delete'])
}
]);
// 重置
const reset = () => {
queryForm.orderNo = undefined
queryForm.materialCode = undefined
queryForm.imgUrl = undefined
queryForm.createUser = undefined
queryForm.createTime = undefined
search()
}
// 删除
const onDelete = (record: FullWorkOrderResp) => {
return handleDelete(() => deleteFullWorkOrder(record.id), {
content: `是否确定删除该条数据?`,
showModal: true
})
}
// 导出
const onExport = () => {
useDownload(() => exportFullWorkOrder(queryForm))
}
const FullWorkOrderAddModalRef = ref<InstanceType<typeof FullWorkOrderAddModal>>()
// 新增
const onAdd = () => {
FullWorkOrderAddModalRef.value?.onAdd()
}
</script>
<style scoped lang="scss"></style>