样品信息管理

This commit is contained in:
Lxq
2026-01-14 14:04:08 +08:00
parent 7c2678429c
commit 36e0644b5d
7 changed files with 1942 additions and 2 deletions

View File

@@ -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',
})
}

View File

@@ -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',
})
}

View File

@@ -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',

View File

@@ -84,6 +84,10 @@
<el-icon><List /></el-icon>
<span>流程管理</span>
</template>
<el-menu-item index="/goods-info">
<el-icon><Filter /></el-icon>
<span>样品管理</span>
</el-menu-item>
<el-menu-item index="/flow-info">
<el-icon><component :is="getMenuIcon('标准流程管理')" /></el-icon>
<span>标准流程管理</span>
@@ -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()

View File

@@ -93,7 +93,7 @@
min-width="120"
:formatter="formatCell"
/>
<el-table-column label="操作" width="180" fixed="right">
<el-table-column label="操作" width="260" fixed="right">
<template #default="scope">
<el-button
:type="scope.row.hasWorkflow ? 'success' : 'warning'"
@@ -103,6 +103,7 @@
>
{{ scope.row.hasWorkflow ? '编辑流程' : '配置流程' }}
</el-button>
<el-button type="info" link @click="handleSelectGoods(scope.row)">选择样品</el-button>
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
@@ -210,6 +211,106 @@
</div>
</template>
</el-drawer>
<!-- 选择样品对话框 -->
<el-dialog
v-model="goodsSelectDialogVisible"
title="选择样品"
width="1000px"
:before-close="handleGoodsSelectDialogClose"
>
<el-table
ref="goodsTableRef"
v-loading="goodsSelectLoading"
:data="goodsSelectTableData"
border
stripe
style="width: 100%"
@selection-change="handleGoodsSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column
type="index"
label="序号"
width="60"
:index="getGoodsIndex"
/>
<el-table-column
prop="goodsName"
label="样品名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="goodsType"
label="样品基质类型"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="incomeTime"
label="采样时间"
min-width="120"
:formatter="formatDate"
/>
<el-table-column
prop="goodFrom"
label="采样地点"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="goalDetect"
label="目标检测物"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="barCode"
label="条形码"
min-width="120"
:formatter="formatCell"
/>
<el-table-column
prop="goodsStatus"
label="样品状态"
width="120"
align="center"
>
<template #default="{ row }">
<el-tag :type="getGoodsStatusType(row.goodsStatus)" size="small">
{{ getGoodsStatusText(row.goodsStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="desc"
label="描述"
min-width="150"
:formatter="formatCell"
/>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="goodsSelectPagination.pageNum"
v-model:page-size="goodsSelectPagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="goodsSelectTotal"
background
@current-change="handleGoodsSelectCurrentChange"
@size-change="handleGoodsSelectSizeChange"
/>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleGoodsSelectDialogClose">取消</el-button>
<el-button type="primary" @click="handleSaveGoodsBind" :loading="savingGoodsBind">
保存
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -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<FlowInfoItem[]>([])
const total = ref(0)
// 选择样品相关
const goodsSelectDialogVisible = ref(false)
const goodsSelectLoading = ref(false)
const savingGoodsBind = ref(false)
const goodsSelectTableData = ref<GoodsInfoItem[]>([])
const goodsSelectTotal = ref(0)
const goodsSelectPagination = reactive({
pageNum: 1,
pageSize: 10,
})
const goodsTableRef = ref()
const selectedGoods = ref<GoodsInfoItem[]>([])
const boundGoods = ref<GoodsInfoItem[]>([])
const currentFlowForGoods = ref<FlowInfoItem | null>(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;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -15,4 +15,8 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
host: '0.0.0.0', // 允许外部访问
port: 5173, // 端口号
},
})