Commit 54b22571 by Neo Turing

完善比对测试功能:更新TestDataItem接口、实现点击编辑模式、优化删除按钮样式

parent 8cbb38c2
......@@ -78,6 +78,7 @@ declare module 'vue' {
NTag: typeof import('naive-ui')['NTag']
NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip']
NTree: typeof import('naive-ui')['NTree']
NUpload: typeof import('naive-ui')['NUpload']
NWatermark: typeof import('naive-ui')['NWatermark']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
......
<script setup lang="ts">
import { computed, defineEmits, defineProps, onMounted, ref, watch } from 'vue';
import { computed, defineEmits, defineProps, h, onMounted, ref, watch } from 'vue';
// 主体框架内置的第三方方法通过window对象暴露、供外部组件使用
const axios = (window as any).$axios;
// 获取主题状态
......@@ -22,11 +22,14 @@ const cssVars = computed(() => ({
'--border-color': isDarkMode.value ? '#434343' : '#e8e8e8'
}));
interface PlanDetailItem {
id: number;
testItem: string;
standardValue: string;
allowableRange: string;
interface CustomTreeOption {
key: string | number;
label: string;
children?: CustomTreeOption[];
origin?: any; // 保存原始API数据
isLeaf?: boolean;
disabled?: boolean;
[key: string]: any; // 添加索引签名以兼容 Naive UI
}
const props = defineProps({
......@@ -38,10 +41,12 @@ const props = defineProps({
const emit = defineEmits(['cancel', 'confirm']);
// 当前可用的方案明细数据
const planDetails = ref<PlanDetailItem[]>([]);
// 已选中的行键数组
const checkedRowKeys = ref<(string | number)[]>([]);
// 树形数据
const treeData = ref<CustomTreeOption[]>([]);
// 已选中的节点键数组
const checkedKeys = ref<(string | number)[]>([]);
// 展开的节点键数组
const expandedKeys = ref<(string | number)[]>([]);
// 加载状态
const loading = ref<boolean>(false);
......@@ -51,15 +56,33 @@ async function fetchTestDetails() {
try {
loading.value = true;
const response = await axios.post('/Restful/Kivii.Standards.Entities.Detection/Query.json', {
StandardKvid: props.standardKvid
});
const queryParams: any = {
StandardKvid: props.standardKvid,
OrderBy: 'SortId'
};
const response = await axios.post(
'/Restful/Kivii.Standards.Entities.DetectionEx/DetectionWithPriceQuery.json',
queryParams
);
if (response.data && response.data.Results) {
// 处理树形结构数据
const treeData = processTreeData(response.data.Results);
planDetails.value = treeData;
const treeStructure = processTreeData(response.data.Results);
treeData.value = treeStructure;
// 收集所有有子节点的节点的key,用于默认展开
const collectExpandableKeys = (nodes: CustomTreeOption[]): (string | number)[] => {
const keys: (string | number)[] = [];
for (const node of nodes) {
if (node.children && node.children.length > 0) {
keys.push(node.key);
keys.push(...collectExpandableKeys(node.children));
}
}
return keys;
};
expandedKeys.value = collectExpandableKeys(treeStructure);
message.success(`已加载${response.data.Results.length}个测试项目`);
} else {
......@@ -67,49 +90,52 @@ async function fetchTestDetails() {
}
} catch (error) {
message.error('获取测试详情失败');
// console.error(error);
console.error(error);
} finally {
loading.value = false;
}
}
// 处理树形结构数据
function processTreeData(data: any[]) {
function processTreeData(data: any[]): CustomTreeOption[] {
// 创建ID到节点的映射
const idMap: Record<string, any> = {};
const result: any[] = [];
const result: CustomTreeOption[] = [];
// 首先将所有节点添加到映射中
data.forEach((item, index) => {
const node = {
id: index + 1,
key: item.Kvid || `item-${index}`, // 使用Kvid作为唯一标识,如果没有则使用索引
testItem: item.Title || '未命名项目',
standardValue: item.StandardValue || item.QualifiedValue || '未设置',
allowableRange: item.AllowableRange || '±5%',
const node: any = {
key: item.Kvid || `item-${index}`, // 使用Kvid作为唯一标识
label: item.Title || '未命名项目',
parentId: item.ParentKvid || null, // 假设API返回ParentKvid表示父节点ID
children: []
children: [],
origin: item, // 保存原始数据
level: 0 // 初始层级为0,后续会更新
};
idMap[node.key] = node;
});
// 构建树形结构
Object.values(idMap).forEach(node => {
Object.values(idMap).forEach((node: any) => {
if (node.parentId && idMap[node.parentId]) {
// 如果有父节点,将当前节点添加到父节点的children中
node.level = idMap[node.parentId].level + 1; // 设置层级
idMap[node.parentId].children.push(node);
} else {
// 如果没有父节点,则是顶级节点
node.level = 0; // 顶级节点层级为0
result.push(node);
}
});
// 处理空children数组
// 处理空children数组并设置isLeaf
const cleanEmptyChildren = (nodes: any[]) => {
for (const node of nodes) {
if (node.children.length === 0) {
node.isLeaf = true;
delete node.children;
} else {
node.isLeaf = false;
cleanEmptyChildren(node.children);
}
}
......@@ -126,35 +152,60 @@ watch(
if (newValue) {
fetchTestDetails();
// 重置选中状态
checkedRowKeys.value = [];
checkedKeys.value = [];
// 重置展开状态
expandedKeys.value = [];
} else {
planDetails.value = [];
treeData.value = [];
checkedKeys.value = [];
expandedKeys.value = [];
}
},
{ immediate: true }
);
// 处理行选择变化
function handleCheckedRowKeysChange(keys: (string | number)[]) {
checkedRowKeys.value = keys;
// 处理选中状态变化
function handleCheckedKeysChange(keys: (string | number)[]) {
checkedKeys.value = keys;
}
// 处理展开状态变化
function handleExpandedKeysChange(keys: (string | number)[]) {
expandedKeys.value = keys;
}
// 自定义节点渲染 - 显示详细信息
function renderLabel({ option }: any) {
const origin = option.origin;
if (!origin) {
return option.label;
}
// 判断是父级还是子级,根据是否有children来确定
const isParent = option.children && option.children.length > 0;
const icon = isParent ? '📁' : '🧪';
// 构建显示内容
const standardValue = origin.StandardValue || origin.QualifiedValue || '未设置';
const allowableRange = origin.AllowableRange || '±5%';
return h('div', { class: 'tree-node-content-inline' }, [
h('span', { class: 'tree-node-icon', style: 'margin-right: 8px;' }, icon),
h('span', { class: 'tree-node-title-inline' }, option.label),
h('span', { class: 'tree-node-separator' }, ' '),
h('span', { class: 'tree-node-detail-inline' }, `📏 标准值: ${standardValue}`),
h('span', { class: 'tree-node-separator' }, ' '),
h('span', { class: 'tree-node-detail-inline' }, `⚖️ 允许偏差: ${allowableRange}`)
]);
}
// 计算测试方案名称
const planName = computed(() => {
// 现在从API获取数据,planName可能需要从父组件传递或使用其他方式获取
return `测试方案 (${props.standardKvid})`;
});
// 表格列定义
const columns = [
{
type: 'selection'
},
{ title: '测试项目', key: 'testItem', align: 'center' },
{ title: '标准值', key: 'standardValue', align: 'center' },
{ title: '允许偏差范围', key: 'allowableRange', align: 'center' }
];
// 取消选择
function cancel() {
emit('cancel');
......@@ -162,127 +213,110 @@ function cancel() {
// 确认选择
function confirm() {
if (checkedRowKeys.value.length === 0) {
if (checkedKeys.value.length === 0) {
message.warning('请至少选择一个测试项目');
return;
}
// 提取完整树形结构中被选中的节点和它们的子节点
const cloneTreeData = JSON.parse(JSON.stringify(planDetails.value));
// 过滤并标记选中的树节点
const filterSelectedNodes = (nodes: any[]) => {
// 收集选中节点的原始数据
const collectSelectedOriginData = (nodes: CustomTreeOption[]): any[] => {
const result: any[] = [];
for (const node of nodes) {
const isSelected = checkedRowKeys.value.includes(node.key);
// 如果当前节点被选中或者有被选中的子节点,则保留
if (isSelected) {
// 创建一个节点副本,只保留需要的属性
const newNode = {
id: node.id,
key: node.key,
testItem: node.testItem,
standardValue: node.standardValue,
allowableRange: node.allowableRange
};
// 如果有子节点,递归处理
if (node.children && node.children.length > 0) {
const selectedChildren = filterSelectedNodes(node.children);
if (selectedChildren.length > 0) {
newNode.children = selectedChildren;
}
}
const isSelected = checkedKeys.value.includes(node.key);
result.push(newNode);
} else if (node.children && node.children.length > 0) {
// 如果当前节点未被选中,但可能有被选中的子节点
const selectedChildren = filterSelectedNodes(node.children);
if (selectedChildren.length > 0) {
// 创建一个节点副本,并添加被选中的子节点
const newNode = {
id: node.id,
key: node.key,
testItem: node.testItem,
standardValue: node.standardValue,
allowableRange: node.allowableRange,
children: selectedChildren
};
result.push(newNode);
}
// 检查是否有子节点被选中
let hasSelectedChildren = false;
const selectedChildren: any[] = [];
if (node.children && node.children.length > 0) {
const childResults = collectSelectedOriginData(node.children);
selectedChildren.push(...childResults);
// 检查子节点中是否有被选中的
hasSelectedChildren = node.children.some((child: CustomTreeOption) =>
checkedKeys.value.includes(child.key) ||
(child.children && hasAnyChildSelected(child))
);
}
// 如果当前节点被选中,或者有子节点被选中但当前节点未被选中时也要包含父节点
if (isSelected && node.origin) {
result.push(node.origin);
} else if (!isSelected && hasSelectedChildren && node.origin) {
// 父节点未被选中但有子节点被选中,也要包含父节点
result.push(node.origin);
}
// 添加选中的子节点
result.push(...selectedChildren);
}
return result;
};
const selectedTreeData = filterSelectedNodes(cloneTreeData);
// 计算选中的项目总数
const countSelectedItems = (nodes: any[]): number => {
let count = 0;
for (const node of nodes) {
count += 1; // 计算当前节点
if (node.children && node.children.length > 0) {
count += countSelectedItems(node.children); // 递归计算子节点
}
// 递归检查节点是否有任何子节点被选中
const hasAnyChildSelected = (node: CustomTreeOption): boolean => {
if (!node.children || node.children.length === 0) {
return false;
}
return count;
return node.children.some((child: CustomTreeOption) =>
checkedKeys.value.includes(child.key) || hasAnyChildSelected(child)
);
};
const selectedCount = countSelectedItems(selectedTreeData);
// 获取选中节点的原始数据
const selectedOriginData = collectSelectedOriginData(treeData.value);
// 去重处理(避免重复添加)
const uniqueData = selectedOriginData.filter((item, index, arr) =>
arr.findIndex(t => t.Kvid === item.Kvid) === index
);
// 将处理后的树形结构传递给父组件
emit('confirm', selectedTreeData);
message.success(`已选择${selectedCount}个测试项目`);
// 将原始数据传递给父组件
emit('confirm', uniqueData);
message.success(`已选择${uniqueData.length}个测试项目(包含必要的父级项目)`);
}
// 页面加载时获取数据
onMounted(() => {
// 删除此处的fetchTestDetails调用,避免重复调用API
// watch中的immediate:true已经确保了初始加载数据
});
</script>
<template>
<div class="plan-detail-selector">
<!--
<div class="header">
<div class="title">测试方案明细选择</div>
<n-space style="display: flex; align-items: center;">
<span class="plan-name">{{ planName }}</span>
<n-tag type="info">{{ checkedRowKeys.length }}项已选择</n-tag>
</n-space>
</div>
-->
<div class="plan-detail-selector" :style="cssVars">
<NAlert type="info" style="margin-bottom: 16px">
请选择需要进行比对测试的项目,选中的项目将被添加到测试数据表格中。
</NAlert>
<NDataTable
:columns="columns"
:data="planDetails"
:bordered="false"
:row-key="row => row.key"
:checked-row-keys="checkedRowKeys"
children-key="children"
:indent="20"
:max-height="300"
:min-height="300"
:pagination="false"
striped
:loading="loading"
@update:checked-row-keys="handleCheckedRowKeysChange"
></NDataTable>
<div class="tree-container">
<NTree
:data="treeData"
:checked-keys="checkedKeys"
:expanded-keys="expandedKeys"
checkable
cascade
check-strategy="all"
block-line
:render-label="renderLabel"
virtual-scroll
style="max-height: 300px; min-height: 300px;"
@update:checked-keys="handleCheckedKeysChange"
@update:expanded-keys="handleExpandedKeysChange"
/>
<div v-if="loading" class="loading-overlay">
<NSpin size="medium" />
</div>
</div>
<div class="footer">
<NSpace justify="end">
<NButton @click="cancel">取消</NButton>
<NButton type="primary" :disabled="checkedRowKeys.length === 0" @click="confirm">
确认选择({{ checkedRowKeys.length }})
<NButton type="primary" :disabled="checkedKeys.length === 0" @click="confirm">
确认选择({{ checkedKeys.length }})
</NButton>
</NSpace>
</div>
......@@ -291,34 +325,126 @@ onMounted(() => {
<style scoped>
.plan-detail-selector {
/* padding: 0 0 16px 0; */
height: 450px;
display: flex;
flex-direction: column;
}
.header {
.tree-container {
position: relative;
flex: 1;
border: 1px solid var(--border-color);
border-radius: 6px;
overflow: hidden;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
justify-content: center;
z-index: 10;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
.footer {
padding-top: 16px;
border-top: 1px solid var(--border-color);
margin-top: 16px;
}
.plan-name {
font-weight: bold;
color: #2080f0;
/* 树节点样式 */
:deep(.n-tree-node-content) {
padding: 8px 12px !important;
}
.footer {
padding-top: 10px;
/* padding-bottom: 16px; */
border-top: 1px solid #f0f0f0;
margin-top: auto;
:deep(.n-tree-node--pending) {
background-color: transparent !important;
}
:deep(.n-tree-node--selected) {
background-color: rgba(24, 160, 88, 0.1) !important;
}
:deep(.n-tree-node--highlighted) {
background-color: rgba(24, 160, 88, 0.05) !important;
}
/* 自定义节点内容样式 - 单行显示 */
.tree-node-content-inline {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: nowrap;
}
.tree-node-title-inline {
font-weight: 600;
color: var(--text-color);
font-size: 14px;
min-width: 120px;
flex-shrink: 0;
}
.tree-node-separator {
color: #ccc;
font-size: 12px;
margin: 0 4px;
flex-shrink: 0;
}
.tree-node-detail-inline {
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
white-space: nowrap;
flex-shrink: 0;
}
.tree-node-icon {
font-size: 14px;
display: inline-block;
vertical-align: middle;
}
/* 父级节点样式 */
:deep(.n-tree-node[aria-level="1"]) {
background-color: #f8f9fa !important;
}
:deep(.n-tree-node[aria-level="1"]:hover) {
background-color: #e9ecef !important;
}
/* 子级节点样式 */
:deep(.n-tree-node[aria-level="2"]) {
background-color: #fff !important;
}
:deep(.n-tree-node[aria-level="2"]:hover) {
background-color: #f5f5f5 !important;
}
/* 选中状态样式 */
:deep(.n-tree-node--checked) {
background-color: rgba(24, 160, 88, 0.1) !important;
}
:deep(.n-tree-node--checked:hover) {
background-color: rgba(24, 160, 88, 0.15) !important;
}
/* 暗色主题适配 */
:deep(.n-tree.n-tree--block-line .n-tree-node) {
border-bottom: 1px solid var(--border-color);
}
</style>
<script setup lang="ts">
import { computed, defineEmits, defineProps, h, onMounted, ref, resolveComponent } from 'vue';
import TestPlanDetailSelector from './TestPlanDetailSelector.vue'; // '/codes/TestPlanDetailSelector.vue';
import {
computed,
defineEmits,
defineProps,
h,
onMounted,
reactive,
ref,
resolveComponent,
watch,
withDefaults
} from 'vue';
import TestPlanDetailSelector from './TestPlanDetailSelector.vue'; //'/codes/Vue3/Lab/TestPlanDetailSelector.vue'
interface TestItem {
id: number;
name: string;
selected: boolean;
// 根据API文档定义完整的数据接口
interface TestDataItem {
Kvid: string;
// 业务相关字段
BizKvid?: string;
BizId?: string;
BizType?: string;
RootKvid?: string;
ReportKvid?: string;
SampleKvid?: string;
SampleRootKvid?: string;
// 标准相关字段
StandardKvid?: string;
StandardCode?: string;
StandardTitle?: string;
// 检测项目相关字段
DetectionKvid?: string;
DetectionTitle?: string;
DetectionReferenceKvid?: string;
DetectionCodeActual?: string;
DetectionUnit?: string;
DetectionValueQualified?: string;
// 货币和价格相关字段
Currency?: string;
GoodsKvid?: string;
PackageKvid?: string;
PackageUnit?: string;
AmountPlan?: number;
Amount?: number;
// 测试相关字段
TestKvid?: string;
TestRootKvid?: string;
TestValue?: string;
TestOriginalFilePath?: string;
TestCreatorKvid?: string;
TestCreatorName?: string;
TestReviewerKvid?: string;
TestReviewerName?: string;
TestCreateTime?: string;
TestReviewTime?: string;
// 层级和基本信息字段
ParentKvid?: string;
Type?: string;
Title: string;
SortId?: number;
SortIdEx?: number;
Judgement?: string;
Remark?: string;
// 工作组和人员字段
WorkGroupKvid?: string;
WorkGroupName?: string;
WorkerKvid?: string;
WorkerName?: string;
CreatorKvid?: string;
CreatorName?: string;
UpdaterKvid?: string;
UpdaterName?: string;
// 状态相关字段(由前端计算)
StatusType?: string;
Status?: string;
StatusColor?: string;
// 前端扩展字段(用于界面交互)
measuredValue?: string;
verifier?: string;
result?: string;
// 兼容旧字段
Unit?: string;
QualifiedValue?: string;
[key: string]: any;
}
interface TestDataItem {
key: string;
testItem: string;
standardValue: string;
allowableRange: string;
measuredValue: string | null;
verifier: string | null;
result: string;
children?: TestDataItem[];
// 定义Item对象的接口
interface ItemProps {
Kvid?: string;
[key: string]: any;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const props = defineProps({
testItems: {
type: Array as () => TestItem[],
required: true
},
saveAsDraft: {
type: Boolean,
required: true
// 定义props
const props = withDefaults(
defineProps<{
item?: ItemProps;
active?: boolean;
}>(),
{
item: () => ({}),
active: false
}
});
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emit = defineEmits(['update:testItems', 'update:saveAsDraft']);
// 主体框架内置的第三方方法通过window对象暴露、供外部组件使用
const axios = (window as any).$axios;
// 获取主题状态
const themeStore = (window as any).$themeStore;
// 获取消息提示
const message = (window as any).$message;
const dialog = (window as any).$dialog;
// 通过计算属性确定当前的主体色
const isDarkMode = computed(() => themeStore?.darkMode || false);
// 根据主题计算文字颜色
const textColor = computed(() => (isDarkMode.value ? 'rgba(255, 255, 255, 0.85)' : 'rgba(0, 0, 0, 0.85)'));
const legendTextColor = computed(() => (isDarkMode.value ? 'rgba(255, 255, 255, 0.65)' : 'rgba(0, 0, 0, 0.65)'));
const borderColor = computed(() => (isDarkMode.value ? '#303030' : '#ffffff'));
// CSS变量
const cssVars = computed(() => ({
'--text-color': textColor.value,
'--bg-color': isDarkMode.value ? '#1f1f1f' : '#f5f5f5',
'--panel-bg': isDarkMode.value ? '#262626' : '#ffffff',
'--card-bg': isDarkMode.value ? '#303030' : '#ffffff',
'--border-color': isDarkMode.value ? '#434343' : '#e8e8e8'
}));
// 表单数据
/**
* 存储从props传入的详细数据
*
* 保存父组件传递的item数据的响应式副本
*/
const detailedData = reactive<ItemProps>({});
/**
* 比对测试数据列表
*
* 存储当前显示的所有比对测试记录,数据结构遵循API文档规范
*/
const testData = ref<TestDataItem[]>([]);
/**
* 加载状态标识
*
* 控制各种异步操作的加载状态显示
*/
const loading = ref<boolean>(false);
/**
* 测试方案选择器选项
*
* 存储可选的测试方案列表
*/
const titleOptions = ref<any[]>([]);
/**
* 当前选中的测试方案
*
* 存储用户选择的测试方案ID
*/
const testPlan = ref('');
const testDate = ref(null);
const sampleCode = ref('');
const testResult = ref('');
const testComment = ref('');
/**
* 控制测试方案明细选择弹窗显示状态
*
* 控制TestPlanDetailSelector模态框的开关
*/
const showDetailSelector = ref(false);
// 状态变量
const loading = ref<boolean>(false);
const titleOptions = ref<Array<{ label: string; value: string }>>([]);
/**
* 计算是否存在比对测试数据
*
* 用于控制测试方案选择器的禁用状态和清空按钮的显示
*
* @returns {boolean} 是否有现有的比对测试数据
* @usage 控制NSelect的disabled状态和清空按钮的显示条件
*/
const hasExistingData = computed(() => {
return testData.value.length > 0;
});
/**
* 状态类型映射函数
*
* 将API返回的StatusType字符串映射为中文显示状态
*
* @param {string} statusType - API返回的状态类型字符串
* @returns {string} 对应的中文状态显示名称
* @usage 在数据处理和状态显示时使用
*/
const mapStatusType = (statusType: string) => {
const statusMap: { [key: string]: string } = {
Unsupported: '草稿',
BeforeTest: '待检测',
Asigning: '检测中',
Testing: '检测中',
BeforeReview: '待复核',
TestFinished: '不通过',
TestCollected: '已通过'
};
return statusMap[statusType] || '草稿';
};
/**
* 状态颜色获取函数
*
* 根据状态类型返回对应的UI显示颜色
*
* @param {string} status - 状态类型字符串
* @returns {string} 对应状态的十六进制颜色值
* @usage 在表格和标签组件中设置状态颜色
*/
const getStatusColor = (status: string) => {
const statusColorMap: { [key: string]: string } = {
BeforeTest: '#f56a00',
Asigning: '#1890ff',
Testing: '#722ed1',
BeforeReview: '#eb2f96',
TestFinished: '#13c2c2',
TestCollected: '#52c41a'
};
return statusColorMap[status] || '#666';
};
// 监听props.item变化,同步到detailedData
watch(
() => props.item,
newItem => {
if (newItem && newItem.Kvid && newItem.Kvid !== detailedData.Kvid) {
fetchDataByReportKvid(newItem.Kvid);
}
// 保持detailedData为props.item的响应式副本
Object.assign(detailedData, newItem || {});
},
{ immediate: true, deep: true }
);
watch(
() => props.active,
newActive => {
if (newActive) {
if (props.item && props.item.Kvid && props.item.Kvid !== detailedData.Kvid) {
fetchDataByReportKvid(props.item.Kvid);
}
}
},
{ immediate: true }
);
/**
* 根据报告Kvid获取比对测试数据
*
* 使用报告ID查询已存在的比对测试数据记录
*
* @param {string} reportKvid - 报告的唯一标识符
* @returns {Promise<void>} 无返回值,结果存储在testData.value中
* @usage 在组件初始化和props变化时调用,获取现有的比对测试数据
*/
async function fetchDataByReportKvid(reportKvid: string) {
if (!reportKvid) return;
if (reportKvid === detailedData.Kvid && testData.value.length > 0) {
return;
}
// 获取测试方案选项
async function fetchTestPlanOptions() {
try {
loading.value = true;
// 调用标准实体查询接口
const response = await axios.post('/Restful/Kivii.Standards.Entities.Standard/Query.json?Type=比对测试');
// console.log('测试方案响应数据:', response.data);
const response = await axios.get('/Restful/Kivii.Lims.Entities.ReportItem/QueryEx.json', {
params: {
ReportKvid: reportKvid,
OrderBy: 'SortId,SortIdEx,Kvid',
WorkGroupName: '比对测试'
}
});
if (response.data && response.data.Results && response.data.Results.length > 0) {
testData.value = response.data.Results.map((item: any) => ({
...item,
Status: mapStatusType(item.StatusType),
StatusColor: getStatusColor(item.StatusType)
}));
message.success('比对测试数据加载成功');
} else {
message.info('暂无比对测试数据');
}
} catch (error) {
message.error('获取比对测试数据失败,请稍后重试');
} finally {
loading.value = false;
}
}
// 添加数据存在性检查
/**
* 获取文件类型选项数据
*
* 从标准实体接口查询比对测试类型数据,用于下拉选择器
*
* @returns {Promise<void>} 无返回值,结果存储在titleOptions.value中
* @usage 点击下拉框时懒加载,有数据则不重复加载
*/
async function fetchTitleOptions() {
if (titleOptions.value.length > 0) {
return;
}
try {
loading.value = true;
const response = await axios.get('/Restful/Kivii.Standards.Entities.Standard/Query.json', {
params: {
Type: '比对测试'
}
});
if (response.data && response.data.Results) {
// 直接使用Results数组
titleOptions.value = response.data.Results.map((item: any) => ({
label: item.Title,
value: item.Kvid
value: item.Kvid,
fullObject: item
}));
} else {
// console.error('无效的数据格式:', response.data);
message.error('数据格式不正确');
}
} catch (error) {
message.error('获取测试方案失败');
// console.error('获取测试方案失败:', error);
} finally {
loading.value = false;
}
}
// 表格列定义
const columns = [
{ title: '测试项目', key: 'testItem' },
{ title: '标准值', key: 'standardValue', align: 'center' as const },
{ title: '允许偏差范围', key: 'allowableRange', align: 'center' as const },
{
title: '实测值',
key: 'measuredValue',
align: 'center' as const,
render: (row: TestDataItem) => {
return h(resolveComponent('n-input'), {
value: row.measuredValue,
placeholder: '请输入测试值',
onUpdateValue: (value: string) => {
row.measuredValue = value;
checkTestResult(row);
}
});
}
// 树形数据结构定义
interface TreeDataItem {
key: string;
label: string;
children?: TreeDataItem[];
origin?: TestDataItem; // 保存原始数据
isLeaf?: boolean;
[key: string]: any;
}
// 树形数据
const treeData = ref<TreeDataItem[]>([]);
// 编辑状态管理
const editingStates = ref<Record<string, { measuredValue: boolean; verifier: boolean }>>({});
// 获取编辑状态
function getEditingState(kvid: string, field: 'measuredValue' | 'verifier') {
if (!editingStates.value[kvid]) {
editingStates.value[kvid] = { measuredValue: false, verifier: false };
}
return editingStates.value[kvid][field];
}
// 设置编辑状态
function setEditingState(kvid: string, field: 'measuredValue' | 'verifier', value: boolean) {
if (!editingStates.value[kvid]) {
editingStates.value[kvid] = { measuredValue: false, verifier: false };
}
editingStates.value[kvid][field] = value;
}
// 监听testData变化,更新treeData
watch(
testData,
(newTestData) => {
treeData.value = convertToTreeData(newTestData);
},
{
title: '授信人',
key: 'verifier',
align: 'center' as const,
render: (row: TestDataItem) => {
return h(resolveComponent('n-input'), {
value: row.verifier,
placeholder: '请输入授信人',
onUpdateValue: (value: string) => {
row.verifier = value;
}
});
{ immediate: true, deep: true }
);
// 将测试数据转换为树形结构
function convertToTreeData(data: TestDataItem[]): TreeDataItem[] {
const idMap: Record<string, any> = {};
const result: TreeDataItem[] = [];
// 首先将所有节点添加到映射中
data.forEach((item) => {
const node: any = {
key: item.Kvid,
label: item.Title,
parentId: item.ParentKvid || null,
children: [],
origin: item, // 保存原始数据
level: 0
};
idMap[node.key] = node;
});
// 构建树形结构
Object.values(idMap).forEach((node: any) => {
if (node.parentId && idMap[node.parentId]) {
node.level = idMap[node.parentId].level + 1;
idMap[node.parentId].children.push(node);
} else {
node.level = 0;
result.push(node);
}
},
{
title: '结果',
key: 'result',
align: 'center' as const,
render: (_row: TestDataItem) => {
return h(resolveComponent('n-tag'), { type: 'default' }, { default: () => '待测试' });
});
// 处理空children数组并设置isLeaf
const cleanEmptyChildren = (nodes: any[]) => {
for (const node of nodes) {
if (node.children.length === 0) {
node.isLeaf = true;
delete node.children;
} else {
node.isLeaf = false;
cleanEmptyChildren(node.children);
}
}
},
{
title: '操作',
key: 'actions',
width: 200,
align: 'center',
render(row: any) {
return h('div', { style: 'display: flex; gap: 8px; justify-content: center;' }, [
h(
resolveComponent('n-button'),
{
};
cleanEmptyChildren(result);
return result;
}
// 自定义节点渲染函数
function renderTreeLabel({ option }: any) {
const origin = option.origin;
if (!origin) {
return option.label;
}
// 判断是父级还是子级
const isParent = option.children && option.children.length > 0;
const icon = isParent ? '📁' : '🧪';
// 构建显示内容
const qualifiedValue = origin.DetectionValueQualified || '未设置';
const unit = origin.DetectionUnit || origin.Unit || '';
const measuredValue = origin.TestValue || origin.measuredValue || '';
const verifier = origin.TestReviewerName || origin.verifier || '';
const status = origin.Status || '待测试';
return h('div', {
style: 'width: 100%; display: flex; align-items: center; justify-content: space-between; min-height: 40px; gap: 16px;'
}, [
// 左侧内容
h('div', {
style: 'display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0;'
}, [
h('span', { style: 'margin-right: 8px; font-size: 14px;' }, icon),
h('span', { style: 'font-weight: 600; color: #333; font-size: 14px;' }, option.label),
h('span', { style: 'color: #ccc; margin: 0 8px;' }, '|'),
h('span', {
style: 'font-size: 12px; color: #666; background: #f5f5f5; padding: 4px 8px; border-radius: 4px; white-space: nowrap;'
}, `📏 标准值: ${qualifiedValue}${unit ? ` (${unit})` : ''}`)
]),
// 右侧内容
h('div', {
style: 'display: flex; align-items: center; gap: 8px; flex-shrink: 0;'
}, [
// 实测值编辑区域
h('span', { style: 'display: flex; align-items: center; gap: 4px;' }, [
h('span', { style: 'font-size: 12px; color: #666; white-space: nowrap;' }, '🔬 实测值:'),
getEditingState(origin.Kvid, 'measuredValue') ?
h(resolveComponent('n-input'), {
value: measuredValue,
placeholder: '请输入',
size: 'small',
style: 'width: 100px;',
autofocus: true,
onUpdateValue: (value: string) => {
origin.TestValue = value;
origin.measuredValue = value; // 保持兼容
checkTestResult(origin);
},
onBlur: () => {
setEditingState(origin.Kvid, 'measuredValue', false);
},
onKeydown: (e: KeyboardEvent) => {
if (e.key === 'Enter') {
setEditingState(origin.Kvid, 'measuredValue', false);
}
}
}) :
h('span', {
style: 'min-width: 100px; padding: 4px 8px; background: #f5f5f5; border-radius: 4px; cursor: pointer; font-size: 12px; color: #333;',
onClick: (e: Event) => {
e.stopPropagation();
setEditingState(origin.Kvid, 'measuredValue', true);
}
}, measuredValue || '点击输入')
]),
h('span', { style: 'color: #ccc; margin: 0 4px;' }, '|'),
// 授信人编辑区域
h('span', { style: 'display: flex; align-items: center; gap: 4px;' }, [
h('span', { style: 'font-size: 12px; color: #666; white-space: nowrap;' }, '👤 授信人:'),
getEditingState(origin.Kvid, 'verifier') ?
h(resolveComponent('n-input'), {
value: verifier,
placeholder: '请输入',
size: 'small',
type: 'error',
onClick: () => deleteTestPlanDetail(row)
},
{ default: () => '删除' }
)
]);
style: 'width: 100px;',
autofocus: true,
onUpdateValue: (value: string) => {
origin.TestReviewerName = value;
origin.verifier = value; // 保持兼容
},
onBlur: () => {
setEditingState(origin.Kvid, 'verifier', false);
},
onKeydown: (e: KeyboardEvent) => {
if (e.key === 'Enter') {
setEditingState(origin.Kvid, 'verifier', false);
}
}
}) :
h('span', {
style: 'min-width: 100px; padding: 4px 8px; background: #f5f5f5; border-radius: 4px; cursor: pointer; font-size: 12px; color: #333;',
onClick: (e: Event) => {
e.stopPropagation();
setEditingState(origin.Kvid, 'verifier', true);
}
}, verifier || '点击输入')
]),
h('span', { style: 'color: #ccc; margin: 0 4px;' }, '|'),
h('span', { style: 'display: flex; align-items: center; gap: 4px;' }, [
h('span', { style: 'margin-right: 4px;' }, '✅'),
h(resolveComponent('n-tag'), {
type: status === '待测试' ? 'default' : 'success',
size: 'small'
}, { default: () => status })
]),
h('span', { style: 'color: #ccc; margin: 0 4px;' }, '|'),
// 删除按钮
h(resolveComponent('n-button'), {
size: 'small',
type: 'error',
style: 'background-color: #ff4757; border-color: #ff4757; color: white; border-radius: 4px; font-size: 12px; padding: 4px 12px; height: 28px;',
onClick: (e: Event) => {
e.stopPropagation();
deleteTestPlanDetail(origin);
}
}, { default: () => '删除' })
])
]);
}
/**
* 批量删除实体数据
*
* 通用的删除方法,支持批量删除多个文件审核记录
*
* @param {string[]} kvids - 要删除的记录ID数组
* @returns {Promise<boolean>} 删除操作是否成功
* @usage 被deleteFileRecord和clearAllFileData方法调用
*/
async function deleteEntities(kvids: string[]) {
if (!kvids || kvids.length === 0) {
message.error('请选择要删除的项目');
return false;
}
try {
loading.value = true;
const response = await axios.post('/Restful/Kivii.Lims.Entities.ReportItem/Delete.json', {
Kvids: kvids
});
if (response.data && response.data.Results && response.data.Results.length > 0) {
message.success(`成功删除 ${kvids.length} 个文件`);
return true;
}
message.error(response.data?.Message || '删除失败');
return false;
} catch (error) {
message.error('删除文件失败,请稍后重试');
return false;
} finally {
loading.value = false;
}
];
}
function deleteTestPlanDetail(row: TestDataItem) {
async function deleteTestPlanDetail(row: TestDataItem) {
dialog.warning({
title: '确认删除',
content: `是否确认删除测试项"${row.testItem}"?`,
content: `是否确认删除测试项"${row.Title}"?`,
positiveText: '确认',
negativeText: '取消',
onPositiveClick: () => {
// 递归查找并删除行项目
const removeItem = (items: TestDataItem[], key: string): boolean => {
for (let i = 0; i < items.length; i++) {
if (items[i].key === key) {
items.splice(i, 1);
return true;
}
if (items[i].children && items[i].children.length > 0) {
if (removeItem(items[i].children, key)) {
return true;
}
}
onPositiveClick: async () => {
// 根据ParentKvid递归收集所有需要删除的子项Kvid
const collectKvidsToDelete = (parentKvid: string): string[] => {
const kvidsToDelete: string[] = [];
// 查找直接子项
const directChildren = testData.value.filter(item => item.ParentKvid === parentKvid);
for (const child of directChildren) {
kvidsToDelete.push(child.Kvid);
// 递归查找该子项的所有后代
const descendantKvids = collectKvidsToDelete(child.Kvid);
kvidsToDelete.push(...descendantKvids);
}
return false;
return kvidsToDelete;
};
if (removeItem(testData.value, row.key)) {
message.success('删除成功');
try {
// 收集所有需要删除的Kvid,包括当前项和所有子项
const kvidsToDelete = [row.Kvid, ...collectKvidsToDelete(row.Kvid)];
if (kvidsToDelete.length === 0) {
message.error('未找到需要删除的项目');
return;
}
// 调用deleteEntities方法批量删除
const success = await deleteEntities(kvidsToDelete);
if (success) {
// 从testData中移除已删除的项目
testData.value = testData.value.filter(item => !kvidsToDelete.includes(item.Kvid));
message.success(`成功删除${kvidsToDelete.length}个项目`);
}
} catch (error) {
// console.error('删除过程中发生错误:', error);
message.error('删除失败,请稍后重试');
}
}
});
}
// 测试数据
const testData = ref<TestDataItem[]>([]);
// 处理测试方案变化
function handlePlanChange(value: string) {
if (value) {
......@@ -201,64 +629,110 @@ function handlePlanChange(value: string) {
}
// 处理方案明细确认事件
function handlePlanDetailsConfirm(selectedItems: any[]) {
// 获取当前最大测试项索引,用于生成唯一key
let maxIndex = 0;
if (testData.value.length > 0) {
// 从现有key中提取数字部分
const keyNumbers = testData.value.map(item => {
const matches = item.key.match(/test-(\d+)/);
return matches ? Number.parseInt(matches[1], 10) : 0;
});
maxIndex = Math.max(...keyNumbers);
async function handlePlanDetailsConfirm(selectedItems: any[]) {
if (!testPlan.value) {
message.error('请先选择测试方案');
return;
}
if (!detailedData.Kvid) {
message.error('缺少报告信息');
return;
}
if (!selectedItems || selectedItems.length === 0) {
message.error('请选择要添加的测试项目');
return;
}
// 处理新选择的测试项,添加正确的key
const processedItems = processTestItems(selectedItems, maxIndex);
// 将处理后的数据添加到现有数据中
testData.value = [...testData.value, ...processedItems];
showDetailSelector.value = false;
message.success(`已添加${selectedItems.length}个测试项目`);
}
// 处理测试项目数据,添加必要的字段并构建树形结构
function processTestItems(items: any[], startId = 0) {
// 创建一个新的数组来存储处理后的数据
return items.map((item, index) => {
const newId = startId + index + 1;
return {
key: `test-${newId}`,
testItem: item.testItem,
standardValue: item.standardValue,
allowableRange: item.allowableRange,
measuredValue: null,
verifier: null,
result: '待测试',
// 如果原数据中有children,也需要递归处理
...(item.children
? {
children: processTestItems(item.children, startId + items.length)
}
: {})
try {
loading.value = true;
// 直接使用用户选择的项目来创建比对测试数据
const createData: any = {
Detections: selectedItems,
ReportKvid: detailedData.Kvid
};
const createResponse = await axios.post('/Restful/Kivii.Lims.Entities.ReportItem/Detection.json', createData);
if (createResponse.data && createResponse.data.Results) {
// 处理返回的数据
testData.value = createResponse.data.Results.map((item: any) => ({
...item,
Status: mapStatusType(item.StatusType),
StatusColor: getStatusColor(item.StatusType)
}));
showDetailSelector.value = false;
message.success(`已添加 ${selectedItems.length} 个测试项目`);
} else {
message.error('创建比对测试数据失败');
}
} catch (error) {
message.error('创建比对测试数据失败,请稍后重试');
} finally {
loading.value = false;
}
}
/**
* 清空所有比对测试数据
*
* 删除当前列表中的所有比对测试记录,并重置相关状态
*
* @returns {void} 无返回值
* @usage 绑定到"清空数据"按钮的点击事件
*/
function clearAllTestData() {
dialog.warning({
title: '确认删除',
content: '确定要删除所有比对测试数据吗?此操作不可撤销。',
positiveText: '确定删除',
negativeText: '取消',
onPositiveClick: async () => {
if (testData.value.length === 0) {
message.info('没有数据需要删除');
return;
}
const kvids = testData.value.map(item => item.Kvid).filter(kvid => kvid);
if (kvids.length === 0) {
message.error('没有有效的数据ID');
return;
}
const success = await deleteEntities(kvids);
if (success) {
testData.value = [];
testPlan.value = '';
}
}
});
}
// 页面加载时获取测试方案选项
onMounted(() => {
fetchTestPlanOptions();
if (props.active) {
if (props.item && props.item.Kvid && props.item.Kvid !== detailedData.Kvid) {
fetchDataByReportKvid(props.item.Kvid);
}
}
});
// 检查测试结果
function checkTestResult(row: TestDataItem) {
// 这里可以添加逻辑来判断测试结果是否在允许范围内
// 现在只是占位,实际应用中应根据实测值和标准值+允许偏差范围判断
if (row.measuredValue) {
const testValue = row.TestValue || row.measuredValue;
if (testValue) {
row.result = '待评估';
row.Status = '待评估';
} else {
row.result = '待测试';
row.Status = '待测试';
}
}
</script>
......@@ -275,22 +749,43 @@ function checkTestResult(row: TestDataItem) {
<span>测试方案选择</span>
</div>
<NFormItem label="测试方案" required>
<NSelect
v-model:value="testPlan"
:options="titleOptions"
placeholder="请选择测试方案"
style="width: 100%"
@update:value="handlePlanChange"
></NSelect>
</NFormItem>
<NFormItem label="选择测试方案" required>
<div class="test-plan-selector">
<div class="selector-row">
<NSelect
v-model:value="testPlan"
:options="titleOptions"
:placeholder="hasExistingData ? '已有数据,请先删除' : '请选择测试方案'"
:disabled="hasExistingData"
style="flex: 1"
@update:value="handlePlanChange"
@focus="fetchTitleOptions"
></NSelect>
<NFormItem label="测试日期" required>
<NDatePicker v-model:value="testDate" type="date" clearable style="width: 100%" format="yyyy/MM/dd"></NDatePicker>
</NFormItem>
<!-- 有数据时显示删除按钮 -->
<NButton v-if="hasExistingData" type="error" size="small" class="clear-button" @click="clearAllTestData">
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
<path
fill="currentColor"
d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"
/>
</svg>
</template>
清空数据
</NButton>
</div>
<NFormItem label="测试样品编号" class="last-form-item" required>
<NInput v-model:value="sampleCode" placeholder="请输入测试样品编号"></NInput>
<!-- 简洁的状态提示 -->
<div v-if="hasExistingData" class="status-tip">
<NIcon size="14" color="#f0a020">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" />
</svg>
</NIcon>
<span>已有 {{ testData.length }} 条比对测试数据</span>
</div>
</div>
</NFormItem>
<NDivider></NDivider>
......@@ -305,50 +800,26 @@ function checkTestResult(row: TestDataItem) {
<span>比对测试数据</span>
</div>
<NDataTable
:columns="columns"
:data="testData"
:bordered="false"
:max-height="300"
:loading="loading"
:row-key="row => row.key"
children-key="children"
:indent="20"
:default-expand-all="true"
striped
></NDataTable>
<div class="tree-container">
<NTree
:data="treeData"
block-line
:render-label="renderTreeLabel"
virtual-scroll
style="max-height: 300px; min-height: 200px;"
default-expand-all
/>
<div v-if="loading" class="loading-overlay">
<NSpin size="medium" />
</div>
</div>
<div class="result-summary">
<span class="result-label">总体结果:</span>
<NTag type="warning">未完成</NTag>
</div>
<NDivider></NDivider>
<div class="section-title">
<svg class="section-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path
fill="currentColor"
d="M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M6,4H13V9H18V20H6V4M8,12V14H16V12H8M8,16V18H13V16H8Z"
></path>
</svg>
<span>测试结论</span>
</div>
<NFormItem label="测试结论">
<NRadioGroup v-model:value="testResult">
<NSpace>
<NRadio value="pass">通过</NRadio>
<NRadio value="fail">不通过</NRadio>
<NRadio value="partial">部分通过</NRadio>
</NSpace>
</NRadioGroup>
</NFormItem>
<NFormItem label="测试意见" class="last-form-item">
<NInput v-model:value="testComment" type="textarea" placeholder="请输入测试意见" :rows="4"></NInput>
</NFormItem>
<!-- 测试方案明细选择弹窗 -->
<NModal
v-model:show="showDetailSelector"
......@@ -419,4 +890,176 @@ function checkTestResult(row: TestDataItem) {
:deep(.last-form-item .n-form-item-feedback-wrapper) {
display: none;
}
.test-plan-selector {
width: 100%;
}
.selector-row {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
}
.clear-button {
flex-shrink: 0;
border-radius: 6px;
height: 34px;
padding: 0 12px;
display: flex;
align-items: center;
gap: 6px;
}
.status-tip {
display: flex;
align-items: center;
gap: 6px;
margin-top: 6px;
font-size: 12px;
color: #f0a020;
padding: 4px 0;
}
.status-tip span {
line-height: 1;
}
/* 禁用状态下的选择器样式 */
:deep(.n-base-selection--disabled) {
background-color: #fef2f2;
border-color: #fecaca;
}
:deep(.n-base-selection--disabled .n-base-selection-placeholder) {
color: #dc2626 !important;
font-weight: 500;
}
/* 树形容器样式 */
.tree-container {
position: relative;
border: 1px solid #e8e8e8;
border-radius: 6px;
overflow: hidden;
background: #fff;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
/* 树节点样式 */
:deep(.n-tree-node-content) {
padding: 12px 16px !important;
}
/* 自定义树节点内容样式 */
.tree-test-node-content {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 40px;
gap: 16px;
}
.tree-test-node-left {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
min-width: 0; /* 允许内容收缩 */
}
.tree-test-node-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0; /* 防止右侧内容被压缩 */
}
.tree-test-node-title {
font-weight: 600;
color: #333;
font-size: 14px;
min-width: 120px;
flex-shrink: 0;
}
.tree-node-separator {
color: #ccc;
font-size: 12px;
margin: 0 2px;
flex-shrink: 0;
}
.tree-test-node-detail {
font-size: 12px;
color: #666;
background: #f5f5f5;
padding: 4px 8px;
border-radius: 4px;
white-space: nowrap;
flex-shrink: 0;
}
.tree-test-node-input-group {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.tree-test-input-label {
font-size: 12px;
color: #666;
white-space: nowrap;
}
.tree-test-node-status {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.tree-node-icon {
font-size: 14px;
display: inline-block;
vertical-align: middle;
}
/* 父级节点样式 */
:deep(.n-tree-node[aria-level="1"]) {
background-color: #f8f9fa !important;
}
:deep(.n-tree-node[aria-level="1"]:hover) {
background-color: #e9ecef !important;
}
/* 子级节点样式 */
:deep(.n-tree-node[aria-level="2"]) {
background-color: #fff !important;
}
:deep(.n-tree-node[aria-level="2"]:hover) {
background-color: #f5f5f5 !important;
}
/* 暗色主题适配 */
:deep(.n-tree.n-tree--block-line .n-tree-node) {
border-bottom: 1px solid #e8e8e8;
}
</style>
......@@ -119,7 +119,6 @@
<script setup lang="ts">
import { h, ref, computed, resolveComponent, watch, withDefaults, onBeforeUnmount } from 'vue';
// import type { DataTableColumns } from 'naive-ui';
interface FileItem {
Kvid: string;
......
......@@ -203,11 +203,19 @@ const isCurrentRowReadonly = computed(() => {
* @usage 在组件初始化和激活时调用,用于填充文件类型下拉选择器
*/
async function fetchTitleOptions() {
// 如果已有数据则不重复加载
if (titleOptions.value.length > 0) {
return;
}
try {
loading.value = true;
const response = await axios.post('/Restful/Kivii.Standards.Entities.Standard/Query.json?Type=文件审核');
const response = await axios.get('/Restful/Kivii.Standards.Entities.Standard/Query.json', {
params: {
Type:'文件审核'
}
});
if (response.data && response.data.Results) {
titleOptions.value = response.data.Results.map((item: any, index: number) => ({
label: item.Title,
......@@ -354,8 +362,6 @@ watch(
onMounted(() => {
if (props.active) {
fetchTitleOptions();
if (props.item && props.item.Kvid && props.item.Kvid !== detailedData.Kvid) {
fetchDataByReportKvid(props.item.Kvid);
}
......@@ -365,9 +371,7 @@ onMounted(() => {
watch(
() => props.active,
(newActive) => {
if (newActive && titleOptions.value.length === 0) {
fetchTitleOptions();
if (newActive) {
if (props.item && props.item.Kvid && props.item.Kvid !== detailedData.Kvid) {
fetchDataByReportKvid(props.item.Kvid);
}
......@@ -719,6 +723,7 @@ async function handleFileListClosed(files: any[]) {
label-field="label"
value-field="value"
@update:value="handleTitleChange"
@focus="fetchTitleOptions"
class="selector-input"
></NSelect>
......
......@@ -347,8 +347,7 @@ function saveApplication(submit = true) {
<!-- 步骤3: 比对测试选择 -->
<ComparisonTest
v-if="visitedSteps.has(2) && currentStep === 2"
:test-items="testItems"
:save-as-draft="false"
:item="formModel"
:active="currentStep === 2"
@update:test-items="newItems => (testItems = newItems)"
></ComparisonTest>
......@@ -357,7 +356,7 @@ function saveApplication(submit = true) {
<FieldReviewPlanning v-show="currentStep === 3"></FieldReviewPlanning>
步骤5: 现场审核执行
<FieldReviewExecution v-show="currentStep === 4"></FieldReviewExecution>
<FieldReviewExecution v-show="currentStep === 4"></FieldReviewExecution>
-->
</NForm>
<NDivider></NDivider>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment