优化
This commit is contained in:
@@ -45,3 +45,13 @@ export function deleteFullWorkOrder(id: string) {
|
||||
export function exportFullWorkOrder(query: FullWorkOrderQuery) {
|
||||
return http.download(`${BASE_URL}/export`, query)
|
||||
}
|
||||
|
||||
/** @desc 保存原材料详情 */
|
||||
export function saveFullWorkOrderDetail(data: any) {
|
||||
return http.post(`${BASE_URL}/detail`, data)
|
||||
}
|
||||
|
||||
/** @desc 获取原材料详情列表 */
|
||||
export function getFullWorkOrderDetailList(id: string) {
|
||||
return http.get(`${BASE_URL}/infos/${id}`)
|
||||
}
|
||||
|
||||
69
src/views/fullClaim/FullWorkOrderDetailListModal.vue
Normal file
69
src/views/fullClaim/FullWorkOrderDetailListModal.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="原材料详情"
|
||||
:width="800"
|
||||
:footer="false"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="detailList"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
bordered
|
||||
>
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image
|
||||
width="80"
|
||||
height="60"
|
||||
:src="record.imgUrl"
|
||||
fit="cover"
|
||||
/>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getFullWorkOrderDetailList } from '@/apis/fullWorkOrder/fullWorkOrder'
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const detailList = ref<any[]>([])
|
||||
|
||||
const columns = [
|
||||
{ title: '称重重量(g)', dataIndex: 'weight', key: 'weight' },
|
||||
{ title: '截图', dataIndex: 'imgUrl', key: 'imgUrl', slotName: 'imgUrl' }
|
||||
]
|
||||
|
||||
const onOpen = async (id: string) => {
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getFullWorkOrderDetailList(id)
|
||||
if (res.code === '0') {
|
||||
detailList.value = res.data || []
|
||||
} else {
|
||||
Message.error(res.msg || '获取详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
Message.error('获取详情失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
detailList.value = []
|
||||
}
|
||||
|
||||
defineExpose({ onOpen })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
368
src/views/fullClaim/FullWorkOrderDetailModal.vue
Normal file
368
src/views/fullClaim/FullWorkOrderDetailModal.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="新增原材料详情"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="1200"
|
||||
:style="{ height: '80vh' }"
|
||||
draggable
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<div class="detail-container">
|
||||
<div class="left-section">
|
||||
<div class="weight-input">
|
||||
<label>称重重量(g)</label>
|
||||
<a-input v-model="weightValue" placeholder="等待电子秤数据..." disabled />
|
||||
</div>
|
||||
<div class="camera-section">
|
||||
<div class="image-container">
|
||||
<img
|
||||
:src="imgData.imgUrl"
|
||||
alt="宇视摄像头实时画面"
|
||||
style="width: 100%; height: 100%; object-fit: cover; border-radius: 4px;"
|
||||
/>
|
||||
<div v-if="cameraStatus === 'error'" class="video-overlay error">
|
||||
<icon-close-circle-fill style="color: #ff4d4f; font-size: 24px;" />
|
||||
<span>连接异常</span>
|
||||
<a-button size="small" type="primary" @click="enterCamera">重试</a-button>
|
||||
</div>
|
||||
<div v-if="cameraStatus === 'entering'" class="video-overlay">
|
||||
<a-spin />
|
||||
<span style="margin-left: 8px;">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="confirm-button">
|
||||
<a-button type="primary" @click="handleConfirm">确定</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<div class="detail-list">
|
||||
<a-table :columns="detailColumns" :data="detailList" :pagination="false" bordered>
|
||||
<template #imgUrl="{ record }">
|
||||
<a-image width="80" height="60" :src="record.imgUrl" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-button type="text" status="danger" @click="handleDeleteDetail(record)">
|
||||
删除
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getCaptureImage, getEnterWeighPage, getLeaveWeighPage } from '@/apis/weightManage/ys'
|
||||
import { weighAHStart, weighAHStop } from '@/apis/weightManage/weightManage'
|
||||
import { saveFullWorkOrderDetail } from '@/apis/fullWorkOrder/fullWorkOrder'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const weightValue = ref('')
|
||||
const cameraStatus = ref<'idle' | 'entering' | 'entered' | 'error'>('idle')
|
||||
const fullWorkOrder = ref('')
|
||||
|
||||
const imgData = reactive({
|
||||
imgUrl: 'http://localhost:6609/file/ys/carousel.jpg',
|
||||
baseUrl: 'http://localhost:6609/file/ys/carousel.jpg'
|
||||
})
|
||||
|
||||
let imageRefreshTimer: any = null
|
||||
|
||||
const detailList = ref<any[]>([])
|
||||
|
||||
const detailColumns = [
|
||||
{ title: '称重重量(g)', dataIndex: 'weight', key: 'weight' },
|
||||
{ title: '截图', dataIndex: 'imgUrl', key: 'imgUrl', slotName: 'imgUrl' },
|
||||
{ title: '操作', dataIndex: 'action', key: 'action', slotName: 'action', width: 80 }
|
||||
]
|
||||
|
||||
// WebSocket连接
|
||||
const ws = ref<WebSocket | null>(null)
|
||||
const wsConnected = ref(false)
|
||||
|
||||
// 建立WebSocket连接(电子称)
|
||||
const establishWebSocket = () => {
|
||||
try {
|
||||
const wsUrl = 'ws://localhost:6609/ws/scale'
|
||||
ws.value = new WebSocket(wsUrl)
|
||||
|
||||
ws.value.onopen = () => {
|
||||
wsConnected.value = true
|
||||
}
|
||||
|
||||
ws.value.onmessage = (event) => {
|
||||
try {
|
||||
if (event.data) {
|
||||
weightValue.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 connectScale = async () => {
|
||||
try {
|
||||
await weighAHStart()
|
||||
establishWebSocket()
|
||||
} catch (error) {
|
||||
console.error('连接电子称失败:', error)
|
||||
Message.error('与电子称的连接建立失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 断开电子称
|
||||
const disconnectScale = async () => {
|
||||
try {
|
||||
await weighAHStop()
|
||||
} catch (error) {
|
||||
console.error('断开电子称失败:', error)
|
||||
}
|
||||
closeWebSocket()
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
weightValue.value = ''
|
||||
detailList.value = []
|
||||
fullWorkOrder.value = ''
|
||||
}
|
||||
|
||||
const enterCamera = async () => {
|
||||
cameraStatus.value = 'entering'
|
||||
try {
|
||||
await getEnterWeighPage()
|
||||
cameraStatus.value = 'entered'
|
||||
} catch (error) {
|
||||
console.error('进入摄像头页面失败:', error)
|
||||
cameraStatus.value = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
const leaveCamera = async () => {
|
||||
try {
|
||||
await getLeaveWeighPage()
|
||||
} catch (error) {
|
||||
console.error('离开摄像头页面失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const onAdd = async (id: string) => {
|
||||
reset()
|
||||
fullWorkOrder.value = id
|
||||
visible.value = true
|
||||
// 独立连接电子称和摄像头
|
||||
await Promise.all([
|
||||
enterCamera(),
|
||||
connectScale()
|
||||
])
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!weightValue.value || weightValue.value.trim() === '') {
|
||||
Message.error('电子秤称重结果为空!')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const data = {
|
||||
type: 2,
|
||||
}
|
||||
// todo
|
||||
const response = await getCaptureImage(data)
|
||||
// const response = {
|
||||
// data: 'http://localhost:6609/file/ys/workOrder/fullOrder_1774322914630.jpg',
|
||||
// }
|
||||
if (response && response.data) {
|
||||
const newItem = {
|
||||
key: Date.now().toString(),
|
||||
weight: weightValue.value,
|
||||
imgUrl: response.data
|
||||
}
|
||||
detailList.value.push(newItem)
|
||||
weightValue.value = ''
|
||||
Message.success('添加成功')
|
||||
} else {
|
||||
Message.error('抓图失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('抓图失败:', error)
|
||||
Message.error('抓图失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteDetail = (record: any) => {
|
||||
detailList.value = detailList.value.filter(item => item.key !== record.key)
|
||||
Message.success('删除成功')
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (detailList.value.length === 0) {
|
||||
Message.error('请至少添加一条详情记录')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const data = detailList.value.map(item => ({
|
||||
weight: item.weight,
|
||||
imgUrl: item.imgUrl,
|
||||
fullWorkOrderId: fullWorkOrder.value,
|
||||
}))
|
||||
|
||||
|
||||
const res = await saveFullWorkOrderDetail(data)
|
||||
if (res.code === '0') {
|
||||
Message.success('保存成功')
|
||||
emit('save-success')
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.msg || '保存失败')
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
Message.error('保存失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (imageRefreshTimer) {
|
||||
clearInterval(imageRefreshTimer)
|
||||
imageRefreshTimer = null
|
||||
}
|
||||
leaveCamera()
|
||||
})
|
||||
|
||||
watch(visible, async (newVal) => {
|
||||
if (newVal) {
|
||||
imageRefreshTimer = setInterval(() => {
|
||||
imgData.imgUrl = `${imgData.baseUrl}?t=${Date.now()}`
|
||||
}, 1500)
|
||||
} else {
|
||||
// 独立断开电子称和摄像头
|
||||
await Promise.all([
|
||||
leaveCamera(),
|
||||
disconnectScale()
|
||||
])
|
||||
if (imageRefreshTimer) {
|
||||
clearInterval(imageRefreshTimer)
|
||||
imageRefreshTimer = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({ onAdd })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
width: 45%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.weight-input {
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.camera-section {
|
||||
flex: 1;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
width: 55%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -50,6 +50,16 @@
|
||||
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link
|
||||
@click="onAddDetail(record.id)"
|
||||
>
|
||||
新增
|
||||
</a-link>
|
||||
<a-link
|
||||
@click="onViewDetail(record)"
|
||||
>
|
||||
详情
|
||||
</a-link>
|
||||
<a-link
|
||||
v-permission="['fullWorkOrder:fullWorkOrder:delete']"
|
||||
status="danger"
|
||||
@@ -64,11 +74,15 @@
|
||||
</GiTable>
|
||||
|
||||
<FullWorkOrderAddModal ref="FullWorkOrderAddModalRef" @save-success="search" />
|
||||
<FullWorkOrderDetailModal ref="FullWorkOrderDetailModalRef" @save-success="search" />
|
||||
<FullWorkOrderDetailListModal ref="FullWorkOrderDetailListModalRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FullWorkOrderAddModal from './FullWorkOrderAddModal.vue'
|
||||
import FullWorkOrderDetailModal from './FullWorkOrderDetailModal.vue'
|
||||
import FullWorkOrderDetailListModal from './FullWorkOrderDetailListModal.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'
|
||||
@@ -138,11 +152,24 @@ const onExport = () => {
|
||||
}
|
||||
|
||||
const FullWorkOrderAddModalRef = ref<InstanceType<typeof FullWorkOrderAddModal>>()
|
||||
const FullWorkOrderDetailModalRef = ref<InstanceType<typeof FullWorkOrderDetailModal>>()
|
||||
const FullWorkOrderDetailListModalRef = ref<InstanceType<typeof FullWorkOrderDetailListModal>>()
|
||||
|
||||
// 新增
|
||||
const onAdd = () => {
|
||||
FullWorkOrderAddModalRef.value?.onAdd()
|
||||
}
|
||||
|
||||
// 新增原材料详情
|
||||
const onAddDetail = (id: string) => {
|
||||
FullWorkOrderDetailModalRef.value?.onAdd(id)
|
||||
}
|
||||
|
||||
// 查看原材料详情
|
||||
const onViewDetail = (record: FullWorkOrderResp) => {
|
||||
FullWorkOrderDetailListModalRef.value?.onOpen(record.id)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -528,7 +528,7 @@ const workOrderResp = ref<WorkOrderResp>({
|
||||
const inputQuantity = ref()
|
||||
const calculateNumber = ref()
|
||||
// todo
|
||||
const ahDeviceWeight = ref('6')
|
||||
const ahDeviceWeight = ref()
|
||||
const calculatedWeight = ref('')
|
||||
const weighingCount = ref(1)
|
||||
|
||||
@@ -759,9 +759,17 @@ const printLabel = async (labelData: any) => {
|
||||
setTimeout(() => {
|
||||
printWindow.focus()
|
||||
printWindow.print()
|
||||
printWindow.close()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 监听打印完成事件
|
||||
printWindow.onafterprint = () => {
|
||||
printWindow.close()
|
||||
Message.success('打印成功,正在初始化称重管理页面...')
|
||||
// 重新加载页面以初始化称重管理
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,7 +935,7 @@ const handleBackToFirst = () => {
|
||||
// 重置称重登记页面数据
|
||||
inputQuantity.value = ''
|
||||
// todo
|
||||
ahDeviceWeight.value = '6'
|
||||
ahDeviceWeight.value = ''
|
||||
calculatedWeight.value = ''
|
||||
weighingCount.value = 1
|
||||
// 清空称重列表
|
||||
@@ -1014,7 +1022,7 @@ const handleConfirm = async () => {
|
||||
// 重置输入(让用户能继续输入)
|
||||
inputQuantity.value = ''
|
||||
// todo
|
||||
ahDeviceWeight.value = '6'
|
||||
ahDeviceWeight.value = ''
|
||||
calculatedWeight.value = ''
|
||||
weighingCount.value = weighingList.value.length + 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user