feat:质量管理-检验方案对接

main
黄伟杰 2 weeks ago
parent e4b82b4b26
commit 0061fb2a3c

@ -5,6 +5,7 @@ export interface ZjItemVO {
id: number // ID
zjType: number // 检验类型
name: string // 名称
zjTypeName?: string
remark: string // 备注
tool: string // 作业方式
standardVal: number // 标准值
@ -20,6 +21,10 @@ export const ZjItemApi = {
return await request.get({ url: `/mes/zj-item/page`, params })
},
getZjItemList: async () => {
return await request.get({ url: `/mes/zj-item/list` })
},
// 查询质量管理-检验项目详情
getZjItem: async (id: number) => {
return await request.get({ url: `/mes/zj-item/get?id=` + id })

@ -2,12 +2,13 @@ import request from '@/config/axios'
// 检验方案 VO
export interface ZjSchemaVO {
type: string // 类型
name: string // 名称
remark: string // 备注
sampleMethod: string // 抽检方式
val: number // 值
item: string // 关联项目
id: number
type: string
name: string
remark: string
sampleMethod: string
val: string
item: string
}
// 检验方案 API
@ -41,4 +42,8 @@ export const ZjSchemaApi = {
exportZjSchema: async (params) => {
return await request.download({ url: `/mes/zj-schema/export-excel`, params })
},
getItemList: async (id: number) => {
return await request.get({ url: `/mes/zj-schema/getItemList?id=` + id })
},
}

@ -1,31 +1,50 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择类型">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
<el-form-item label="类型" prop="type">
<el-radio-group v-model="formData.type">
<el-radio v-for="dict in getDictOptions('mes_zj_schema_type')" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="抽检方式" prop="sampleMethod">
<el-input v-model="formData.sampleMethod" placeholder="请输入抽检方式" />
<el-form-item v-if="isSampleType" label="抽检方式" prop="sampleMethod">
<el-radio-group v-model="formData.sampleMethod">
<el-radio v-for="dict in getDictOptions('mes_zj_schema_sample_method')" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="值" prop="val">
<el-input v-model="formData.val" placeholder="请输入值" />
<el-form-item v-if="isSampleType" label="值" prop="val">
<template v-if="isRateMethod">
<el-input-number v-model="rateVal" :min="0" class="!w-160px" />
<span class="ml-8px">%</span>
</template>
<template v-else-if="isGapMethod">
<span>每间隔</span>
<el-input-number v-model="gapInterval" :min="1" class="!w-120px mx-8px" :controls="false" />
<span>抽取</span>
<el-input-number v-model="gapCount" :min="1" class="!w-120px ml-8px" :controls="false" />
</template>
<template v-else>
<el-input v-model="formData.val" placeholder="请输入值" />
</template>
</el-form-item>
<el-form-item label="关联项目" prop="item">
<el-input v-model="formData.item" placeholder="请输入关联项目" />
<el-row :gutter="8" class="w-full">
<el-col :span="22">
<el-input :model-value="displayItemNames" placeholder="请选择检验项目" readonly />
</el-col>
<el-col :span="2" class="text-right">
<el-button type="primary" @click="openItemDialog"></el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
@ -33,9 +52,79 @@
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
<el-dialog v-model="itemDialogVisible" title="选择检验项目" width="920px" draggable>
<div v-loading="itemLoading">
<div class="formula-config-picklist">
<div class="formula-config-panel">
<div class="formula-config-panel__header">
<div>来源</div>
<div class="formula-config-panel__meta">{{ filteredSourceItems.length }}</div>
</div>
<div class="formula-config-panel__filter">
<el-input v-model="sourceKeyword" placeholder="筛选" clearable />
</div>
<el-table
class="formula-config-panel__table"
:data="filteredSourceItems"
height="440"
row-key="key"
@selection-change="handleSourceSelectionChange"
>
<el-table-column type="selection" width="44" />
<el-table-column label="名称" prop="name" />
<el-table-column label="检验类型">
<template #default="scope">
{{ scope.row.zjTypeName }}
</template>
</el-table-column>
</el-table>
</div>
<div class="formula-config-actions">
<el-button type="primary" :disabled="!sourceCheckedKeys.length" @click="addToTarget">
&gt;&gt;
</el-button>
<el-button :disabled="!targetCheckedKeys.length" @click="removeFromTarget">
&lt;&lt;
</el-button>
</div>
<div class="formula-config-panel">
<div class="formula-config-panel__header">
<div>目标</div>
<div class="formula-config-panel__meta">{{ filteredTargetItems.length }}</div>
</div>
<div class="formula-config-panel__filter">
<el-input v-model="targetKeyword" placeholder="筛选" clearable />
</div>
<el-table
class="formula-config-panel__table"
:data="filteredTargetItems"
height="440"
row-key="key"
@selection-change="handleTargetSelectionChange"
>
<el-table-column type="selection" width="44" />
<el-table-column label="名称" prop="name" />
<el-table-column label="检验类型">
<template #default="scope">
{{ scope.row.zjTypeName }}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<template #footer>
<el-button @click="itemDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmSelectItems"> </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { getDictOptions } from '@/utils/dict'
import { ZjSchemaApi, ZjSchemaVO } from '@/api/mes/zjschema'
import { ZjItemApi, ZjItemVO } from '@/api/mes/zjitem'
/** 检验方案 表单 */
defineOptions({ name: 'ZjSchemaForm' })
@ -48,18 +137,183 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
type: undefined,
name: undefined,
remark: undefined,
sampleMethod: undefined,
val: undefined,
item: undefined,
type: 'All' as string | undefined,
name: undefined as string | undefined,
remark: undefined as string | undefined,
sampleMethod: undefined as string | undefined,
val: undefined as string | undefined,
item: undefined as string | undefined,
})
const rateVal = ref<number | undefined>(undefined)
const gapInterval = ref<number | undefined>(undefined)
const gapCount = ref<number | undefined>(undefined)
const itemDialogVisible = ref(false)
const itemLoading = ref(false)
const itemList = ref<ZjItemVO[]>([])
const selectedItemIds = ref<number[]>([])
const sourceKeyword = ref('')
const targetKeyword = ref('')
const sourceCheckedKeys = ref<number[]>([])
const targetCheckedKeys = ref<number[]>([])
const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
sampleMethod: [{ required: true, message: '抽检方式不能为空', trigger: 'change' }],
val: [{ validator: (_: any, __: any, callback: any) => validateVal(callback), trigger: 'blur' }],
})
const formRef = ref() // Ref
const isSampleType = computed(() => {
const type = formData.value.type
return type === 'Sample'
})
const isRateMethod = computed(() => formData.value.sampleMethod === 'Rate')
const isGapMethod = computed(() => formData.value.sampleMethod === 'Gap')
const displayItemNames = computed(() => {
if (!selectedItemIds.value.length) return ''
if (!itemList.value.length) {
return formData.value.item ? String(formData.value.item) : ''
}
const map = new Map(itemList.value.map((item) => [item.id, item.name]))
const names = selectedItemIds.value
.map((id) => map.get(id))
.filter((name) => name)
return names.join(',')
})
const allItems = computed(() =>
itemList.value.map((item) => {
const typeName = item.zjTypeName || ''
const name = item.name || ''
const label = typeName ? `${typeName}-${name}` : name
return {
...item,
key: item.id,
label,
}
})
)
const sourceItems = computed(() =>
allItems.value.filter((item) => !selectedItemIds.value.includes(item.id))
)
const targetItems = computed(() =>
allItems.value.filter((item) => selectedItemIds.value.includes(item.id))
)
const filteredSourceItems = computed(() => {
const keyword = sourceKeyword.value.trim()
if (!keyword) return sourceItems.value
return sourceItems.value.filter((item) => item.label?.includes(keyword))
})
const filteredTargetItems = computed(() => {
const keyword = targetKeyword.value.trim()
if (!keyword) return targetItems.value
return targetItems.value.filter((item) => item.label?.includes(keyword))
})
const validateVal = (callback: any) => {
if (!isSampleType.value) {
callback()
return
}
if (isRateMethod.value) {
if (rateVal.value === undefined || rateVal.value === null) {
callback(new Error('值不能为空'))
return
}
} else if (isGapMethod.value) {
if (!gapInterval.value || !gapCount.value) {
callback(new Error('值不能为空'))
return
}
} else if (!formData.value.val) {
callback(new Error('值不能为空'))
return
}
callback()
}
watch(
() => formData.value.type,
(val) => {
if (val === 'Sample') {
if (!formData.value.sampleMethod) {
const opts = getDictOptions('mes_zj_schema_sample_method')
if (opts && opts.length > 0) {
formData.value.sampleMethod = String(opts[0].value)
}
}
return
}
formData.value.sampleMethod = undefined
formData.value.val = undefined
rateVal.value = undefined
gapInterval.value = undefined
gapCount.value = undefined
formRef.value?.clearValidate?.(['sampleMethod', 'val'])
}
)
const openItemDialog = async () => {
itemDialogVisible.value = true
if (!itemList.value.length) {
itemLoading.value = true
try {
itemList.value = await ZjItemApi.getZjItemList()
} finally {
itemLoading.value = false
}
}
initSelectedItemIds()
sourceCheckedKeys.value = []
targetCheckedKeys.value = []
}
const initSelectedItemIds = () => {
if (!formData.value.item) {
selectedItemIds.value = []
return
}
const ids = String(formData.value.item)
.split(',')
.map((v) => Number(v.trim()))
.filter((v) => !Number.isNaN(v))
selectedItemIds.value = ids
}
const confirmSelectItems = () => {
formData.value.item = selectedItemIds.value.join(',')
itemDialogVisible.value = false
}
const handleSourceSelectionChange = (rows: any[]) => {
sourceCheckedKeys.value = rows.map((row: any) => row.id)
}
const handleTargetSelectionChange = (rows: any[]) => {
targetCheckedKeys.value = rows.map((row: any) => row.id)
}
const addToTarget = () => {
if (!sourceCheckedKeys.value.length) return
const set = new Set(selectedItemIds.value)
sourceCheckedKeys.value.forEach((id) => set.add(id))
selectedItemIds.value = Array.from(set)
sourceCheckedKeys.value = []
}
const removeFromTarget = () => {
if (!targetCheckedKeys.value.length) return
const removeSet = new Set(targetCheckedKeys.value)
selectedItemIds.value = selectedItemIds.value.filter((id) => !removeSet.has(id))
targetCheckedKeys.value = []
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
@ -70,7 +324,21 @@ const open = async (type: string, id?: number) => {
if (id) {
formLoading.value = true
try {
formData.value = await ZjSchemaApi.getZjSchema(id)
const data = await ZjSchemaApi.getZjSchema(id)
formData.value = data
initSelectedItemIds()
if (formData.value.type === 'Sample') {
if (formData.value.sampleMethod === 'Rate') {
const n = Number(formData.value.val)
rateVal.value = Number.isNaN(n) ? undefined : n
} else if (formData.value.sampleMethod === 'Gap' && typeof formData.value.val === 'string') {
const parts = formData.value.val.split(',').map((v: string) => v.trim())
const interval = Number(parts[0])
const count = Number(parts[1])
gapInterval.value = Number.isNaN(interval) ? undefined : interval
gapCount.value = Number.isNaN(count) ? undefined : count
}
}
} finally {
formLoading.value = false
}
@ -83,6 +351,21 @@ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成
const submitForm = async () => {
//
await formRef.value.validate()
if (isSampleType.value) {
if (isRateMethod.value) {
formData.value.val =
rateVal.value === undefined || rateVal.value === null ? undefined : String(rateVal.value)
} else if (isGapMethod.value) {
if (gapInterval.value && gapCount.value) {
formData.value.val = `${gapInterval.value},${gapCount.value}`
} else {
formData.value.val = undefined
}
}
} else {
formData.value.sampleMethod = ''
formData.value.val = ''
}
//
formLoading.value = true
try {
@ -105,13 +388,67 @@ const submitForm = async () => {
/** 重置表单 */
const resetForm = () => {
formData.value = {
type: undefined,
type: 'All',
name: undefined,
remark: undefined,
sampleMethod: undefined,
val: undefined,
item: undefined,
}
rateVal.value = undefined
gapInterval.value = undefined
gapCount.value = undefined
selectedItemIds.value = []
sourceKeyword.value = ''
targetKeyword.value = ''
sourceCheckedKeys.value = []
targetCheckedKeys.value = []
formRef.value?.resetFields()
}
</script>
<style scoped>
.formula-config-picklist {
display: flex;
width: 100%;
}
.formula-config-panel {
width: calc((100% - 96px) / 2);
border: 1px solid var(--el-border-color);
border-radius: 4px;
overflow: hidden;
background: var(--el-bg-color);
}
.formula-config-panel__header {
display: flex;
justify-content: space-between;
padding: 10px 12px;
border-bottom: 1px solid var(--el-border-color);
font-weight: 600;
}
.formula-config-panel__meta {
color: var(--el-text-color-secondary);
font-weight: 400;
}
.formula-config-panel__filter {
padding: 10px 12px;
border-bottom: 1px solid var(--el-border-color);
}
.formula-config-panel__table {
width: 100%;
}
.formula-config-actions {
width: 96px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
}
</style>

@ -1,97 +1,58 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="类型" prop="type">
<el-select
v-model="queryParams.type"
placeholder="请选择类型"
clearable
class="!w-240px"
>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable class="!w-240px">
<el-option
v-for="dict in getDictOptions('mes_zj_schema_type')" :key="dict.value" :label="dict.label"
:value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="抽检方式" prop="sampleMethod">
<el-input
v-model="queryParams.sampleMethod"
placeholder="请输入抽检方式"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<el-select v-model="queryParams.sampleMethod" placeholder="请选择抽检方式" clearable class="!w-240px">
<el-option
v-for="dict in getDictOptions('mes_zj_schema_sample_method')" :key="dict.value" :label="dict.label"
:value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="值" prop="val">
<el-input v-model="queryParams.val" placeholder="请输入值" clearable @keyup.enter="handleQuery" class="!w-240px" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.val"
placeholder="请输入值"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
v-model="queryParams.remark" placeholder="请输入备注" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
<el-form-item label="关联项目" prop="item">
<el-input
v-model="queryParams.item"
placeholder="请输入关联项目"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
v-model="queryParams.item" placeholder="请输入关联项目" clearable @keyup.enter="handleQuery"
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-220px"
/>
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-220px" />
</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="['mes:zj-schema:create']"
>
<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="['mes:zj-schema:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['mes:zj-schema:export']"
>
type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['mes:zj-schema:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
@ -100,36 +61,52 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="类型" align="center" prop="type" />
<el-table
v-loading="loading"
:data="list"
:show-overflow-tooltip="true"
@expand-change="handleExpandChange"
>
<el-table-column type="expand">
<template #default="scope">
<el-table
v-loading="scope.row._itemLoading"
:data="scope.row._items || []"
size="small"
:show-overflow-tooltip="true"
>
<el-table-column label="名称" prop="name" align="center" />
<el-table-column label="作业方式" prop="tool" align="center" />
<el-table-column label="标准值" prop="standardVal" align="center" />
<el-table-column label="单位" prop="unit" align="center" />
<el-table-column label="上限值" prop="upperVal" align="center" />
<el-table-column label="下限值" prop="lowerVal" align="center" />
<el-table-column label="备注" prop="remark" align="center" />
</el-table>
</template>
</el-table-column>
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="抽检方式" align="center" prop="sampleMethod" />
<el-table-column label="类型" align="center" prop="type">
<template #default="scope">
<dict-tag type="mes_zj_schema_type" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="抽检方式" align="center" prop="sampleMethod">
<template #default="scope">
<dict-tag type="mes_zj_schema_sample_method" :value="scope.row.sampleMethod" />
</template>
</el-table-column>
<el-table-column label="值" align="center" prop="val" />
<el-table-column label="关联项目" align="center" prop="item" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="操作" align="center" min-width="120px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mes:zj-schema:update']"
>
link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['mes:zj-schema:update']">
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mes:zj-schema:delete']"
>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['mes:zj-schema:delete']">
删除
</el-button>
</template>
@ -137,11 +114,8 @@
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
:total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
@ -149,6 +123,8 @@
</template>
<script setup lang="ts">
import { DictTag } from '@/components/DictTag'
import { getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ZjSchemaApi, ZjSchemaVO } from '@/api/mes/zjschema'
@ -217,7 +193,7 @@ const handleDelete = async (id: number) => {
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
} catch { }
}
/** 导出按钮操作 */
@ -235,6 +211,18 @@ const handleExport = async () => {
}
}
const handleExpandChange = async (row: any) => {
if (!row || !row.id) return
if (row._items && Array.isArray(row._items)) return
row._itemLoading = true
try {
const data = await ZjSchemaApi.getItemList(row.id)
row._items = data || []
} finally {
row._itemLoading = false
}
}
/** 初始化 **/
onMounted(() => {
getList()

Loading…
Cancel
Save