<template>
|
<div class="app-container metric-binding">
|
<el-row :gutter="16"
|
class="metric-binding-row">
|
<!-- 左侧:检测标准列表 -->
|
<el-col :xs="24"
|
:sm="24"
|
:md="12"
|
:lg="12"
|
:xl="12"
|
class="left-col">
|
<div class="panel left-panel">
|
<PIMTable rowKey="id"
|
:column="standardColumns"
|
:tableData="standardTableData"
|
:page="page"
|
:isSelection="false"
|
:rowClassName="rowClassNameCenter"
|
:tableLoading="tableLoading"
|
:rowClick="handleTableRowClick"
|
@pagination="handlePagination"
|
:total="page.total">
|
<template #standardNoCell="{ row }">
|
<span class="clickable-link"
|
@click="handleStandardRowClick(row)">
|
{{ row.standardNo }}
|
</span>
|
</template>
|
<!-- 表头搜索 -->
|
<template #standardNoHeader>
|
<el-input v-model="searchForm.standardNo"
|
placeholder="标准编号"
|
clearable
|
size="small"
|
@change="handleQuery"
|
@clear="handleQuery" />
|
</template>
|
<template #standardNameHeader>
|
<el-input v-model="searchForm.standardName"
|
placeholder="标准名称"
|
clearable
|
size="small"
|
@change="handleQuery"
|
@clear="handleQuery" />
|
</template>
|
<template #inspectTypeHeader>
|
<el-select v-model="searchForm.inspectType"
|
placeholder="类别"
|
clearable
|
size="small"
|
style="width: 120px"
|
@change="handleQuery"
|
@clear="handleQuery">
|
<el-option label="原材料检验"
|
value="0" />
|
<el-option label="过程检验"
|
value="1" />
|
<el-option label="出厂检验"
|
value="2" />
|
</el-select>
|
</template>
|
<template #stateHeader>
|
<el-select v-model="searchForm.state"
|
placeholder="状态"
|
clearable
|
size="small"
|
style="width: 110px"
|
@change="handleQuery"
|
@clear="handleQuery">
|
<el-option label="草稿"
|
value="0" />
|
<el-option label="通过"
|
value="1" />
|
<el-option label="撤销"
|
value="2" />
|
</el-select>
|
</template>
|
</PIMTable>
|
</div>
|
</el-col>
|
<!-- 右侧:绑定列表 -->
|
<el-col :xs="24"
|
:sm="24"
|
:md="12"
|
:lg="12"
|
:xl="12"
|
class="right-col">
|
<div class="panel right-panel">
|
<div class="right-header">
|
<div class="title">绑定关系</div>
|
<div class="desc"
|
v-if="currentStandard">
|
当前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
|
</div>
|
<div class="desc"
|
v-else>请选择左侧检测标准</div>
|
</div>
|
<div class="right-toolbar">
|
<el-button type="primary"
|
:disabled="!currentStandard"
|
@click="openBindingDialog">添加绑定</el-button>
|
<el-button type="danger"
|
plain
|
:disabled="!currentStandard"
|
@click="handleBatchUnbind">删除</el-button>
|
</div>
|
<el-table v-loading="bindingLoading"
|
:data="bindingTableData"
|
border
|
:row-class-name="() => 'row-center'"
|
class="center-table"
|
style="width: 100%"
|
height="calc(100vh - 220px)"
|
@selection-change="handleBindingSelectionChange">
|
<el-table-column type="selection"
|
width="48"
|
align="center" />
|
<el-table-column type="index"
|
label="序号"
|
width="60"
|
align="center" />
|
<el-table-column prop="productName"
|
label="产品名称"
|
min-width="140" />
|
<el-table-column prop="materialCode"
|
label="物料编码"
|
min-width="140" />
|
<el-table-column prop="model"
|
label="规格型号"
|
min-width="140" />
|
<el-table-column label="操作"
|
width="120"
|
fixed="right"
|
align="center">
|
<template #default="{ row }">
|
<el-button link
|
type="danger"
|
size="small"
|
@click="handleUnbind(row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
</el-col>
|
</el-row>
|
<!-- 添加绑定弹框 -->
|
<el-dialog v-model="bindingDialogVisible"
|
title="添加绑定"
|
width="520px"
|
@close="closeBindingDialog">
|
<el-form label-width="100px">
|
<el-form-item label="产品">
|
<el-button type="primary"
|
@click="openProductSelectDialog">选择产品</el-button>
|
<div class="selected-products mt-2"
|
v-if="selectedProducts.length > 0">
|
<el-tag v-for="product in selectedProducts"
|
:key="product.id"
|
closable
|
@close="removeSelectedProduct(product.id)"
|
class="mr-2 mb-2">
|
{{ product.productName }} - {{ product.model }}
|
</el-tag>
|
</div>
|
<div v-else
|
class="text-gray-400"></div>
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="closeBindingDialog">取消</el-button>
|
<el-button type="primary"
|
:disabled="selectedProducts.length === 0"
|
@click="submitBinding">确定</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
<!-- 产品选择对话框 -->
|
<ProductSelectDialog v-model="productSelectDialogVisible"
|
:single="false"
|
@confirm="handleProductSelect" />
|
</div>
|
</template>
|
|
<script setup>
|
import { Search } from "@element-plus/icons-vue";
|
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
import PIMTable from "@/components/PIMTable/PIMTable.vue";
|
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
|
import { qualityTestStandardListPage } from "@/api/qualityManagement/metricMaintenance.js";
|
import { productProcessListPage } from "@/api/basicData/productProcess.js";
|
import {
|
qualityTestStandardBindingList,
|
qualityTestStandardBindingAdd,
|
qualityTestStandardBindingDel,
|
} from "@/api/qualityManagement/qualityTestStandardBinding.js";
|
|
const { proxy } = getCurrentInstance();
|
|
// 左侧标准列表:整行内容居中(配合样式)
|
const rowClassNameCenter = () => "row-center";
|
|
const data = reactive({
|
searchForm: {
|
standardNo: "",
|
standardName: "",
|
state: "",
|
inspectType: "",
|
},
|
});
|
const { searchForm } = toRefs(data);
|
|
// 左侧
|
const standardTableData = ref([]);
|
const tableLoading = ref(false);
|
const page = reactive({ current: 1, size: 10, total: 0 });
|
|
// 工序下拉(用于列表回显)
|
const processOptions = ref([]);
|
|
const getProcessList = async () => {
|
try {
|
const res = await productProcessListPage({ current: 1, size: 1000 });
|
if (res?.code === 200) {
|
const records = res?.data?.records || [];
|
processOptions.value = records.map(item => ({
|
label: item.processName || item.name || item.label,
|
value: item.id || item.processId || item.value,
|
}));
|
}
|
} catch (error) {
|
console.error("获取工序列表失败:", error);
|
}
|
};
|
|
const standardColumns = ref([
|
{
|
label: "标准编号",
|
prop: "standardNo",
|
dataType: "slot",
|
slot: "standardNoCell",
|
minWidth: 160,
|
align: "center",
|
headerSlot: "standardNoHeader",
|
},
|
{
|
label: "标准名称",
|
prop: "standardName",
|
minWidth: 180,
|
align: "center",
|
headerSlot: "standardNameHeader",
|
},
|
{
|
label: "类别",
|
prop: "inspectType",
|
headerSlot: "inspectTypeHeader",
|
align: "center",
|
dataType: "tag",
|
formatData: val => {
|
const map = { 0: "原材料检验", 1: "过程检验", 2: "出厂检验" };
|
return map[val] || val;
|
},
|
},
|
{
|
label: "工序",
|
prop: "processId",
|
align: "center",
|
dataType: "tag",
|
formatData: val => {
|
const target = processOptions.value.find(
|
item => String(item.value) === String(val)
|
);
|
return target?.label || val;
|
},
|
},
|
{
|
label: "备注",
|
prop: "remark",
|
minWidth: 160,
|
align: "center",
|
},
|
// {
|
// label: '状态',
|
// prop: 'state',
|
// headerSlot: 'stateHeader',
|
// dataType: 'tag',
|
// formatData: (val) => {
|
// const map = { 0: '草稿', 1: '通过', 2: '撤销' }
|
// return map[val] || val
|
// },
|
// formatType: (val) => {
|
// if (val == 1) return 'success'
|
// if (val == 2) return 'warning'
|
// return 'info'
|
// }
|
// }
|
]);
|
|
const currentStandard = ref(null);
|
|
// 右侧绑定
|
const bindingTableData = ref([]);
|
const bindingLoading = ref(false);
|
const bindingSelectedRows = ref([]);
|
const bindingDialogVisible = ref(false);
|
|
// 产品选择
|
const productSelectDialogVisible = ref(false);
|
const selectedProducts = ref([]);
|
|
const openProductSelectDialog = () => {
|
productSelectDialogVisible.value = true;
|
};
|
|
const handleProductSelect = products => {
|
// 合并已选择的产品,避免重复
|
const existingIds = new Set(selectedProducts.value.map(p => p.id));
|
const newProducts = products.filter(p => !existingIds.has(p.id));
|
selectedProducts.value = [...selectedProducts.value, ...newProducts];
|
};
|
|
const removeSelectedProduct = id => {
|
selectedProducts.value = selectedProducts.value.filter(p => p.id !== id);
|
};
|
|
const handleQuery = () => {
|
page.current = 1;
|
getStandardList();
|
};
|
|
const handlePagination = obj => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getStandardList();
|
};
|
|
const getStandardList = () => {
|
tableLoading.value = true;
|
qualityTestStandardListPage({
|
...searchForm.value,
|
current: page.current,
|
size: page.size,
|
state: 1,
|
})
|
.then(res => {
|
const records = res?.data?.records || [];
|
standardTableData.value = records;
|
page.total = res?.data?.total || records.length;
|
})
|
.finally(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
// 表格行点击,加载右侧绑定列表
|
const handleTableRowClick = row => {
|
currentStandard.value = row;
|
loadBindingList();
|
};
|
|
// 左侧行点击,加载右侧绑定列表(保留用于标准编号列的点击)
|
const handleStandardRowClick = row => {
|
currentStandard.value = row;
|
loadBindingList();
|
};
|
|
const loadBindingList = () => {
|
if (!currentStandard.value?.id) {
|
bindingTableData.value = [];
|
return;
|
}
|
bindingLoading.value = true;
|
qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
|
.then(res => {
|
const base = res?.data || [];
|
// 将当前标准的工序和备注带到绑定列表中展示
|
bindingTableData.value = base.map(item => ({
|
...item,
|
processId: currentStandard.value?.processId,
|
remark: currentStandard.value?.remark,
|
}));
|
})
|
.finally(() => {
|
bindingLoading.value = false;
|
});
|
};
|
|
const handleBindingSelectionChange = selection => {
|
bindingSelectedRows.value = selection;
|
};
|
|
const openBindingDialog = () => {
|
if (!currentStandard.value?.id) return;
|
selectedProducts.value = [];
|
bindingDialogVisible.value = true;
|
};
|
|
const closeBindingDialog = () => {
|
bindingDialogVisible.value = false;
|
selectedProducts.value = [];
|
};
|
|
const submitBinding = async () => {
|
const testStandardId = currentStandard.value?.id;
|
if (!testStandardId) return;
|
const ids = (selectedProducts.value || []).map(p => p.id).filter(Boolean);
|
if (!ids.length) {
|
ElMessage.warning("请选择产品");
|
return;
|
}
|
const payload = ids.map(pid => ({
|
productId: pid,
|
testStandardId,
|
}));
|
await qualityTestStandardBindingAdd(payload);
|
ElMessage.success("添加成功");
|
bindingDialogVisible.value = false;
|
selectedProducts.value = [];
|
loadBindingList();
|
};
|
|
const handleUnbind = async row => {
|
const id = row?.id ?? row?.qualityTestStandardBindingId;
|
if (id == null || id === "") return;
|
try {
|
await ElMessageBox.confirm("确认删除该绑定?", "提示", { type: "warning" });
|
} catch {
|
return;
|
}
|
try {
|
await qualityTestStandardBindingDel([id]);
|
proxy.$message.success("删除成功");
|
loadBindingList();
|
} catch (err) {
|
console.error("删除绑定失败:", err);
|
proxy.$message?.error(err?.message || "删除失败");
|
}
|
};
|
|
const handleBatchUnbind = async () => {
|
if (!bindingSelectedRows.value.length) {
|
proxy.$message.warning("请选择数据");
|
return;
|
}
|
const ids = bindingSelectedRows.value
|
.map(i => i?.id ?? i?.qualityTestStandardBindingId)
|
.filter(id => id != null && id !== "");
|
if (!ids.length) {
|
proxy.$message.warning("选中数据缺少有效 id");
|
return;
|
}
|
try {
|
await ElMessageBox.confirm(
|
"选中的内容将被删除,是否确认删除?",
|
"删除提示",
|
{ type: "warning" }
|
);
|
} catch {
|
return;
|
}
|
try {
|
await qualityTestStandardBindingDel(ids);
|
proxy.$message.success("删除成功");
|
loadBindingList();
|
} catch (err) {
|
console.error("批量删除绑定失败:", err);
|
proxy.$message?.error(err?.message || "删除失败");
|
}
|
};
|
|
onMounted(() => {
|
getStandardList();
|
getProcessList();
|
});
|
</script>
|
|
<style scoped>
|
.metric-binding {
|
padding: 0;
|
}
|
|
.metric-binding-row {
|
width: 100%;
|
}
|
|
.metric-binding-row .left-col,
|
.metric-binding-row .right-col {
|
margin-bottom: 16px;
|
}
|
|
.metric-binding-row .panel {
|
background: #ffffff;
|
padding: 16px;
|
box-sizing: border-box;
|
height: 100%;
|
min-height: 400px;
|
}
|
|
.left-panel,
|
.right-panel {
|
height: 100%;
|
}
|
|
.toolbar {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 12px;
|
}
|
|
.toolbar-right {
|
flex-shrink: 0;
|
}
|
|
.right-header {
|
display: flex;
|
align-items: baseline;
|
justify-content: space-between;
|
margin-bottom: 10px;
|
}
|
|
.right-header .title {
|
font-size: 16px;
|
font-weight: 600;
|
}
|
|
.right-header .desc {
|
font-size: 13px;
|
color: #666;
|
}
|
|
.right-toolbar {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
margin-bottom: 10px;
|
}
|
|
.link-text {
|
color: #409eff;
|
cursor: default;
|
}
|
|
.clickable-link {
|
color: #409eff;
|
cursor: pointer;
|
}
|
|
.clickable-link:hover {
|
text-decoration: underline;
|
}
|
|
:deep(.row-center td) {
|
text-align: center !important;
|
}
|
|
/* el-table 表头/内容统一居中(row-class-name 不作用于表头) */
|
:deep(.center-table .el-table__header-wrapper th .cell) {
|
text-align: center !important;
|
}
|
:deep(.center-table .el-table__body-wrapper td .cell) {
|
text-align: center !important;
|
}
|
|
/* PIMTable 表头居中 */
|
:deep(.lims-table .pim-table-header-cell) {
|
text-align: center;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
}
|
|
:deep(.lims-table .pim-table-header-title) {
|
text-align: center;
|
width: 100%;
|
}
|
|
:deep(.lims-table .pim-table-header-extra) {
|
width: 100%;
|
margin-top: 4px;
|
}
|
</style>
|