From 36e0644b5d75128e3a1a76d5886a3d492c325302 Mon Sep 17 00:00:00 2001 From: Lxq <19852720163@163.com> Date: Wed, 14 Jan 2026 14:04:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B7=E5=93=81=E4=BF=A1=E6=81=AF=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/tb/goodsflowstatus.ts | 47 + rc_autoplc_front/src/api/tb/goodsinfo.ts | 39 + rc_autoplc_front/src/router/index.ts | 5 + rc_autoplc_front/src/views/Layout.vue | 6 +- rc_autoplc_front/src/views/flowinfo/index.vue | 445 +++++- rc_autoplc_front/src/views/goods/index.vue | 1398 +++++++++++++++++ rc_autoplc_front/vite.config.ts | 4 + 7 files changed, 1942 insertions(+), 2 deletions(-) create mode 100644 rc_autoplc_front/src/api/tb/goodsflowstatus.ts create mode 100644 rc_autoplc_front/src/api/tb/goodsinfo.ts create mode 100644 rc_autoplc_front/src/views/goods/index.vue diff --git a/rc_autoplc_front/src/api/tb/goodsflowstatus.ts b/rc_autoplc_front/src/api/tb/goodsflowstatus.ts new file mode 100644 index 0000000..b36276b --- /dev/null +++ b/rc_autoplc_front/src/api/tb/goodsflowstatus.ts @@ -0,0 +1,47 @@ +import request from '@/utils/request' + +export function goodsFlowStatusadd(data: any) { + return request({ + url: '/goodsFlowStatus/add', + method: 'post', + data, + }) +} + +export function goodsFlowStatusdel(id: string | number) { + return request({ + url: `/goodsFlowStatus/del/${id}`, + method: 'delete', + }) +} + +export function goodsFlowStatusupd(data: any) { + return request({ + url: '/goodsFlowStatus/update', + method: 'put', + data, + }) +} + +export function goodsFlowStatuslist(data: any) { + return request({ + url: '/goodsFlowStatus/listPage', + method: 'get', + params: data, + }) +} + +export function goodsFlowStatusselect(data: any) { + return request({ + url: '/goodsFlowStatus/list', + method: 'get', + params: data, + }) +} + +export function goodsFlowStatusbyid(id: string | number) { + return request({ + url: `/goodsFlowStatus/getById/${id}`, + method: 'get', + }) +} \ No newline at end of file diff --git a/rc_autoplc_front/src/api/tb/goodsinfo.ts b/rc_autoplc_front/src/api/tb/goodsinfo.ts new file mode 100644 index 0000000..0c0b42c --- /dev/null +++ b/rc_autoplc_front/src/api/tb/goodsinfo.ts @@ -0,0 +1,39 @@ +import request from '@/utils/request' + +export function goodsInfoadd(data: any) { + return request({ + url: '/goodsInfo/add', + method: 'post', + data, + }) +} + +export function goodsInfodel(id: string | number) { + return request({ + url: `/goodsInfo/del/${id}`, + method: 'delete', + }) +} + +export function goodsInfoupd(data: any) { + return request({ + url: '/goodsInfo/update', + method: 'put', + data, + }) +} + +export function goodsInfolist(data: any) { + return request({ + url: '/goodsInfo/listPage', + method: 'get', + params: data, + }) +} + +export function goodsInfobyid(id: string | number) { + return request({ + url: `/goodsInfo/getById/${id}`, + method: 'get', + }) +} \ No newline at end of file diff --git a/rc_autoplc_front/src/router/index.ts b/rc_autoplc_front/src/router/index.ts index b74f227..e1aefe7 100644 --- a/rc_autoplc_front/src/router/index.ts +++ b/rc_autoplc_front/src/router/index.ts @@ -52,6 +52,11 @@ const router = createRouter({ name: 'plc-devinfo', component: () => import('../views/devinfo/plc.vue'), }, + { + path: '/goods-info', + name: 'goods-info', + component: () => import('../views/goods/index.vue'), + }, { path: '/flow-info', name: 'flow-info', diff --git a/rc_autoplc_front/src/views/Layout.vue b/rc_autoplc_front/src/views/Layout.vue index 61c5cc3..e6c95b0 100644 --- a/rc_autoplc_front/src/views/Layout.vue +++ b/rc_autoplc_front/src/views/Layout.vue @@ -84,6 +84,10 @@ 流程管理 + + + 样品管理 + 标准流程管理 @@ -108,7 +112,7 @@ import { ref, computed } from 'vue' import { useRouter, useRoute } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' -import { User, Setting, Avatar, OfficeBuilding, Briefcase, Document, CaretBottom, UserFilled, Grid, Monitor, Connection, List, EditPen, Files, Tickets, Management, FolderOpened } from '@element-plus/icons-vue' +import { User, Setting, Avatar, OfficeBuilding, Briefcase, Document, CaretBottom, UserFilled, Grid, Monitor, Connection, List, EditPen, Files, Tickets, Management, FolderOpened, Box, Filter } from '@element-plus/icons-vue' import { useAuthStore } from '@/stores/auth' const router = useRouter() diff --git a/rc_autoplc_front/src/views/flowinfo/index.vue b/rc_autoplc_front/src/views/flowinfo/index.vue index 8a0870b..d23e1c4 100644 --- a/rc_autoplc_front/src/views/flowinfo/index.vue +++ b/rc_autoplc_front/src/views/flowinfo/index.vue @@ -93,7 +93,7 @@ min-width="120" :formatter="formatCell" /> - + @@ -210,6 +211,106 @@ + + + + + + + + + + + + + + + + + + + + @@ -227,6 +328,7 @@ import { flowInfobyid, } from '@/api/tb/flowinfo' import { stepInfolist } from '@/api/tb/stepinfo' +import { goodsInfolist, goodsInfoupd } from '@/api/tb/goodsinfo' interface FlowInfoItem { id?: number | string @@ -244,12 +346,40 @@ interface FlowInfoItem { workflowLoading?: boolean } +interface GoodsInfoItem { + id?: number | string + goodsName?: string + goodsType?: string + incomeTime?: string + goodFrom?: string + goalDetect?: string + barCode?: string + goodsStatus?: number + desc?: string + flowId?: number | string +} + const router = useRouter() const loading = ref(false) const submitting = ref(false) const tableData = ref([]) const total = ref(0) +// 选择样品相关 +const goodsSelectDialogVisible = ref(false) +const goodsSelectLoading = ref(false) +const savingGoodsBind = ref(false) +const goodsSelectTableData = ref([]) +const goodsSelectTotal = ref(0) +const goodsSelectPagination = reactive({ + pageNum: 1, + pageSize: 10, +}) +const goodsTableRef = ref() +const selectedGoods = ref([]) +const boundGoods = ref([]) +const currentFlowForGoods = ref(null) + // 查询表单 const queryForm = reactive({ flowName: '', @@ -316,6 +446,41 @@ const formatCell = (_row: any, _column: any, value: any) => { return value === undefined || value === null || value === '' ? '暂无' : value } +// 格式化日期(只显示年月日) +const formatDate = (_row: any, _column: any, value: any) => { + if (!value) return '暂无' + try { + const date = new Date(value) + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + } catch (error) { + return value + } +} + +// 获取样品状态类型 +const getGoodsStatusType = (status: number | null | undefined) => { + if (status === 0) return 'primary' // 待处理 - 蓝色 + if (status === 1) return 'success' // 处理成功 - 绿色 + if (status === 4) return 'danger' // 处理失败 - 红色 + return 'info' +} + +// 获取样品状态文本 +const getGoodsStatusText = (status: number | null | undefined) => { + if (status === 0) return '待处理' + if (status === 1) return '处理成功' + if (status === 4) return '处理失败' + return '未知' +} + +// 获取样品序号 +const getGoodsIndex = (index: number) => { + return (goodsSelectPagination.pageNum - 1) * goodsSelectPagination.pageSize + index + 1 +} + // 获取流程信息列表 const getFlowInfoList = async () => { try { @@ -602,6 +767,276 @@ const handleDrawerClose = () => { resetForm() } +// 加载已绑定的样品 +const loadBoundGoods = async (flowId: number | string) => { + try { + const res: any = await goodsInfolist({ + pageNum: 1, + pageSize: 1000, + }) + const allGoods = res?.data ?? res ?? {} + const recordsFromData = + allGoods.records || + allGoods.list || + allGoods.rows || + allGoods.items || + (Array.isArray(allGoods.data) ? allGoods.data : undefined) + const allGoodsList = Array.isArray(recordsFromData) + ? recordsFromData + : Array.isArray(allGoods) + ? allGoods + : [] + if (Array.isArray(allGoodsList)) { + boundGoods.value = allGoodsList.filter((goods: any) => + goods.flowId && (goods.flowId === flowId || goods.flowId === String(flowId)) + ) + } + } catch (error) { + console.error('加载已绑定样品失败:', error) + boundGoods.value = [] + } +} + +// 获取样品列表(选择弹框用) +const getGoodsInfoListForSelect = async () => { + try { + goodsSelectLoading.value = true + const params: any = { + pageNum: goodsSelectPagination.pageNum, + pageSize: goodsSelectPagination.pageSize, + } + + const res: any = await goodsInfolist(params) + const data = res?.data ?? res ?? {} + + const recordsFromData = + data.records || + data.list || + data.rows || + data.items || + (Array.isArray(data.data) ? data.data : undefined) + + const records = Array.isArray(recordsFromData) + ? recordsFromData + : Array.isArray(data) + ? data + : [] + + const totalValue = + data.total ?? data.count ?? data.totalCount ?? records.length ?? 0 + + // 过滤样品:只显示flowId为0或空,或者flowId等于当前流程ID的样品 + if (currentFlowForGoods.value?.id) { + const currentFlowId = currentFlowForGoods.value.id + goodsSelectTableData.value = records.filter((goods: any) => { + const flowId = goods.flowId + return ( + !flowId || + flowId === 0 || + flowId === '0' || + flowId === currentFlowId || + flowId === String(currentFlowId) + ) + }) + // 重新计算总数(实际应该后端过滤,这里简化处理) + goodsSelectTotal.value = records.filter((goods: any) => { + const flowId = goods.flowId + return ( + !flowId || + flowId === 0 || + flowId === '0' || + flowId === currentFlowId || + flowId === String(currentFlowId) + ) + }).length + } else { + goodsSelectTableData.value = records + goodsSelectTotal.value = Number(totalValue) || 0 + } + } catch (error) { + console.error('获取样品列表失败:', error) + ElMessage.error('获取样品列表失败') + goodsSelectTableData.value = [] + goodsSelectTotal.value = 0 + } finally { + goodsSelectLoading.value = false + } +} + +// 打开选择样品对话框 +const handleSelectGoods = async (row: FlowInfoItem) => { + if (!row.id) { + ElMessage.warning('缺少流程信息ID') + return + } + + currentFlowForGoods.value = row + selectedGoods.value = [] + boundGoods.value = [] + goodsSelectPagination.pageNum = 1 + goodsSelectPagination.pageSize = 10 + + // 加载已绑定的样品 + await loadBoundGoods(row.id) + + goodsSelectDialogVisible.value = true + await getGoodsInfoListForSelect() + + // 等待DOM更新后设置已选中的样品 + await new Promise(resolve => setTimeout(resolve, 100)) + if (goodsTableRef.value && boundGoods.value.length > 0) { + const boundGoodsIds = boundGoods.value.map((g: any) => g.id) + goodsSelectTableData.value.forEach((goods: any) => { + if (boundGoodsIds.includes(goods.id)) { + goodsTableRef.value.toggleRowSelection(goods, true) + } + }) + } +} + +// 样品选择变化 +const handleGoodsSelectionChange = (selection: GoodsInfoItem[]) => { + selectedGoods.value = selection +} + +// 样品分页变化(页码) +const handleGoodsSelectCurrentChange = (page: number) => { + goodsSelectPagination.pageNum = page + getGoodsInfoListForSelect().then(() => { + // 等待DOM更新后重新设置已选中的样品 + setTimeout(() => { + if (goodsTableRef.value && boundGoods.value.length > 0) { + const boundGoodsIds = boundGoods.value.map((g: any) => g.id) + goodsSelectTableData.value.forEach((goods: any) => { + if (boundGoodsIds.includes(goods.id)) { + goodsTableRef.value.toggleRowSelection(goods, true) + } + }) + } + }, 100) + }) +} + +// 样品分页变化(每页大小) +const handleGoodsSelectSizeChange = (size: number) => { + goodsSelectPagination.pageSize = size + getGoodsInfoListForSelect().then(() => { + // 等待DOM更新后重新设置已选中的样品 + setTimeout(() => { + if (goodsTableRef.value && boundGoods.value.length > 0) { + const boundGoodsIds = boundGoods.value.map((g: any) => g.id) + goodsSelectTableData.value.forEach((goods: any) => { + if (boundGoodsIds.includes(goods.id)) { + goodsTableRef.value.toggleRowSelection(goods, true) + } + }) + } + }, 100) + }) +} + +// 保存样品绑定 +const handleSaveGoodsBind = async () => { + if (!currentFlowForGoods.value || !currentFlowForGoods.value.id) { + ElMessage.warning('缺少流程信息ID,无法保存') + return + } + + const flowId = currentFlowForGoods.value.id + + try { + savingGoodsBind.value = true + + // 获取当前选中的样品ID列表 + const selectedGoodsIds = selectedGoods.value.map((g: any) => g.id) + + // 获取之前已绑定的样品ID列表 + const previousBoundGoodsIds = boundGoods.value.map((g: any) => g.id) + + // 需要绑定的样品(新增的) + const goodsToBind = selectedGoodsIds.filter((id: any) => !previousBoundGoodsIds.includes(id)) + + // 需要解绑的样品(取消选中的) + const goodsToUnbind = previousBoundGoodsIds.filter((id: any) => !selectedGoodsIds.includes(id)) + + // 执行绑定操作 + const bindPromises = goodsToBind.map((goodsId: any) => { + const goods = goodsSelectTableData.value.find((g: any) => g.id === goodsId) || + boundGoods.value.find((g: any) => g.id === goodsId) + if (goods) { + return goodsInfoupd({ + id: goodsId, + flowId: flowId, + }) + } + return Promise.resolve() + }) + + // 执行解绑操作 + const unbindPromises = goodsToUnbind.map((goodsId: any) => { + const goods = goodsSelectTableData.value.find((g: any) => g.id === goodsId) || + boundGoods.value.find((g: any) => g.id === goodsId) + if (goods) { + return goodsInfoupd({ + id: goodsId, + flowId: 0, // 解绑时设置为0 + }) + } + return Promise.resolve() + }) + + // 执行所有操作 + const allPromises = [...bindPromises, ...unbindPromises] + if (allPromises.length > 0) { + const results = await Promise.all(allPromises) + // 检查是否有失败的请求 + const failedResults = results.filter((res: any) => { + if (!res) return false + const data = res.data || res + return data && (data.code !== '0' && data.code !== 0 && data.code !== undefined && !data.success) + }) + if (failedResults.length > 0) { + const firstFailed = failedResults[0] + const errorData = firstFailed?.data || firstFailed + throw new Error(errorData?.message || errorData?.msg || '部分样品绑定/解绑失败') + } + } + + ElMessage.success('保存成功') + goodsSelectDialogVisible.value = false + selectedGoods.value = [] + boundGoods.value = [] + if (goodsTableRef.value) { + goodsTableRef.value.clearSelection() + } + } catch (error: any) { + console.error('保存样品绑定失败:', error) + let errorMessage = '保存样品绑定失败' + if (error?.message) { + errorMessage = error.message + } else if (error?.response?.data?.message) { + errorMessage = error.response.data.message + } else if (error?.response?.data?.msg) { + errorMessage = error.response.data.msg + } else if (typeof error === 'string') { + errorMessage = error + } + ElMessage.error(errorMessage) + } finally { + savingGoodsBind.value = false + } +} + +// 关闭选择样品对话框 +const handleGoodsSelectDialogClose = () => { + goodsSelectDialogVisible.value = false + selectedGoods.value = [] + boundGoods.value = [] + if (goodsTableRef.value) { + goodsTableRef.value.clearSelection() + } +} + // 初始化 onMounted(() => { getFlowInfoList() @@ -644,4 +1079,12 @@ onMounted(() => { justify-content: flex-end; gap: 10px; } + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 10px; +} + + diff --git a/rc_autoplc_front/src/views/goods/index.vue b/rc_autoplc_front/src/views/goods/index.vue new file mode 100644 index 0000000..59d28b5 --- /dev/null +++ b/rc_autoplc_front/src/views/goods/index.vue @@ -0,0 +1,1398 @@ + + + + + diff --git a/rc_autoplc_front/vite.config.ts b/rc_autoplc_front/vite.config.ts index 4217010..cc06fd8 100644 --- a/rc_autoplc_front/vite.config.ts +++ b/rc_autoplc_front/vite.config.ts @@ -15,4 +15,8 @@ export default defineConfig({ '@': fileURLToPath(new URL('./src', import.meta.url)) }, }, + server: { + host: '0.0.0.0', // 允许外部访问 + port: 5173, // 端口号 + }, })