first commit

This commit is contained in:
2026-03-05 16:52:12 +08:00
commit 8ca2e6d52f
1899 changed files with 321565 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,490 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="120px">
<!-- <el-form-item label="罐标" prop="canLabel">
<el-input v-model="queryParams.canLabel" placeholder="请输入罐标" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item> -->
<el-form-item label="产品名称" prop="productName">
<el-select v-model="queryParams.productName" placeholder="请选择产品名称" clearable class="!w-240px">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="产品规格" prop="productSpec">
<el-input v-model="queryParams.productSpec" placeholder="请输入产品规格" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="质检员" prop="inspector">
<el-input v-model="queryParams.inspector" placeholder="请输入质检员" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="质检日期" prop="inspectionDate">
<el-date-picker v-model="queryParams.inspectionDate" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
</el-form-item>
<el-form-item label="合格状态" prop="qualifiedStatus">
<el-select v-model="queryParams.qualifiedStatus" placeholder="请选择合格状态" clearable class="!w-240px">
<el-option v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" :key="String(dict.value)"
:label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="质检数据" prop="inspectionData">
<el-input v-model="queryParams.inspectionData" placeholder="请输入质检数据" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="关联单据号" prop="relatedDocumentNo">
<el-input v-model="queryParams.relatedDocumentNo" placeholder="请输入关联单据号" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['iot:quality:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['iot:quality:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
<el-button type="danger" plain :disabled="isEmpty(checkedIds)" @click="handleDeleteBatch"
v-hasPermi="['iot:quality:delete']">
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table row-key="id" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
@selection-change="handleRowCheckboxChange">
<el-table-column type="selection" width="55" />
<!-- <el-table-column label="罐标" align="center" prop="canLabel" /> -->
<el-table-column label="产品名称" align="center" prop="productName" />
<el-table-column label="产品规格" align="center" prop="productSpec" />
<el-table-column label="质检员" align="center" prop="inspector" />
<el-table-column label="质检日期" align="center" prop="inspectionDate" :formatter="dateFormatter2" width="180px" />
<!-- <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> -->
<el-table-column label="合格状态" align="center" prop="qualifiedStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.qualifiedStatus" />
</template>
</el-table-column>
<el-table-column label="附件" align="center" min-width="120px">
<template #default="scope">
<span v-if="scope.row.attachmentName">{{ scope.row.attachmentName }}</span>
<span v-else class="text-gray-400"></span>
</template>
</el-table-column>
<!-- <el-table-column label="关联单据号" align="center" prop="relatedDocumentNo" /> -->
<el-table-column label="操作" align="center" min-width="260px">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['iot:quality:update']">
编辑
</el-button>
<el-button link type="info" @click="openDetail(scope.row)">
质检详情
</el-button>
<el-button
v-if="scope.row.attachmentUrl"
link
type="success"
@click="handleDownloadAttachment(scope.row)"
v-hasPermi="['iot:quality:query']"
>
下载附件
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['iot:quality:delete']">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<QualityForm ref="formRef" :inspection-type="inspectionType" @success="getList" />
<!-- 质检详情弹窗 -->
<Dialog v-model="detailVisible" title="质检详情" width="80%">
<el-table :data="detailData" border>
<el-table-column label="项目名称" prop="itemName" align="center" />
<el-table-column label="检测结果" prop="result" align="center" />
<el-table-column label="检测标准" prop="standard" align="center" />
<el-table-column label="是否合格" prop="qualified" align="center">
<template #default="scope">
<el-tag :type="scope.row.qualified === '合格' || scope.row.qualified === 'true' ? 'success' : 'danger'">
{{ scope.row.qualified === 'true' ? '合格' : scope.row.qualified === 'false' ? '不合格' : scope.row.qualified }}
</el-tag>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import { isEmpty } from '@/utils/is'
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download'
import { QualityApi, Quality } from '@/api/iot/product-inspection'
import QualityForm from './QualityForm.vue'
/** 质检列表通用组件 */
defineOptions({ name: 'QualityInspectionList' })
// 定义props
interface Props {
inspectionType: number // 质检类型1-原料质检2-过程质检3-产品质检
pageTitle?: string // 页面标题,用于导出文件名
}
const props = withDefaults(defineProps<Props>(), {
pageTitle: '质检'
})
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<Quality[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
// 质检详情相关
const detailVisible = ref(false) // 详情弹窗显示状态
const detailData = ref<any[]>([]) // 详情数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
canLabel: undefined,
productName: undefined,
productSpec: undefined,
inspector: undefined,
inspectionDate: [],
createTime: [],
qualifiedStatus: undefined,
inspectionType: props.inspectionType,
inspectionData: undefined,
relatedDocumentNo: undefined
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await QualityApi.getQualityPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 打开质检详情 */
const openDetail = async (row: Quality) => {
try {
// 解析质检数据
let inspectionData = {}
if (row.inspectionData) {
inspectionData = JSON.parse(row.inspectionData)
}
// 构建详情数据
const detailItems: any[] = []
// 检查是否是新的LabView兼容数据结构
if (inspectionData.inspectionItems && Array.isArray(inspectionData.inspectionItems)) {
// 新格式LabView兼容的结构化数据
inspectionData.inspectionItems.forEach((item: any) => {
detailItems.push({
itemName: item.itemName || getFieldDisplayName(item.itemKey),
result: getItemResultLabel(item),
standard: getFieldStandardFromItem(item),
qualified: item.qualified === 'true' ? '合格' : item.qualified === 'false' ? '不合格' : item.qualified || '未设置'
})
})
} else {
// 旧格式:键值对结构(向后兼容)
// 获取产品标准信息
let productStandards: Record<string, any> = {}
if (row.productName) {
try {
const { ProductApi } = await import('@/api/erp/product/product')
// 通过产品名称查找产品
const productList = await ProductApi.getProductSimpleList()
const product = productList.find((item: any) => item.name === row.productName)
if (product) {
const productDetail = await ProductApi.getProduct(product.id)
if (productDetail.jsonField) {
const jsonConfig = JSON.parse(productDetail.jsonField)
if (jsonConfig.items && Array.isArray(jsonConfig.items)) {
// 新格式从FormBuilder配置中获取标准信息
jsonConfig.items.forEach((item: any) => {
if (item.standard || item.standardValue || item.standardRange) {
productStandards[item.key] = {
standard: item.standard,
standardType: item.standardType,
standardValue: item.standardValue,
standardRange: item.standardRange,
standardOptions: item.standardOptions
}
}
})
}
}
}
} catch (error) {
console.warn('获取产品标准信息失败:', error)
}
}
// 遍历质检数据,分离普通字段和合格状态字段
Object.keys(inspectionData).forEach(key => {
if (!key.endsWith('_qualified') && inspectionData[key] !== null && inspectionData[key] !== undefined) {
const qualifiedKey = `${key}_qualified`
const qualifiedValue = inspectionData[qualifiedKey]
detailItems.push({
itemName: getFieldDisplayName(key),
result: inspectionData[key],
standard: getFieldStandard(key, productStandards[key]),
qualified: qualifiedValue === 'true' ? '合格' : qualifiedValue === 'false' ? '不合格' : qualifiedValue || '未设置'
})
}
})
}
detailData.value = detailItems
detailVisible.value = true
} catch (error) {
console.error('解析质检数据失败:', error)
message.error('解析质检数据失败')
}
}
/** 获取字段显示名称 */
const getFieldDisplayName = (key: string): string => {
const nameMap: Record<string, string> = {
quality: '质量等级',
defectCount: '缺陷数量',
remark: '备注',
weight: '重量',
color: '颜色',
purity: '纯度',
moisture: '水分含量',
temperature: '温度',
pressure: '压力',
flowRate: '流量',
status: '状态'
}
return nameMap[key] || key
}
/** 获取检测结果展示值优先用选项label */
const getItemResultLabel = (item: any): string => {
const rawResult = item?.result
if (rawResult === null || rawResult === undefined || rawResult === '') {
return ''
}
const options = Array.isArray(item?.options) ? item.options : []
if (options.length > 0) {
const match = options.find((opt: any) => opt?.value === rawResult)
return match?.label ?? String(rawResult)
}
return String(rawResult)
}
/** 获取字段标准 */
const getFieldStandard = (_key: string, standardInfo?: any): string => {
if (!standardInfo) {
return '未设置标准'
}
// 优先显示标准范围
if (standardInfo.standardRange) {
const { min, max } = standardInfo.standardRange
// 处理不同的范围情况
if (min !== null && min !== undefined && max !== null && max !== undefined) {
// 有最小值和最大值10 - 20
return `${min} - ${max}`
} else if (min !== null && min !== undefined && (max === null || max === undefined)) {
// 只有最小值:≥ 10
return `${min}`
} else if ((min === null || min === undefined) && max !== null && max !== undefined) {
// 只有最大值:≤ 20
return `${max}`
} else {
// 都没有或都是null
return '未设置范围'
}
}
// 如果有标准描述,返回标准描述
if (standardInfo.standard) {
return standardInfo.standard
}
// 如果有标准值,显示标准值
if (standardInfo.standardValue) {
return `标准值: ${standardInfo.standardValue}`
}
// 如果是选择类型,显示可选值
if (standardInfo.standardType === 'select' && standardInfo.standardOptions && standardInfo.standardOptions.length > 0) {
const options = standardInfo.standardOptions.map((opt: any) => opt.label).join('、')
return `可选值: ${options}`
}
return '未设置标准'
}
/** 从LabView兼容的检测项目中获取字段标准 */
const getFieldStandardFromItem = (item: any): string => {
// 优先显示标准范围
if (item.standardRange && (item.standardRange.min !== 0 || item.standardRange.max !== 100)) {
const { min, max } = item.standardRange
// 处理不同的范围情况
if (min !== null && min !== undefined && max !== null && max !== undefined) {
// 有最小值和最大值10 - 20
return `${min} - ${max}`
} else if (min !== null && min !== undefined && (max === null || max === undefined)) {
// 只有最小值:≥ 10
return `${min}`
} else if ((min === null || min === undefined) && max !== null && max !== undefined) {
// 只有最大值:≤ 20
return `${max}`
} else {
// 都没有或都是null
return '未设置范围'
}
}
// 如果有标准描述,返回标准描述
if (item.standard) {
return item.standard
}
// 如果有标准值,显示标准值
if (item.standardValue) {
return `标准值: ${item.standardValue}`
}
// 如果是选择类型,显示可选值
if (item.standardType === 'select' && item.standardOptions && item.standardOptions.length > 0) {
const options = item.standardOptions.map((opt: any) => opt.label).join('、')
return `可选值: ${options}`
}
return '未设置标准'
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await QualityApi.deleteQuality(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch { }
}
/** 批量删除 */
const handleDeleteBatch = async () => {
try {
// 删除的二次确认
await message.delConfirm()
await QualityApi.deleteQualityList(checkedIds.value);
message.success(t('common.delSuccess'))
await getList();
} catch { }
}
const checkedIds = ref<number[]>([])
const handleRowCheckboxChange = (records: Quality[]) => {
checkedIds.value = records.map((item) => item.id);
}
/** 下载附件 */
const handleDownloadAttachment = async (row: any) => {
try {
// 直接使用存储的URL下载文件
if (row.attachmentUrl) {
const link = document.createElement('a')
link.href = row.attachmentUrl
link.style.display = 'none'
link.target = '_blank' // 在新标签页打开
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
message.success('附件下载成功')
} else {
message.error('附件不存在')
}
} catch (error) {
console.error('下载附件失败:', error)
message.error('下载附件失败')
}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await QualityApi.exportQuality(queryParams)
download.excel(data, `定制-上能石化-${props.pageTitle}.xls`)
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>