Files
RCZN-bs-program/rc_autoplc_front/src/views/goods/index.vue
2026-01-19 13:41:14 +08:00

1718 lines
48 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="goodsinfo-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item>流程管理</el-breadcrumb-item>
<el-breadcrumb-item>样品信息管理</el-breadcrumb-item>
</el-breadcrumb>
<!-- 搜索栏 -->
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="120px">
<el-form-item label="样品名称">
<el-input
v-model="queryForm.goodsName"
placeholder="输入样品名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="样品基质类型">
<el-select
v-model="queryForm.goodsType"
placeholder="请选择样品基质类型"
clearable
filterable
style="width: 200px"
>
<el-option
v-for="option in goodsTypeOptions"
:key="option.id"
:label="option.dicValue"
:value="option.dicValue"
/>
</el-select>
</el-form-item>
<el-form-item label="样品状态">
<el-select
v-model="queryForm.goodsStatus"
placeholder="请选择样品状态"
clearable
style="width: 200px"
>
<el-option label="待处理" :value="0" />
<el-option label="处理成功" :value="1" />
<el-option label="处理失败" :value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增样品
</el-button>
</div>
</div>
</el-card>
<!-- 样品信息列表 -->
<el-card shadow="never">
<el-table
:data="tableData"
stripe
border
style="width: 100%"
v-loading="loading"
>
<el-table-column
type="index"
label="序号"
width="60"
:index="getIndex"
/>
<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-button
:type="getStatusType(row.goodsStatus)"
link
@click="handleStatusClick(row)"
>
{{ getStatusText(row.goodsStatus) }}
</el-button>
</template>
</el-table-column>
<el-table-column
label="标准流程"
width="150"
align="center"
>
<template #default="{ row }">
<el-button
:type="getFlowButtonType(row)"
link
@click="openFlowSelectDialog(row)"
>
{{ getFlowButtonText(row) }}
</el-button>
</template>
</el-table-column>
<el-table-column
prop="desc"
label="描述"
min-width="150"
:formatter="formatCell"
/>
<el-table-column label="操作" width="180" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</el-card>
<!-- 新增/编辑抽屉 -->
<el-drawer
v-model="drawerVisible"
:title="drawerTitle"
direction="rtl"
size="600px"
:before-close="handleDrawerClose"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
label-position="left"
>
<el-form-item label="样品名称" prop="goodsName">
<el-input
v-model="formData.goodsName"
placeholder="请输入样品名称"
clearable
/>
</el-form-item>
<el-form-item label="样品基质类型" prop="goodsType">
<div style="display: flex; gap: 8px; width: 100%">
<el-select
v-model="formData.goodsType"
placeholder="请选择样品基质类型"
clearable
filterable
style="flex: 1"
>
<el-option
v-for="option in goodsTypeOptions"
:key="option.id"
:label="option.dicValue"
:value="option.dicValue"
/>
</el-select>
<el-button
type="success"
@click="handleConfigGoodsType"
>
<el-icon><Setting /></el-icon>
配置
</el-button>
</div>
</el-form-item>
<el-form-item label="采样时间" prop="incomeTime">
<el-date-picker
v-model="formData.incomeTime"
type="date"
placeholder="请选择采样时间"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="采样地点">
<el-input
v-model="formData.goodFrom"
placeholder="请输入采样地点"
clearable
/>
</el-form-item>
<el-form-item label="目标检测物">
<el-input
v-model="formData.goalDetect"
placeholder="请输入目标检测物"
clearable
/>
</el-form-item>
<el-form-item label="条形码">
<el-input
v-model="formData.barCode"
placeholder="请输入条形码"
clearable
/>
</el-form-item>
<el-form-item label="描述">
<el-input
v-model="formData.desc"
type="textarea"
:rows="3"
placeholder="请输入描述"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<div class="drawer-footer">
<el-button @click="handleDrawerClose">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
确定
</el-button>
</div>
</template>
</el-drawer>
<!-- 下拉框选项配置对话框 -->
<el-dialog
v-model="selectOptionsDialogVisible"
:title="`[样品基质类型]下拉框选项列表配置`"
width="600px"
:before-close="handleSelectOptionsDialogClose"
>
<div class="select-options-container">
<!-- 输入区域 -->
<div class="select-options-input-area">
<span class="input-label">选项数据</span>
<el-input
v-model="selectOptionInput"
placeholder="请输入选项数据"
clearable
style="flex: 1; margin-right: 8px"
@keyup.enter="handleAddSelectOption"
/>
<el-button type="primary" @click="handleAddSelectOption">添加</el-button>
</div>
<!-- 选项卡片列表 -->
<div class="select-options-cards">
<div
v-for="option in selectOptionsList"
:key="option.id"
class="select-option-card"
>
<span class="option-text">{{ option.dicValue }}</span>
<el-icon
class="option-delete-icon"
@click="handleDeleteSelectOption(option)"
>
<Close />
</el-icon>
</div>
<div v-if="selectOptionsList.length === 0" class="empty-tip">
暂无选项数据请添加
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleSelectOptionsDialogClose">取消</el-button>
<el-button type="primary" @click="handleSaveSelectOptions">保存</el-button>
</div>
</template>
</el-dialog>
<!-- 选择标准流程对话框 -->
<el-dialog
v-model="flowSelectDialogVisible"
title="选择标准流程"
width="1000px"
:close-on-click-modal="false"
:destroy-on-close="true"
>
<el-table
:data="flowSelectTableData"
stripe
border
style="width: 100%"
v-loading="flowSelectLoading"
highlight-current-row
:row-class-name="getFlowRowClassName"
>
<el-table-column
prop="flowIndex"
label="标准流程序号"
width="130"
:formatter="formatCell"
/>
<el-table-column
prop="flowName"
label="标准流程名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="busyCode"
label="业务编号"
min-width="120"
:formatter="formatCell"
/>
<el-table-column
prop="goodsCode"
label="样品编号"
min-width="120"
:formatter="formatCell"
/>
<el-table-column
prop="goodsName"
label="样品名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="checkCode"
label="检样编号"
min-width="120"
:formatter="formatCell"
/>
<el-table-column
prop="programName"
label="项目名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="testMethod"
label="检测方法"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="scanNum"
label="扫描编号"
min-width="120"
:formatter="formatCell"
/>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<div style="display: flex; align-items: center; gap: 8px;">
<el-button type="success" link @click="handleChooseFlow(row)">
选择
</el-button>
<el-tag
v-if="isFlowSelected(row)"
type="success"
size="small"
effect="dark"
class="selected-tag-icon"
>
<el-icon><Check /></el-icon>
</el-tag>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="flowSelectPagination.pageNum"
v-model:page-size="flowSelectPagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="flowSelectTotal"
background
@current-change="handleFlowSelectCurrentChange"
@size-change="handleFlowSelectSizeChange"
/>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleFlowSelectCancel">取消</el-button>
<el-button type="primary" @click="handleFlowSelectSave">
保存
</el-button>
</div>
</template>
</el-dialog>
<!-- 样品流程状态列表弹窗 -->
<el-dialog
v-model="flowStatusDialogVisible"
:title="flowStatusDialogTitle"
width="1200px"
:close-on-click-modal="false"
:destroy-on-close="true"
>
<el-table
:data="flowStatusTableData"
stripe
border
style="width: 100%"
v-loading="flowStatusLoading"
>
<el-table-column
type="index"
label="序号"
width="60"
:index="(index: number) => index + 1"
/>
<el-table-column
prop="islandName"
label="功能岛名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="devName"
label="设备名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="paramName"
label="参数名称"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="paramValue"
label="参数值"
min-width="150"
:formatter="formatCell"
/>
<el-table-column
prop="status"
label="状态"
width="100"
align="center"
>
<template #default="{ row }">
<el-tag :type="getFlowStatusType(row.status)" size="small">
{{ getFlowStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="createTime"
label="执行时间"
width="180"
:formatter="formatDateTime"
/>
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="flowStatusDialogVisible = false">
关闭
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Setting, Close, Check } from '@element-plus/icons-vue'
import {
goodsInfoadd,
goodsInfodel,
goodsInfoupd,
goodsInfolist,
goodsInfobyid,
} from '@/api/tb/goodsinfo'
import { flowInfolist, flowInfobyid } from '@/api/tb/flowinfo'
import { dictypeadd, dictypelist } from '@/api/system/dictype'
import { dicdataadd, dicdatadel, dicdatabydicid } from '@/api/system/dicdata'
import { goodsFlowStatusselect } from '@/api/tb/goodsflowstatus'
import { islandInfobyid } from '@/api/tb/islandinfo'
import { devInfobyid } from '@/api/tb/devinfo'
import { stepInfolist } from '@/api/tb/stepinfo'
import { devparambyid } from '@/api/tb/devparam'
interface GoodsInfoItem {
id?: number | string
goodsName?: string
goodsType?: string
incomeTime?: string
goodFrom?: string
goalDetect?: string
barCode?: string
goodsStatus?: number
desc?: string
remark?: string
flowId?: number | string
flowName?: string
}
interface FlowInfoForSelect {
id?: number | string
flowIndex?: number | string
flowName?: string
busyCode?: string
goodsCode?: string
goodsName?: string
checkCode?: string
programName?: string
testMethod?: string
scanNum?: string
}
const loading = ref(false)
const submitting = ref(false)
const tableData = ref<GoodsInfoItem[]>([])
const total = ref(0)
// 查询表单
const queryForm = reactive({
goodsName: '',
goodsType: '',
goodsStatus: undefined as number | undefined,
})
// 分页
const pagination = reactive({
pageNum: 1,
pageSize: 10,
})
// 抽屉相关
const drawerVisible = ref(false)
const drawerTitle = ref('新增样品')
const isEdit = ref(false)
const formRef = ref<FormInstance>()
// 表单数据
const formData = reactive<GoodsInfoItem>({
id: undefined,
goodsName: '',
goodsType: '',
incomeTime: '',
goodFrom: '',
goalDetect: '',
barCode: '',
goodsStatus: 0, // 状态自动设置为0
desc: '',
})
// 表单验证规则
const formRules: FormRules = {
goodsName: [
{ required: true, message: '请输入样品名称', trigger: 'blur' },
],
goodsType: [
{ required: true, message: '请选择样品基质类型', trigger: 'change' },
],
incomeTime: [
{ required: true, message: '请选择采样时间', trigger: 'change' },
],
}
// 样品基质类型下拉框选项
const goodsTypeOptions = ref<any[]>([])
const goodsTypeDicId = ref<number | null>(null)
// 下拉框选项配置相关
const selectOptionsDialogVisible = ref(false)
const selectOptionInput = ref('')
const selectOptionsList = ref<any[]>([])
// 标准流程选择相关
const flowSelectDialogVisible = ref(false)
const flowSelectLoading = ref(false)
const flowSelectTableData = ref<FlowInfoForSelect[]>([])
const flowSelectTotal = ref(0)
const flowSelectPagination = reactive({
pageNum: 1,
pageSize: 10,
})
const currentGoodsForFlow = ref<GoodsInfoItem | null>(null)
const tempSelectedFlowId = ref<number | string | null>(null)
const tempSelectedFlowName = ref('')
const currentBoundFlowName = ref('')
// 样品流程状态列表相关
const flowStatusDialogVisible = ref(false)
const flowStatusDialogTitle = ref('样品流程状态列表')
const flowStatusLoading = ref(false)
const flowStatusTableData = ref<any[]>([])
const currentGoodsForStatus = ref<GoodsInfoItem | null>(null)
// 获取序号
const getIndex = (index: number) => {
return (pagination.pageNum - 1) * pagination.pageSize + index + 1
}
// 格式化单元格
const formatCell = (_row: any, _column: any, value: any) => {
if (value === 0) return 0
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 getStatusType = (status: number | null | undefined) => {
if (status === 0) return 'primary' // 待处理 - 蓝色
if (status === 1) return 'success' // 处理成功 - 绿色
if (status === 4) return 'danger' // 处理失败 - 红色
return 'info'
}
// 获取状态文本
const getStatusText = (status: number | null | undefined) => {
if (status === 0) return '待处理'
if (status === 1) return '处理成功'
if (status === 4) return '处理失败'
return '未知'
}
// 获取标准流程按钮文本
const getFlowButtonText = (row: GoodsInfoItem) => {
const flowId = (row as any).flowId
if (flowId === undefined || flowId === null || Number(flowId) === 0) {
return '未选择'
}
// 如果flowId存在优先显示flowName
return row.flowName || '标准流程'
}
// 获取标准流程按钮类型
const getFlowButtonType = (row: GoodsInfoItem) => {
const flowId = (row as any).flowId
if (flowId === undefined || flowId === null || Number(flowId) === 0) {
return 'warning' // 橙色
}
return 'success' // 绿色
}
// 样品状态点击事件处理
const handleStatusClick = (row: GoodsInfoItem) => {
const status = row.goodsStatus
if (status === 0) {
// 待处理 - 显示提示
ElMessage.info(`${row.goodsName || '该样品'}等待处理`)
} else if (status === 1 || status === 4) {
// 处理成功或处理失败 - 显示流程状态列表弹窗
currentGoodsForStatus.value = row
flowStatusDialogTitle.value = `${row.goodsName || '样品'}流程状态列表`
flowStatusDialogVisible.value = true
getFlowStatusList(row)
}
}
// 格式化日期时间(年月日时分秒)
const formatDateTime = (_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')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
} catch (error) {
return value
}
}
// 获取流程状态类型
const getFlowStatusType = (status: number | null | undefined) => {
if (status === 0) return 'primary'
if (status === 1) return 'success'
if (status === 4) return 'danger'
return 'info'
}
// 获取流程状态文本
const getFlowStatusText = (status: number | null | undefined) => {
if (status === 0) return '待处理'
if (status === 1) return '处理成功'
if (status === 4) return '处理失败'
return '未知'
}
// 加载样品基质类型下拉框选项
const loadGoodsTypeOptions = async () => {
try {
// 查找或创建"样品基质类型"字典类型
const dicTypeListRes: any = await dictypelist({
pageNum: 1,
pageSize: 1000,
})
const dicTypeData = dicTypeListRes?.data ?? dicTypeListRes ?? {}
const dicTypeRecords = dicTypeData.records || dicTypeData.list || dicTypeData.rows || []
let dicType = dicTypeRecords.find(
(item: any) => item.dicName === '样品基质类型'
)
if (!dicType) {
// 如果不存在,创建新的字典类型
const dicTypeRes: any = await dictypeadd({
dicName: '样品基质类型',
})
if (dicTypeRes?.code === '0' || dicTypeRes?.code === 0 || dicTypeRes?.success) {
const newDicTypeData = dicTypeRes?.data ?? dicTypeRes ?? {}
dicType = { id: newDicTypeData.id }
// 如果返回的数据中没有ID重新查询
if (!dicType.id) {
const refreshListRes: any = await dictypelist({
pageNum: 1,
pageSize: 1000,
})
const refreshData = refreshListRes?.data ?? refreshListRes ?? {}
const refreshRecords = refreshData.records || refreshData.list || refreshData.rows || []
const newDicType = refreshRecords.find(
(item: any) => item.dicName === '样品基质类型'
)
if (newDicType) {
dicType = newDicType
}
}
}
}
if (dicType && dicType.id) {
goodsTypeDicId.value = dicType.id
// 加载选项列表
const optionsRes: any = await dicdatabydicid(dicType.id)
const optionsData = optionsRes?.data ?? optionsRes ?? {}
const optionsRecords = Array.isArray(optionsData) ? optionsData : (optionsData.records || optionsData.list || optionsData.rows || [])
goodsTypeOptions.value = optionsRecords
}
} catch (error) {
console.error('加载样品基质类型选项失败:', error)
}
}
// 获取样品信息列表
const getGoodsInfoList = async () => {
try {
loading.value = true
const params: any = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
}
// 添加查询条件(过滤空值)
if (queryForm.goodsName && queryForm.goodsName.trim()) {
params.goodsName = queryForm.goodsName.trim()
}
if (queryForm.goodsType && queryForm.goodsType.trim()) {
params.goodsType = queryForm.goodsType.trim()
}
if (queryForm.goodsStatus !== undefined && queryForm.goodsStatus !== null) {
params.goodsStatus = queryForm.goodsStatus
}
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但没有flowName的记录获取flowName
const recordsWithFlowName = await Promise.all(
records.map(async (record: GoodsInfoItem) => {
const flowId = (record as any).flowId
if (
flowId !== undefined &&
flowId !== null &&
Number(flowId) !== 0 &&
!record.flowName
) {
try {
const flowRes: any = await flowInfobyid(flowId)
if (flowRes.code === '0' || flowRes.code === 0) {
const flowData = flowRes.data || flowRes || {}
record.flowName = flowData.flowName || ''
}
} catch (error) {
console.error('获取标准流程名称失败:', error)
}
}
return record
})
)
tableData.value = recordsWithFlowName
total.value = Number(totalValue) || 0
} catch (error) {
console.error('获取样品信息列表失败:', error)
ElMessage.error('获取样品信息列表失败')
tableData.value = []
total.value = 0
} finally {
loading.value = false
}
}
// 查询
const handleSearch = () => {
pagination.pageNum = 1
getGoodsInfoList()
}
// 重置查询
const resetSearch = () => {
queryForm.goodsName = ''
queryForm.goodsType = ''
queryForm.goodsStatus = undefined
handleSearch()
}
// 分页大小改变
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getGoodsInfoList()
}
// 当前页改变
const handleCurrentChange = (page: number) => {
pagination.pageNum = page
getGoodsInfoList()
}
// 重置表单
const resetForm = () => {
formData.id = undefined
formData.goodsName = ''
formData.goodsType = ''
formData.incomeTime = ''
formData.goodFrom = ''
formData.goalDetect = ''
formData.barCode = ''
formData.goodsStatus = 0
formData.desc = ''
formRef.value?.clearValidate()
}
// 打开新增抽屉
const handleAdd = () => {
isEdit.value = false
drawerTitle.value = '新增样品'
resetForm()
drawerVisible.value = true
}
// 编辑样品信息
const handleEdit = async (row: GoodsInfoItem) => {
try {
if (!row.id) {
ElMessage.warning('缺少样品信息ID')
return
}
loading.value = true
const res: any = await goodsInfobyid(row.id)
if (res.code === '0' || res.code === 0) {
const data = res.data || res || row
formData.id = data.id ?? row.id
formData.goodsName = data.goodsName ?? row.goodsName ?? ''
formData.goodsType = data.goodsType ?? row.goodsType ?? ''
// 处理日期格式
if (data.incomeTime || row.incomeTime) {
const dateValue = data.incomeTime || row.incomeTime
try {
const date = new Date(dateValue)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
formData.incomeTime = `${year}-${month}-${day}`
} catch (error) {
formData.incomeTime = dateValue
}
} else {
formData.incomeTime = ''
}
formData.goodFrom = data.goodFrom ?? row.goodFrom ?? ''
formData.goalDetect = data.goalDetect ?? row.goalDetect ?? ''
formData.barCode = data.barCode ?? row.barCode ?? ''
formData.goodsStatus = data.goodsStatus ?? row.goodsStatus ?? 0
formData.desc = data.desc ?? row.desc ?? ''
isEdit.value = true
drawerTitle.value = '编辑样品'
drawerVisible.value = true
} else {
ElMessage.error(res.message || res.msg || '获取样品信息失败')
}
} catch (error) {
console.error('获取样品信息失败:', error)
ElMessage.error('获取样品信息失败')
} finally {
loading.value = false
}
}
// 删除样品信息
const handleDelete = async (row: GoodsInfoItem) => {
if (!row.id) {
ElMessage.warning('缺少样品信息ID')
return
}
try {
await ElMessageBox.confirm(
`确定要删除样品信息"${row.goodsName || '未命名'}"吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
loading.value = true
const res: any = await goodsInfodel(row.id)
if (res.code === '0' || res.code === 0) {
ElMessage.success('删除成功')
getGoodsInfoList()
} else {
ElMessage.error(res.message || res.msg || '删除失败')
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除样品信息失败:', error)
ElMessage.error('删除样品信息失败')
}
} finally {
loading.value = false
}
}
// 判断某个标准流程是否被选中
const isFlowSelected = (row: FlowInfoForSelect) => {
if (!row.id || !tempSelectedFlowId.value) return false
return String(row.id) === String(tempSelectedFlowId.value)
}
// 给选中的标准流程行添加样式类
const getFlowRowClassName = ({ row }: { row: FlowInfoForSelect }) => {
if (isFlowSelected(row)) {
return 'selected-flow-row'
}
return ''
}
// 获取标准流程列表(选择弹框用)
const getFlowInfoListForSelect = async () => {
try {
flowSelectLoading.value = true
const params: any = {
pageNum: flowSelectPagination.pageNum,
pageSize: flowSelectPagination.pageSize,
}
const res: any = await flowInfolist(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
flowSelectTableData.value = records
flowSelectTotal.value = Number(totalValue) || 0
} catch (error) {
console.error('获取标准流程列表失败:', error)
ElMessage.error('获取标准流程列表失败')
flowSelectTableData.value = []
flowSelectTotal.value = 0
} finally {
flowSelectLoading.value = false
}
}
// 打开标准流程选择对话框
const openFlowSelectDialog = async (row: GoodsInfoItem) => {
if (!row.id) {
ElMessage.warning('缺少样品信息ID')
return
}
currentGoodsForFlow.value = row
tempSelectedFlowId.value = null
tempSelectedFlowName.value = ''
currentBoundFlowName.value = ''
flowSelectPagination.pageNum = 1
flowSelectPagination.pageSize = 10
// 如果样品已绑定标准流程,读取已绑定流程名称并设置为选中状态
const rawFlowId = (row as any).flowId
if (rawFlowId !== undefined && rawFlowId !== null && Number(rawFlowId) !== 0) {
try {
const res: any = await flowInfobyid(rawFlowId)
if (res.code === '0' || res.code === 0) {
const data = res.data || res || {}
const flowName = data.flowName || row.flowName || ''
currentBoundFlowName.value = flowName
// 设置为已选中状态,这样打开弹框时就会显示选中效果
tempSelectedFlowId.value = rawFlowId
tempSelectedFlowName.value = flowName
}
} catch (error) {
console.error('获取已绑定标准流程信息失败:', error)
}
}
flowSelectDialogVisible.value = true
await getFlowInfoListForSelect()
}
// 标准流程分页变化(页码)
const handleFlowSelectCurrentChange = (page: number) => {
flowSelectPagination.pageNum = page
getFlowInfoListForSelect()
}
// 标准流程分页变化(每页大小)
const handleFlowSelectSizeChange = (size: number) => {
flowSelectPagination.pageSize = size
getFlowInfoListForSelect()
}
// 在弹框中选择某个标准流程
const handleChooseFlow = async (row: FlowInfoForSelect) => {
if (!row.id) {
return
}
const newFlowId = row.id
const newFlowName = row.flowName || ''
// 如果当前没有样品上下文,直接缓存
if (!currentGoodsForFlow.value) {
tempSelectedFlowId.value = newFlowId
tempSelectedFlowName.value = newFlowName
return
}
const existingFlowIdRaw = (currentGoodsForFlow.value as any).flowId
const existingFlowId =
existingFlowIdRaw === undefined || existingFlowIdRaw === null
? 0
: Number(existingFlowIdRaw)
// 如果未绑定或绑定为0直接选择
if (!existingFlowId || existingFlowId === 0) {
tempSelectedFlowId.value = newFlowId
tempSelectedFlowName.value = newFlowName
return
}
// 如果选择的是当前已绑定的流程
if (existingFlowId === Number(newFlowId)) {
tempSelectedFlowId.value = newFlowId
tempSelectedFlowName.value = newFlowName
return
}
// 已经有绑定,选择新的需要确认
const boundName =
currentBoundFlowName.value || tempSelectedFlowName.value || '当前标准流程'
try {
await ElMessageBox.confirm(
`该样品已绑定【${boundName}】,确定换绑吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
tempSelectedFlowId.value = newFlowId
tempSelectedFlowName.value = newFlowName
} catch (error) {
// 取消换绑
}
}
// 取消标准流程选择(清空缓存并关闭对话框)
const handleFlowSelectCancel = () => {
tempSelectedFlowId.value = null
tempSelectedFlowName.value = ''
currentBoundFlowName.value = ''
flowSelectDialogVisible.value = false
}
// 保存标准流程选择(调用更新接口)
const handleFlowSelectSave = async () => {
if (!currentGoodsForFlow.value || !currentGoodsForFlow.value.id) {
ElMessage.warning('缺少样品信息ID无法保存')
return
}
// 如果没有选择任何标准流程,相当于不变更,直接关闭
if (!tempSelectedFlowId.value) {
flowSelectDialogVisible.value = false
return
}
try {
const res: any = await goodsInfoupd({
id: currentGoodsForFlow.value.id,
flowId: tempSelectedFlowId.value,
})
if (res.code === '0' || res.code === 0) {
ElMessage.success('标准流程绑定成功')
// 更新当前行的flowId和flowName
if (currentGoodsForFlow.value) {
currentGoodsForFlow.value.flowId = tempSelectedFlowId.value
currentGoodsForFlow.value.flowName = tempSelectedFlowName.value
// 同时更新tableData中对应的行
const index = tableData.value.findIndex(
(item) => item.id === currentGoodsForFlow.value?.id
)
if (index !== -1 && tableData.value[index]) {
tableData.value[index].flowId = tempSelectedFlowId.value
tableData.value[index].flowName = tempSelectedFlowName.value
}
}
flowSelectDialogVisible.value = false
tempSelectedFlowId.value = null
tempSelectedFlowName.value = ''
currentBoundFlowName.value = ''
// 重新加载样品列表,刷新绑定显示
getGoodsInfoList()
} else {
ElMessage.error(res.message || res.msg || '标准流程绑定失败')
}
} catch (error) {
console.error('保存标准流程绑定失败:', error)
ElMessage.error('保存标准流程绑定失败')
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
submitting.value = true
// 构建提交数据
const submitData: any = {
goodsName: formData.goodsName,
goodsType: formData.goodsType,
incomeTime: formData.incomeTime ? `${formData.incomeTime}T00:00:00` : undefined,
goodFrom: formData.goodFrom || undefined,
goalDetect: formData.goalDetect || undefined,
barCode: formData.barCode || undefined,
desc: formData.desc || undefined,
}
// 移除空字符串字段
Object.keys(submitData).forEach((key) => {
if (submitData[key] === '' || submitData[key] === null) {
delete submitData[key]
}
})
let res: any
if (isEdit.value && formData.id) {
// 编辑 - 需传入样品信息ID,其他字段可选(非空则更新)
res = await goodsInfoupd({
id: formData.id,
...submitData,
})
} else {
// 新增 - 状态自动设置为0
res = await goodsInfoadd({
...submitData,
goodsStatus: 0,
})
}
if (res.code === '0' || res.code === 0) {
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
drawerVisible.value = false
getGoodsInfoList()
} else {
ElMessage.error(res.message || res.msg || (isEdit.value ? '编辑失败' : '新增失败'))
}
} catch (error: any) {
if (error !== false) {
console.error('提交失败:', error)
ElMessage.error('提交失败,请检查表单')
}
} finally {
submitting.value = false
}
}
// 关闭抽屉
const handleDrawerClose = () => {
drawerVisible.value = false
resetForm()
}
// 打开下拉框选项配置对话框
const handleConfigGoodsType = async () => {
try {
if (!goodsTypeDicId.value) {
// 如果字典ID不存在先创建或查找
await loadGoodsTypeOptions()
}
if (!goodsTypeDicId.value) {
ElMessage.warning('无法获取样品基质类型字典ID')
return
}
selectOptionsDialogVisible.value = true
await loadSelectOptions(goodsTypeDicId.value)
} catch (error) {
console.error('打开下拉框配置失败:', error)
ElMessage.error('打开下拉框配置失败')
}
}
// 加载下拉框选项列表
const loadSelectOptions = async (dicId: number) => {
try {
const res: any = await dicdatabydicid(dicId)
const data = res?.data ?? res ?? {}
const records = Array.isArray(data) ? data : (data.records || data.list || data.rows || [])
selectOptionsList.value = records
} catch (error) {
console.error('加载下拉框选项失败:', error)
ElMessage.error('加载下拉框选项失败')
selectOptionsList.value = []
}
}
// 添加下拉框选项
const handleAddSelectOption = async () => {
if (!selectOptionInput.value || selectOptionInput.value.trim() === '') {
ElMessage.warning('请输入选项数据')
return
}
if (!goodsTypeDicId.value) {
ElMessage.error('字典类型ID不存在')
return
}
try {
const res: any = await dicdataadd({
dicId: goodsTypeDicId.value,
dicLabel: '样品基质类型',
dicValue: selectOptionInput.value.trim(),
})
if (res?.code === '0' || res?.code === 0 || res?.success) {
ElMessage.success('添加成功')
selectOptionInput.value = ''
// 重新加载选项列表
await loadSelectOptions(goodsTypeDicId.value)
// 重新加载下拉框选项
await loadGoodsTypeOptions()
} else {
ElMessage.error(res?.message || res?.msg || '添加失败')
}
} catch (error) {
console.error('添加下拉框选项失败:', error)
ElMessage.error('添加下拉框选项失败')
}
}
// 删除下拉框选项
const handleDeleteSelectOption = async (option: any) => {
try {
await ElMessageBox.confirm(
`确定要删除"${option.dicValue || '该选项'}"吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
const res: any = await dicdatadel(option.id)
if (res?.code === '0' || res?.code === 0 || res?.success) {
ElMessage.success('删除成功')
// 重新加载选项列表
if (goodsTypeDicId.value) {
await loadSelectOptions(goodsTypeDicId.value)
// 重新加载下拉框选项
await loadGoodsTypeOptions()
}
} else {
ElMessage.error(res?.message || res?.msg || '删除失败')
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除下拉框选项失败:', error)
ElMessage.error('删除下拉框选项失败')
}
}
}
// 保存下拉框选项配置
const handleSaveSelectOptions = async () => {
if (selectOptionsList.value.length === 0) {
await ElMessageBox.alert(
'样品基质类型的下拉框选项列表还未配置,请先完成配置',
'提示',
{
confirmButtonText: '确定',
type: 'warning',
}
)
return
}
selectOptionsDialogVisible.value = false
ElMessage.success('样品基质类型下拉框选项列表保存成功!')
// 重新加载下拉框选项
await loadGoodsTypeOptions()
}
// 关闭下拉框选项配置对话框
const handleSelectOptionsDialogClose = () => {
selectOptionsDialogVisible.value = false
selectOptionInput.value = ''
selectOptionsList.value = []
}
// 获取样品流程状态列表
const getFlowStatusList = async (row: GoodsInfoItem) => {
if (!row.id) {
ElMessage.warning('缺少样品信息ID')
return
}
try {
flowStatusLoading.value = true
// 调用goodsFlowStatusselect接口传入goodsId
const res: any = await goodsFlowStatusselect({ goodsId: row.id })
const data = res?.data ?? res ?? {}
const records = Array.isArray(data) ? data : (data.records || data.list || data.rows || [])
// 处理每条记录,查询功能岛名称、设备名称、参数名称和参数值
const processedRecords = await Promise.all(
records.map(async (record: any, index: number) => {
const processedRecord = { ...record }
// 查询功能岛名称
if (record.islandId) {
try {
const islandRes: any = await islandInfobyid(record.islandId)
if (islandRes.code === '0' || islandRes.code === 0) {
const islandData = islandRes.data || islandRes || {}
processedRecord.islandName = islandData.islandName || ''
}
} catch (error) {
console.error('获取功能岛名称失败:', error)
processedRecord.islandName = ''
}
} else {
processedRecord.islandName = ''
}
// 查询设备名称
if (record.devId) {
try {
const devRes: any = await devInfobyid(record.devId)
if (devRes.code === '0' || devRes.code === 0) {
const devData = devRes.data || devRes || {}
processedRecord.devName = devData.devName || ''
}
} catch (error) {
console.error('获取设备名称失败:', error)
processedRecord.devName = ''
}
} else {
processedRecord.devName = ''
}
// 查询参数名称和参数值根据flowSort
if (record.flowSort && row.flowId) {
try {
// 根据flowId和flowSort查询步骤信息
const stepRes: any = await stepInfolist({
flowId: row.flowId,
flowSort: record.flowSort,
})
const stepData = stepRes?.data ?? stepRes ?? {}
const stepRecords = Array.isArray(stepData) ? stepData : (stepData.records || stepData.list || stepData.rows || [])
if (stepRecords.length > 0) {
const stepInfo = stepRecords[0]
// 根据步骤信息中的paramId查询参数名称
if (stepInfo.paramId) {
try {
const paramRes: any = await devparambyid(stepInfo.paramId)
if (paramRes.code === '0' || paramRes.code === 0) {
const paramData = paramRes.data || paramRes || {}
processedRecord.paramName = paramData.paramName || ''
} else {
processedRecord.paramName = ''
}
} catch (error) {
console.error('获取参数名称失败:', error)
processedRecord.paramName = ''
}
} else {
processedRecord.paramName = ''
}
// 参数值从流程状态记录中获取
processedRecord.paramValue = record.paramValue || ''
} else {
processedRecord.paramName = ''
processedRecord.paramValue = record.paramValue || ''
}
} catch (error) {
console.error('获取参数信息失败:', error)
processedRecord.paramName = ''
processedRecord.paramValue = record.paramValue || ''
}
} else {
processedRecord.paramName = ''
processedRecord.paramValue = record.paramValue || ''
}
return processedRecord
})
)
flowStatusTableData.value = processedRecords
} catch (error) {
console.error('获取样品流程状态列表失败:', error)
ElMessage.error('获取样品流程状态列表失败')
flowStatusTableData.value = []
} finally {
flowStatusLoading.value = false
}
}
// 初始化
onMounted(() => {
loadGoodsTypeOptions().then(() => {
getGoodsInfoList()
})
})
</script>
<style lang="scss" scoped>
.goodsinfo-page {
display: flex;
flex-direction: column;
gap: 12px;
}
.search-card {
padding-bottom: 30px;
:deep(.el-card__body) {
padding: 16px 20px;
}
}
.search-bar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
:deep(.el-form) {
margin-bottom: 0;
margin-left: -50px;
}
:deep(.el-form-item) {
margin-right: 8px;
margin-bottom: 0;
}
:deep(.el-form-item__label) {
padding-right: 8px;
}
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.select-options-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.select-options-input-area {
display: flex;
align-items: center;
gap: 8px;
}
.input-label {
min-width: 80px;
font-weight: 500;
font-size: 16px;
}
.select-options-cards {
display: flex;
flex-wrap: wrap;
gap: 20px;
min-height: 100px;
max-height: 300px;
overflow-y: auto;
padding: 20px;
background: linear-gradient(135deg, #e3f2fd 0%, #f0f2f5 100%);
border-radius: 4px;
background-image:
linear-gradient(rgba(64, 158, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(64, 158, 255, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
}
.select-option-card {
position: relative;
padding: 16px 20px;
background-color: #fff;
border: 1px solid rgba(64, 158, 255, 0.1);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: default;
transition: all 0.3s;
min-width: 120px;
font-size: 16px;
}
.select-option-card:hover {
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
transform: translateY(-4px);
border-color: rgba(64, 158, 255, 0.3);
}
.option-text {
font-size: 1.2em;
color: #303133;
font-weight: bold;
}
.option-delete-icon {
position: absolute;
top: 10px;
right: 10px;
font-size: 16px;
color: #909399;
cursor: pointer;
transition: color 0.3s;
padding: 4px;
}
.option-delete-icon:hover {
color: #f56c6c;
}
.empty-tip {
width: 100%;
text-align: center;
color: #909399;
font-size: 16px;
padding: 20px 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
// 标准流程选择表格 - 选中行的样式
:deep(.selected-flow-row) {
background-color: #e8f4ff !important;
td {
background-color: #e8f4ff !important;
}
&:hover > td {
background-color: #d9ecff !important;
}
}
// 已选择标签 - 正方形小绿块样式
.selected-tag-icon {
width: 24px;
height: 24px;
padding: 0 !important;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
.el-icon {
margin: 0;
font-size: 14px;
}
}
</style>