Files
wms-ui/src/views/weightManage/index.vue
2026-04-03 16:06:04 +08:00

1278 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="gi_page">
<div class="container">
<!-- 步骤导航 -->
<div class="step-nav">
<div class="step-item" :class="{ active: activeStep >= 1, completed: activeStep > 1 }">
<div class="step-circle">1</div>
<div class="step-title">扫码核验</div>
</div>
<div class="step-line"></div>
<div class="step-item" :class="{ active: activeStep >= 2, completed: activeStep > 2 }">
<div class="step-circle">2</div>
<div class="step-title">称重登记</div>
</div>
<div class="step-line"></div>
<div class="step-item" :class="{ active: activeStep >= 3, completed: activeStep > 3 }">
<div class="step-circle">3</div>
<div class="step-title">完成创建</div>
</div>
</div>
<!-- 步骤内容 -->
<!-- 扫码核验页面 -->
<div v-if="activeStep === 1" class="step-content">
<div class="main-content">
<!-- 左侧表单 -->
<div class="left-section">
<a-image v-if="!formData.photoUrl" :src="formData.photoUrl" />
<img v-else :src="formData.photoUrl" class="sample-image square-image" alt="样图">
<a-form :model="formData" layout="vertical">
<div class="form-row">
<div class="form-item">
<a-form-item label="物料名称">
<a-input v-model="formData.materialName" placeholder="物料名称" disabled />
</a-form-item>
</div>
<div class="form-item">
<a-form-item label="物料规格">
<a-input v-model="formData.materialSpec" placeholder="物料规格" disabled />
</a-form-item>
</div>
</div>
<div class="form-row">
<div class="form-item">
<a-form-item label="物料编码">
<a-input v-model="formData.encoding" placeholder="物料编码" disabled />
</a-form-item>
</div>
<div class="form-item">
<a-form-item label="重量(g)">
<a-input v-model="formData.unitWeight" placeholder="Kg" disabled />
</a-form-item>
</div>
</div>
<div class="form-row">
<div class="form-item">
<a-form-item label="物料编码">
<a-input
ref="materialCodeInput"
v-model="formData.inputMaterialCode"
placeholder="请点击此处确保光标闪烁,且输入法为英文状态,使用扫码枪扫描物料编码"
@keydown="handleKeyDown"
@input="handleMaterialCodeChange"
/>
</a-form-item>
</div>
</div>
</a-form>
</div>
<!-- 右侧信息 -->
<div class="right-section">
<div class="camera-section">
<!-- 图片展示 -->
<div class="image-placeholder square-image">
<img
:src="imgData.imgUrl"
alt="图片展示"
style="width: 100%; height: 100%; object-fit: cover; border-radius: 4px;"
/>
</div>
</div>
<div class="info-section">
<div class="info-card">
<h4>识别信息</h4>
<div class="info-item">
<span class="label">物料名称:</span>
<span class="value">{{ formData.materialName || '-' }}</span>
</div>
<div class="info-item">
<span class="label">物料编码:</span>
<span class="value">{{ formData.encoding || '-' }}</span>
</div>
</div>
<div class="info-card">
<h4>比对结果</h4>
<div class="info-item">
<span class="label">比对结果:</span>
<span
class="value match-result" :class="{
'match-success': compareMatchResult === 'success',
'match-failed': compareMatchResult === 'failed',
'match-pending': !compareMatchResult,
}"
>
<icon-check-circle-fill v-if="compareMatchResult === 'success'" style="color: #52c41a; margin-right: 8px; font-size: 25px;" />
<icon-close-circle-fill v-else-if="compareMatchResult === 'failed'" style="color: #ff4d4f; margin-right: 8px; font-size: 25px;" />
{{ compareMatchResult === 'success' ? '成功' : compareMatchResult === 'failed' ? '失败' : '请放置需比对的物料到扫描区域!' }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 称重登记页面 - 这里放FLV播放器 -->
<div v-else-if="activeStep === 2" class="step-content">
<div class="weighing-content">
<!-- 左侧输入区域 -->
<div class="left-weighing-section">
<div class="form-row">
<div class="form-item">
<label>物料名称:</label>
<a-input v-model="formData.materialName" placeholder="物料名称" disabled />
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>输入数量:</label>
<a-input-number :min="1" mode="button" v-model="inputQuantity" placeholder="请输入数量" @change="calculateWeight" />
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>标准重量(g):</label>
<a-input v-model="calculatedWeight" placeholder="-" disabled />
</div>
<div class="form-item">
<label>称重重量(g):</label>
<a-input v-model="ahDeviceWeight" placeholder="-" disabled />
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>该物料重量范围:</label>
<a-input v-model="formData.weightRange" placeholder="该物料重量范围" disabled />
</div>
</div>
<!-- 图片展示 -->
<div class="image-placeholder square-image">
<img
:src="weighingImgData.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>
<a-button size="small" type="primary" @click="enterWeighPage">重试</a-button>
</div>
<!-- 加载状态 -->
<div v-if="weighingPageStatus === 'entering'" class="video-overlay">
<a-spin />
<span style="margin-left: 8px;">加载中...</span>
</div>
</div>
<div class="weighing-actions">
<a-button type="primary" @click="handleConfirm">确定</a-button>
<a-button style="margin-left: 12px;" @click="handleReset">重置</a-button>
</div>
</div>
<!-- 右侧表格区域 -->
<div class="right-weighing-section">
<div class="weighing-section">
<div class="section-header">
<h4>称重列表</h4>
</div>
<a-table :columns="columns" :data="weighingList" bordered>
<template #image="{ record }">
<a-image width="60" :src="record.image"/>
</template>
<template #action="{ record }">
<a-button type="text" status="danger" @click="handleDelete(record.key)">
删除
</a-button>
</template>
</a-table>
</div>
</div>
</div>
</div>
<!-- 完成创建页面 -->
<div v-else-if="activeStep === 3" class="step-content">
<div class="completion-content">
<div class="completion-icon">
<a-icon type="check-circle" :size="64" style="color: #52c41a;" />
</div>
<h2>{{ workOrderResp.matchResult === 'success' ? '创建成功' : '创建失败' }}</h2>
<p>{{ workOrderResp.matchResult === 'success' ? '任务创建成功' : '任务创建失败' }}以下是任务详情</p>
<div class="completion-info">
<div class="info-item">
<span class="label">任务工单号:</span>
<span class="value">{{ workOrderResp.orderNo }}</span>
</div>
<div class="info-item">
<span class="label">物料名称:</span>
<span class="value">{{ formData.materialName }}</span>
</div>
<div class="info-item">
<span class="label">物料编码:</span>
<span class="value">{{ formData.encoding }}</span>
</div>
<div class="info-item">
<span class="label">物料总个数:</span>
<span class="value">{{ workOrderResp.totalCount }}</span>
</div>
<div class="info-item">
<span class="label">标准总重量(g):</span>
<span class="value">{{ workOrderResp.totalCalculatedWeight }}</span>
</div>
<div class="info-item">
<span class="label">实际总重量(g):</span>
<span class="value">{{ workOrderResp.totalWeight }}</span>
</div>
</div>
<div class="completion-actions">
<a-button type="primary" @click="onPrint">打印</a-button>
<a-button type="primary" @click="handleBackToFirst">返回首页</a-button>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<a-button
v-if="activeStep > 1 && activeStep < 3"
class="previous-button"
@click="handlePrevious"
>
上一步
</a-button>
<a-button
v-if="activeStep < 3"
type="primary"
class="next-button"
@click="handleNext"
>
{{ activeStep === 2 ? '完成' : (compareMatchResult === 'success' ? '下一步' : '开始比对') }}
</a-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { Message, Modal, Notification } from '@arco-design/web-vue'
import { getMaterialDetail, validateWeighing, vmSend } from '@/apis/weightManage/weightManage'
import {type WorkOrderResp, addWorkOrder} from '@/apis/workOrder/workOrder'
import {getCaptureImage, getCheckStatus, getEnterWeighPage, getLeaveWeighPage} from "@/apis/weightManage/ys";
import {brightness, connect, disconnect} from "@/apis/weightManage/light";
import router from "@/router";
import type {TableInstanceColumns} from "@/components/GiTable/type";
defineOptions({ name: 'WeightManage' })
// 当前步骤
const activeStep = ref(1)
// 表单数据
const formData = reactive({
inputMaterialCode: '', // 输入的物料编码
id: '', // 物料ID
encoding: '', // 物料编码
materialName: '', // 物料名称
materialSpec: '', // 物料规格
unitWeight: 0, // 重量
photoUrl: '', // 样图URL
weightRange: '', // 物料重量范围
})
const imgData = reactive({
imgUrl: 'http://localhost:6609/file/001.bmp', // 样图URL
baseUrl: 'http://localhost:6609/file/001.bmp' // 基础URL
})
const weighingImgData = reactive({
imgUrl: 'http://localhost:6609/file/ys/carousel.jpg', // 称重页面图片URL
baseUrl: 'http://localhost:6609/file/ys/carousel.jpg' // 基础URL
})
// 比对结果
const compareMatchResult = ref('')
// 物料编码输入框引用
const materialCodeInput = ref<any>(null)
// 称重页面状态
const weighingPageStatus = ref<'idle' | 'entering' | 'entered' | 'error'>('idle')
// 检查状态定时器
let statusCheckTimer: any = null
// 图片刷新定时器
let imageRefreshTimer: any = null
// 进入称重页面
const enterWeighPage = async () => {
weighingPageStatus.value = 'entering'
try {
const res = await getEnterWeighPage();
if (res.code === '0') {
weighingPageStatus.value = 'entered'
// 启动状态检查
startStatusCheck()
} else {
weighingPageStatus.value = 'error'
Message.error(res.msg)
}
} catch (error) {
weighingPageStatus.value = 'error'
Message.error("摄像头连接失败")
}
}
// 离开称重页面
const leaveWeighPage = async () => {
try {
await getLeaveWeighPage();
} catch (error) {
console.error('离开称重页面失败:', error)
}
// 停止状态检查
stopStatusCheck()
weighingPageStatus.value = 'idle'
}
// 手动抓图
const captureImage = async (imgName) => {
try {
const response = await getCaptureImage(imgName);
if (response.code === '0') {
return response.data
}
return null
} catch (error) {
console.error('抓图失败:', error)
return null
}
}
// 检查状态
const checkStatus = async () => {
try {
const response = await getCheckStatus();
if (response.code !== '0') {
weighingPageStatus.value = 'error'
}
} catch (error) {
weighingPageStatus.value = 'error'
}
}
// 启动状态检查
const startStatusCheck = () => {
// 每30秒检查一次状态
statusCheckTimer = setInterval(checkStatus, 30000)
}
// 停止状态检查
const stopStatusCheck = () => {
if (statusCheckTimer) {
clearInterval(statusCheckTimer)
statusCheckTimer = null
}
}
// 组件挂载时
onMounted(() => {
// 启动图片自动刷新,每秒更新一次
imageRefreshTimer = setInterval(() => {
// 添加时间戳参数,避免浏览器缓存
imgData.imgUrl = `${imgData.baseUrl}?t=${Date.now()}`
weighingImgData.imgUrl = `${weighingImgData.baseUrl}?t=${Date.now()}`
}, 1500)
// 页面加载后自动聚焦到物料编码输入框
nextTick(() => {
if (materialCodeInput.value) {
materialCodeInput.value.focus()
}
})
// 初始进入扫码验证页面,连接灯光
if (activeStep.value === 1) {
connect().catch(error => {
console.error('连接灯光失败:', error)
})
}
// 监听步骤变化
watch(activeStep, (newVal) => {
if (newVal === 1) {
// 进入扫码验证页面,连接灯光
connect().catch(error => {
console.error('连接灯光失败:', error)
})
} else if (newVal === 2) {
// 进入称重登记页面,断开灯光
disconnect().catch(error => {
console.error('断开灯光失败:', error)
})
nextTick(() => {
enterWeighPage()
})
} else {
// 离开称重页面,断开灯光
leaveWeighPage()
disconnect().catch(error => {
console.error('断开灯光失败:', error)
})
}
})
})
// 组件卸载时
onBeforeUnmount(() => {
// 清除图片刷新定时器
if (imageRefreshTimer) {
clearInterval(imageRefreshTimer)
imageRefreshTimer = null
}
// 停止状态检查
stopStatusCheck()
// 离开称重页面
if (activeStep.value === 2) {
leaveWeighPage()
}
// 断开灯光连接
disconnect().catch(error => {
console.error('断开灯光失败:', error)
})
})
const workOrderResp = ref<WorkOrderResp>({
id: '',
title: '',
orderNo: '',
materialName: '',
encoding: '',
unitWeight: '',
materialSpec: '',
photoUrl: '',
totalWeight: '',
totalCount: '',
createUserString: '',
updateUserString: '',
totalCalculatedWeight: '',
workOrderInfos: [],
matchResult: 'failed',
qrCodeData: '',
})
// 称重登记页面数据
const inputQuantity = ref()
// todo
const ahDeviceWeight = ref('')
const calculatedWeight = ref('')
const weighingCount = ref(1)
// WebSocket连接
const ws = ref<WebSocket | null>(null)
const wsConnected = ref(false)
// 称重列表数据
const weighingList = ref([
// 初始数据可以在这里添加
])
// 称重表格列
const columns = ref<TableInstanceColumns[]>([
{ title: '序号', dataIndex: 'weightTime', key: 'weightTime',},
{ title: '数量', dataIndex: 'quantity', key: 'quantity', className: 'green-bg',},
{ title: '称重重量(g)', dataIndex: 'weight', key: 'weight', className: 'green-bg',},
{ title: '标准重量(g)', dataIndex: 'calculatedWeight', key: 'calculatedWeight',},
{ title: '抓图', dataIndex: 'image', key: 'image',slotName: 'image',},
{ title: '操作', dataIndex: 'action', key: 'action', slotName: 'action',},
])
// 防抖函数
const debounce = <T extends (...args: any[]) => any>(func: T, delay: number): ((...args: Parameters<T>) => void) => {
let timer: ReturnType<typeof setTimeout> | null = null
return function (...args: Parameters<T>) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func(...args)
}, delay)
}
}
// 原始的物料编码变化处理函数
const originalHandleMaterialCodeChange = async () => {
// 确保输入框内容是完整的物料编码
const materialCode = formData.inputMaterialCode?.trim()
// 无论是否有输入,先重置所有物料相关字段,确保新数据能完全覆盖旧数据
formData.encoding = ''
formData.materialName = ''
formData.materialSpec = ''
formData.unitWeight = 0
formData.photoUrl = ''
formData.weightRange = ''
compareMatchResult.value = '';
// 如果有物料编码输入,获取物料数据
if (materialCode) {
try {
await fetchMaterialData(materialCode)
} catch (error) {
console.error('获取物料数据失败:', error)
// 即使获取失败,也保持字段为空,避免显示旧数据
}
}
}
// 记录上次输入时间,用于判断是否是扫码枪输入
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) {
// 清空输入框,准备接收新的扫码数据
formData.inputMaterialCode = ''
// 标记为开始扫描
isScanning = true
// }
}
// 更新上次输入时间
lastInputTime = currentTime
}
// 带防抖的物料编码变化处理函数
const handleMaterialCodeChange = debounce(originalHandleMaterialCodeChange, 500) // 500ms防抖延迟
// 扫码核验获取物料信息
const fetchMaterialData = async (code: string) => {
const res = await getMaterialDetail(code);
if (res.code === '0') {
// 更新表单数据
formData.id = res.data?.id || ''
formData.encoding = res.data?.encoding || ''
formData.materialName = res.data?.materialName || ''
formData.materialSpec = res.data?.materialSpec || ''
formData.unitWeight = res.data?.unitWeight || 0
formData.photoUrl = res.data?.photoUrl || ''
formData.weightRange = (res.data?.downFloatRatio || '-') + '% ~ ' + (res.data?.upFloatRatio || '-') + '%'
}
if(res.data && res.data.id) {
await brightness(res.data.id);
}
}
// 处理下一步
const handleNext = async () => {
if (activeStep.value < 3) {
if (activeStep.value === 2) {
// 当在称重登记页面点击完成时,显示确认弹框
Modal.confirm({
title: '确认完成',
content: '确定要完成称重登记吗?',
onOk: async () => {
try {
// 准备工作订单数据
const workOrderData = {
materialId: formData.id,
workOrderInfos: weighingList.value,
}
// 调用后端接口
addWorkOrder(workOrderData).then((res) => {
if (res.code === '0') {
Notification.success({
title: '操作成功',
content: `工单创建成功!`,
})
workOrderResp.value.id = res.data?.id || ''
workOrderResp.value.matchResult = 'success'
workOrderResp.value.title = res.data?.title || ''
workOrderResp.value.orderNo = res.data?.orderNo || ''
workOrderResp.value.totalWeight = res.data?.totalWeight || ''
workOrderResp.value.totalCalculatedWeight = res.data?.totalCalculatedWeight || ''
workOrderResp.value.totalCount = res.data?.totalCount || ''
// 关闭WebSocket连接
closeWebSocket()
// 跳转到完成页面
activeStep.value++
return true
}
})
} catch (error) {
console.error('创建工作订单失败:', error)
Message.error('创建工作订单失败')
}
},
})
} else if (activeStep.value === 1 && compareMatchResult.value !== 'success') {
// 当在扫码核验页面点击开始比对时,调用后端接口获取比对结果
try {
// 这里应该调用后端的比对接口暂时使用fetchMaterialData模拟
// 实际项目中应该替换为专门的比对接口
const materialCode = formData.inputMaterialCode?.trim()
if (!materialCode) {
Message.error('请先扫描物料编码')
return
}
// 调用后端接口获取比对结果 // todo
const res = await vmSend(materialCode);
if (res.data && res.data == materialCode) {
compareMatchResult.value === 'success';
Message.success('比对成功')
} else {
// 比对失败,提示错误
Message.error('比对失败')
}
} catch (error) {
console.error('比对失败:', error)
Message.error('比对失败,请重试')
}
} else {
activeStep.value++
// 进入称重页面时建立WebSocket连接
if (activeStep.value === 2) {
establishWebSocket()
}
}
}
}
// 处理上一步
const handlePrevious = () => {
if (activeStep.value > 1) {
activeStep.value--
// 离开称重页面时关闭WebSocket连接
if (activeStep.value !== 2) {
closeWebSocket()
}
}
}
// 打印
const onPrint = () => {
// 跳转到标签打印页面,并传递数据
router.push({
path: '/print',
query: {
workerOrderId: workOrderResp.value.id,
}
})
}
// 返回第一步
const handleBackToFirst = () => {
// 重置所有数据
activeStep.value = 1
// 重置表单数据
formData.inputMaterialCode = ''
formData.id = ''
formData.encoding = ''
formData.materialName = ''
formData.materialSpec = ''
formData.unitWeight = 0
formData.photoUrl = ''
compareMatchResult.value = ''
// 重置称重登记页面数据
inputQuantity.value = ''
// todo
ahDeviceWeight.value = ''
calculatedWeight.value = ''
weighingCount.value = 1
// 清空称重列表
weighingList.value = []
// 重置工作订单响应数据
workOrderResp.value = {}
// 离开称重页面时关闭WebSocket连接
closeWebSocket()
// 回到第一步后自动聚焦到物料编码输入框
nextTick(() => {
if (materialCodeInput.value) {
materialCodeInput.value.focus()
}
})
}
// 计算重量
const calculateWeight = () => {
if (inputQuantity.value && formData.unitWeight) {
const singleWeight = Number.parseFloat(formData.unitWeight.toString())
const quantity = Number.parseFloat(inputQuantity.value)
if (!isNaN(singleWeight) && !isNaN(quantity)) {
calculatedWeight.value = (singleWeight * quantity).toFixed(2)
}
} else {
calculatedWeight.value = ''
}
}
//处理确定
const handleConfirm = async () => {
// 校验输入数量是否为空
if (!inputQuantity.value || inputQuantity.value <= 0) {
Message.error('数量需大于0')
return
}
if (!ahDeviceWeight.value || ahDeviceWeight.value.trim() === '') {
Message.error('电子秤称重结果为空!')
return
}
const data = {
calculatedWeight: calculatedWeight.value,
materialId: formData.id,
inputQuantity: inputQuantity.value,
ahDeviceWeight: ahDeviceWeight.value,
}
const res = await validateWeighing(data);
// 然后处理错误信息
if (!res || !res.code) {
return;
}
if (res.data.code !== '200') {
if (res.data.code !== '501') {
playAudio('tooMany')
}
if (res.data.code !== '502') {
playAudio('tooLess')
}
Message.error(res.data.msg || '系统异常!');
return;
}
// 立即创建一个新项的基本数据
const newItem = {
key: (weighingList.value.length + 1).toString(),
weightTime: weighingCount.value,
materialId: formData.id,
quantity: inputQuantity.value,
weight: ahDeviceWeight.value,
calculatedWeight: calculatedWeight.value,
image: '加载中...', // 先显示加载状态
}
// 先添加到列表,让用户能立即看到记录
weighingList.value.push(newItem)
// 重置输入(让用户能继续输入)
inputQuantity.value = ''
// todo
ahDeviceWeight.value = ''
calculatedWeight.value = ''
weighingCount.value = weighingList.value.length + 1
// 调用手动抓图接口
const imageUrl = await captureImage(newItem.key + '_' + newItem.materialId);
const addedItem = weighingList.value.find((item) => item.key === newItem.key)
if (addedItem) {
addedItem.image = imageUrl || '抓图失败'
// 触发视图更新
weighingList.value = [...weighingList.value]
}
Message.success('添加成功')
}
// 处理重置
const handleReset = () => {
inputQuantity.value = ''
ahDeviceWeight.value = ''
calculatedWeight.value = ''
}
// 处理删除
const handleDelete = (key) => {
// 过滤掉要删除的项
weighingList.value = weighingList.value.filter((item) => item.key !== key)
// 重新排序称重次数
weighingList.value.forEach((item, index) => {
item.weightTime = index + 1
})
// 更新当前称重次数
weighingCount.value = weighingList.value.length + 1
}
// 建立WebSocket连接
const establishWebSocket = () => {
try {
// 这里替换为实际的WebSocket服务器地址
const wsUrl = 'ws://localhost:6609/ws/scale'
ws.value = new WebSocket(wsUrl)
ws.value.onopen = () => {
Message.success('已和电子秤建立连接')
}
ws.value.onmessage = (event) => {
try {
if (event.data) {
ahDeviceWeight.value = event.data
}
} catch (error) {
console.error('WebSocket消息解析失败:', error)
}
}
ws.value.onclose = () => {
wsConnected.value = false
}
ws.value.onerror = (error) => {
console.error('WebSocket错误:', error)
wsConnected.value = false
Message.error('称重连接失败')
}
} catch (error) {
console.error('建立WebSocket连接失败:', error)
Message.error('无法建立称重连接')
}
}
// 关闭WebSocket连接
const closeWebSocket = () => {
if (ws.value) {
ws.value.close()
ws.value = null
wsConnected.value = false
}
}
// 播放音频文件
const playAudio = (filename: string) => {
console.log(filename)
try {
// 创建音频对象并播放
const audio = new Audio(`/src/assets/wav/${filename}.wav`)
audio.play().catch(error => {
console.error('音频播放失败:', error)
})
} catch (error) {
console.error('播放音频时出错:', error)
}
}
// 组件卸载时关闭WebSocket连接
onUnmounted(() => {
closeWebSocket()
})
</script>
<style scoped lang="scss">
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 步骤导航 */
.step-nav {
display: flex;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background-color: var(--color-bg-2);
border-radius: 4px;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
}
.step-circle {
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #e8e8e8;
color: #666;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
}
.step-title {
font-size: 14px;
color: #666;
}
.step-line {
flex: 1;
height: 2px;
background-color: #e8e8e8;
margin: 0 20px;
}
.step-item.active .step-circle {
background-color: #1677ff;
color: white;
}
.step-item.active .step-title {
color: #1677ff;
font-weight: bold;
}
.step-item.completed .step-circle {
background-color: #52c41a;
color: white;
}
.step-item.completed .step-title {
color: #52c41a;
}
.step-item.completed ~ .step-line {
background-color: #52c41a;
}
/* 步骤内容 */
.step-content {
margin-bottom: 30px;
}
/* 主内容区域 */
.main-content {
display: flex;
margin-bottom: 30px;
}
.left-section {
flex: 1;
margin-right: 30px;
}
.right-section {
flex: 1;
}
/* 称重登记页面 */
.weighing-content {
display: flex;
margin-bottom: 30px;
}
.left-weighing-section {
width: 400px;
margin-right: 30px;
}
.right-weighing-section {
flex: 1;
}
.weighing-section {
margin-bottom: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.section-header h4 {
margin: 0;
font-size: 16px;
font-weight: bold;
}
.input-placeholder {
width: 100%;
height: 32px;
background-color: #e8e8e8;
border-radius: 4px;
margin-top: 8px;
}
.weighing-actions {
margin-top: 20px;
}
.table-actions {
display: flex;
justify-content: flex-end;
margin-top: 20px;
}
/* 绿色背景列 */
:deep(.green-bg) {
background-color: #f6ffed;
}
/* 完成创建页面 */
.completion-content {
text-align: center;
padding: 60px 0;
}
.completion-icon {
margin-bottom: 20px;
}
.completion-content h2 {
margin-bottom: 20px;
color: #52c41a;
}
.completion-content p {
margin-bottom: 30px;
font-size: 16px;
}
.completion-info {
max-width: 400px;
margin: 0 auto 40px;
text-align: left;
background-color: var(--color-bg-2);
padding: 20px;
border-radius: 4px;
}
.info-item {
margin-bottom: 12px;
display: flex;
}
.info-item .label {
width: 100px;
font-weight: bold;
}
.info-item .value {
flex: 1;
}
.completion-actions {
margin-top: 40px;
display: flex;
gap: 16px;
justify-content: center;
}
/* 图片占位符 */
.image-placeholder {
position: relative;
width: 100%;
height: 150px;
background-color: #e8e8e8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
border-radius: 4px;
overflow: hidden;
}
/* 视频覆盖层样式(用于错误和加载状态) */
.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);
}
/* 正方形图片 */
.square-image {
width: 200px;
height: 200px;
}
/* 样图 */
.sample-image {
object-fit: cover;
margin-bottom: 20px;
border-radius: 4px;
}
/* 相机区域 */
.camera-section {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.camera-section .image-placeholder {
width: calc(50% - 10px);
}
/* 表单区域 */
.form-section {
margin-top: 20px;
}
.form-row {
display: flex;
margin-bottom: 16px;
}
.form-item {
flex: 1;
margin-right: 20px;
}
.form-item.full-width {
flex: 1;
margin-right: 0;
}
/* 确保输入框宽度一致 */
:deep(.arco-input-wrapper) {
width: 100%;
height: 35px;
}
:deep(.arco-input) {
font-size: 16px;
}
.form-item label {
display: block;
margin-bottom: 8px;
font-size: 16px;
color: #666;
}
.form-actions {
margin-top: 20px;
}
/* 信息区域 */
.info-section {
margin-top: 20px;
}
.info-card {
background-color: var(--color-bg-2);
border-radius: 4px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.info-card h4 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: bold;
color: #333;
}
.info-item {
display: flex;
margin-bottom: 12px;
align-items: center;
}
.info-item .label {
width: 100px;
font-size: 14px;
color: #666;
font-weight: 500;
}
.info-item .value {
flex: 1;
font-size: 16px;
color: #333;
}
.match-result {
font-weight: 500;
display: flex;
align-items: center;
}
.match-success {
color: #52c41a;
}
.match-failed {
color: #ff4d4f;
}
.match-pending {
color: #d9d9d9;
}
.info-text {
margin-top: 20px;
font-size: 14px;
line-height: 1.5;
}
/* 操作按钮 */
.action-buttons {
display: flex;
justify-content: flex-end;
}
.next-button {
width: 120px;
height: 40px;
font-size: 14px;
}
.previous-button {
width: 120px;
height: 40px;
font-size: 14px;
margin-right: 12px;
}
</style>