feat-实现数据分析-原始数据AI标记筛选和数据导出功能

This commit is contained in:
zhang.weiwei
2025-03-07 17:18:53 +08:00
parent 3ee27a207a
commit 4f5203d4e0
13 changed files with 390 additions and 145 deletions

View File

@@ -3,7 +3,7 @@ VUE_APP_CURRENTMODE = 'dev'
VUE_APP_BASEOSS = 'https://diaoyan-files.automark.cc' VUE_APP_BASEOSS = 'https://diaoyan-files.automark.cc'
#VUE_APP_BASEURL = 'http://planetg-java.test.automark.cc/' #VUE_APP_BASEURL = 'http://planetg-java.test.automark.cc/'
VUE_APP_BASEURL = 'http://yls-api-uat.dctest.digitalyili.com/' VUE_APP_BASEURL = 'https://yls-api-uat.dctest.digitalyili.com'
VUE_APP_DELiVERY_BASEURL='https://javaxq.test.automark.cc/' VUE_APP_DELiVERY_BASEURL='https://javaxq.test.automark.cc/'
VUE_APP_MESSAGE_CENTER ='http://gtech-gateway.dcin-test.digitalyili.com/apigtech/message-send-center/'; VUE_APP_MESSAGE_CENTER ='http://gtech-gateway.dcin-test.digitalyili.com/apigtech/message-send-center/';

View File

@@ -2,7 +2,8 @@ NODE_ENV = 'production'
VUE_APP_CURRENTMODE = 'sit' VUE_APP_CURRENTMODE = 'sit'
VUE_APP_BASEOSS = 'https://test-cxp-pubcos.yili.com/uat-yls' VUE_APP_BASEOSS = 'https://test-cxp-pubcos.yili.com/uat-yls'
VUE_APP_BASEURL = 'https://yls-api-uat.dctest.digitalyili.com'
VUE_APP_BASEURL = 'https://ylst-api-sit.dctest.digitalyili.com'
VUE_APP_JAVA_DELiVERY_BASEURL='https://ylsdist-net-uat.dctest.digitalyili.com' VUE_APP_JAVA_DELiVERY_BASEURL='https://ylsdist-net-uat.dctest.digitalyili.com'

View File

@@ -311,12 +311,12 @@ const toAiInspection = () => {
console.log('questions', questionInfo.questions); console.log('questions', questionInfo.questions);
if (!questionInfo.questions || questionInfo.questions.length === 0) { if (!questionInfo.questions || questionInfo.questions.length === 0) {
// 未设计问卷,不允许质检 // 未设计问卷,不允许质检
message.error('请先设计问卷'); message.error('您还没添加任何问题请添加后再进行AI质检。');
return; return;
} }
const questions = questionInfo.questions; const questions = questionInfo.questions;
emitter.emit('app-loading', { visible: true, description: '正在 AI 质检...请您稍候~' }); emitter.emit('app-loading', { visible: true, description: '正在 AI 质检...请您稍候~' });
aiQualityInspection(sn) aiQualityInspection(route.query.sn, { sn})
.then((res) => { .then((res) => {
if (!res || !res.code === 0 || !res.data) { if (!res || !res.code === 0 || !res.data) {
// 质检接口调用失败 // 质检接口调用失败
@@ -338,11 +338,11 @@ const toAiInspection = () => {
questions.forEach((question) => { questions.forEach((question) => {
questionMap[question.title] = question; questionMap[question.title] = question;
}); });
resultList.forEach((resultQuestion) => { resultList.forEach((result) => {
const question = questionMap[resultQuestion.title]; const question = questionMap[result.title];
if (question) { if (question) {
// 有匹配上的问题,将质检结果合并到问题中 // 有匹配上的问题,将质检结果合并到问题中
question.ai_inspection_result = resultQuestion; question.ai_inspection_result = result.advice;
} }
}); });
store.commit('common/M_COMMON_SET_QUESTION_INFO', questionInfo); store.commit('common/M_COMMON_SET_QUESTION_INFO', questionInfo);

View File

@@ -5,22 +5,46 @@
v-if="[12, 13, 14].includes(question_type)" v-if="[12, 13, 14].includes(question_type)"
v-html="content" v-html="content"
></div> ></div>
<div v-else-if="titleInfo.titleType === 'withIcon'" class="icon-title">
<a-tooltip placement="topLeft">
<template #title>
<div class="custom-head-preview">{{ titleInfo.title }}</div>
</template>
<!--带有图标的标题AI/人工样本标记-->
<div class="common-text">{{ titleInfo.title }}</div>
</a-tooltip>
<a-tooltip placement="topLeft">
<template #title>
<div class="custom-head-preview">{{ titleInfo.titleIconTip }}</div>
</template>
<!--带有图标的标题AI/人工样本标记-->
<img class="icon-title__icon" :src="titleInfo.titleIconSrc" alt="" @click="onIconClicked" />
</a-tooltip>
</div>
<a-tooltip placement="topLeft" v-else> <a-tooltip placement="topLeft" v-else>
<template #title> <template #title>
<div class="custom-head-preview" v-html="content"></div> <div class="custom-head-preview" v-html="content"></div>
</template> </template>
<div class="custom-head-main"> <div class="custom-head-main">
<!-- 下载题型--> <!-- 下载题型-->
<div class="file-question" v-if="question_type === 18" :style="{ width: data.width + 'px' }"> <div
class="file-question"
v-if="question_type === 18"
:style="{ width: data.width + 'px' }"
>
<div class="label">{{ content }}</div> <div class="label">{{ content }}</div>
<i class="iconfont icon-xiazai" style="color: #70b936; cursor: pointer" @click="download"></i> <i
class="iconfont icon-xiazai"
style="color: #70b936; cursor: pointer"
@click="download"
></i>
<!-- <a-button size="mini" @click="download">下载全部附件333</a-button>--> <!-- <a-button size="mini" @click="download">下载全部附件333</a-button>-->
</div> </div>
<!-- 图文题--> <!-- 图文题-->
<div class="common-text" v-else>{{ content }}</div> <div class="common-text" v-else>{{ content }}</div>
</div> </div>
</a-tooltip> </a-tooltip>
<!-- 下载中心 --> <!-- 下载中心 -->
<DownloadCenter v-model:visible="downloadVisible" v-if="downloadVisible"></DownloadCenter> <DownloadCenter v-model:visible="downloadVisible" v-if="downloadVisible"></DownloadCenter>
</div> </div>
@@ -47,6 +71,8 @@ const props = defineProps({
type: String type: String
} }
}); });
const emit = defineEmits(['iconClick']);
const titleInfo = ref({});
const content = ref(''); const content = ref('');
const question_index = ref(null); const question_index = ref(null);
const question_type = ref(null); const question_type = ref(null);
@@ -90,7 +116,15 @@ function downloadCenter() {
}); });
} }
/**
* 点击标题图标,发送事件
*/
function onIconClicked() {
emit('iconClick', titleInfo.value);
}
watchEffect(() => { watchEffect(() => {
titleInfo.value = props.data;
content.value = props.data.title; content.value = props.data.title;
question_index.value = props.data.question_index; question_index.value = props.data.question_index;
question_type.value = props.data.question_type; question_type.value = props.data.question_type;
@@ -137,5 +171,15 @@ watchEffect(() => {
align-items: center; align-items: center;
word-wrap: break-word; word-wrap: break-word;
} }
.icon-title {
display: flex;
align-items: center;
&__icon {
width: 20px;
height: 20px;
margin-left: 10px;
}
}
} }
</style> </style>

View File

@@ -12,7 +12,12 @@
<template v-for="col in tableHeaders" :key="col.dataIndex" #[col.dataIndex]="{ record, text }"> <template v-for="col in tableHeaders" :key="col.dataIndex" #[col.dataIndex]="{ record, text }">
<template v-if="col.dataIndex === 'is_mark'"> <template v-if="col.dataIndex === 'is_mark'">
<slot name="is_mark" v-bind:record="record"> <slot name="is_mark" v-bind:record="record">
<i v-if="record.is_mark == 1" class="icon iconfont show-sign curror" @click="hideSign(record)">&#xe7d6;</i> <i
v-if="record.is_mark == 1"
class="icon iconfont show-sign curror"
@click="hideSign(record)"
>&#xe7d6;</i
>
<i v-else class="icon iconfont hide-sign curror" @click="showSign(record)">&#xe7d6;</i> <i v-else class="icon iconfont hide-sign curror" @click="showSign(record)">&#xe7d6;</i>
</slot> </slot>
</template> </template>
@@ -126,7 +131,7 @@ const props = defineProps({
default: '' default: ''
} }
}); });
const emit = defineEmits(['change', 'select']); const emit = defineEmits(['change', 'select', 'headerClick']);
const store = useStore(); const store = useStore();
const data = ref([]); const data = ref([]);
const columns = ref(null); const columns = ref(null);
@@ -134,7 +139,9 @@ const searchParams = ref(props.params);
const { perPage: per_page, total, page, rowKey } = toRefs(props); const { perPage: per_page, total, page, rowKey } = toRefs(props);
const { pagination } = usePagination(per_page, total, page); const { pagination } = usePagination(per_page, total, page);
const { tableHeaders } = useGeneratorTableColumns(columns); const { tableHeaders } = useGeneratorTableColumns(columns);
const { customHeaders } = useCustomHeaderTitle(tableHeaders, props.sn, searchParams); const { customHeaders } = useCustomHeaderTitle(tableHeaders, props.sn, searchParams, (column) => {
emit('headerClick', column);
});
// 页码&分页条数,改变时候触发 // 页码&分页条数,改变时候触发
const handlePageChange = (data) => { const handlePageChange = (data) => {
@@ -187,6 +194,7 @@ const showSign = (item) => {
const hideSign = (item) => { const hideSign = (item) => {
emit('sign', [item], false); emit('sign', [item], false);
}; };
watch( watch(
() => props.params, () => props.params,
(val) => { (val) => {

View File

@@ -11,7 +11,10 @@
<img class="suffix-icon" :src="require('@/assets/img/select-arrow-down.png')" alt=""> <img class="suffix-icon" :src="require('@/assets/img/select-arrow-down.png')" alt="">
</template> </template>
</a-select> --> </a-select> -->
<a-dropdown class="searchSelect operChd" :getPopupContainer="(triggerNode) => triggerNode.parentNode"> <a-dropdown
class="searchSelect operChd"
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
>
<template #overlay> <template #overlay>
<a-menu @click="opChange"> <a-menu @click="opChange">
<a-menu-item v-for="item in opList" :key="item.value"> <a-menu-item v-for="item in opList" :key="item.value">
@@ -24,7 +27,11 @@
<img class="suffix-icon" :src="require('@/assets/img/select-arrow-down.png')" alt="" /> <img class="suffix-icon" :src="require('@/assets/img/select-arrow-down.png')" alt="" />
</a-button> </a-button>
</a-dropdown> </a-dropdown>
<a-button class="operChd custom-button" @click="emit('cleanAllBtn', true)">清空数据</a-button>
<!-- delete by zhangweiwei 20250331_ai 无效样本标记-不显示清空数据按钮 start -->
<!-- <a-button class="operChd custom-button" @click="emit('cleanAllBtn', true)">清空数据</a-button> -->
<!-- delete by zhangweiwei 20250331_ai 无效样本标记-不显示清空数据按钮 end -->
<!-- <a-button class="operChd" @click="handleDownload">下载全部答卷</a-button> --> <!-- <a-button class="operChd" @click="handleDownload">下载全部答卷</a-button> -->
<!-- <a-dropdown class="searchSelect operChd" :getPopupContainer="(triggerNode) => triggerNode.parentNode"> <!-- <a-dropdown class="searchSelect operChd" :getPopupContainer="(triggerNode) => triggerNode.parentNode">
<template #overlay> <template #overlay>
@@ -40,7 +47,9 @@
<CaretDownOutlined class="suffix-icon" /> <CaretDownOutlined class="suffix-icon" />
</a-button> </a-button>
</a-dropdown> --> </a-dropdown> -->
<a-button type="primary" class="operChd custom-button" @click="dataChange({ key: 2 })">全量数据导出</a-button> <a-button type="primary" class="operChd custom-button" @click="dataChange({ key: 2 })"
>全量数据导出</a-button
>
<!-- <a-select <!-- <a-select
:getPopupContainer="triggerNode=>triggerNode.parentNode" :getPopupContainer="triggerNode=>triggerNode.parentNode"
class="searchSelect operChd" class="searchSelect operChd"
@@ -51,8 +60,14 @@
<img class="suffix-icon" :src="require('@/assets/img/select-arrow-down.png')" alt=""> <img class="suffix-icon" :src="require('@/assets/img/select-arrow-down.png')" alt="">
</template> </template>
</a-select> --> </a-select> -->
<a-button type="primary" class="operChd custom-button" @click="noData">无效样本处理</a-button>
<a-button type="primary" class="operChd custom-button" @click="configVisible = true">列表配置</a-button> <!-- delete by zhangweiwei 20250331_ai 无效样本标记-不显示无效样本处理按钮 start -->
<!-- <a-button type="primary" class="operChd custom-button" @click="noData">无效样本处理</a-button> -->
<!-- delete by zhangweiwei 20250331_ai 无效样本标记-不显示无效样本处理按钮 end -->
<a-button type="primary" class="operChd custom-button" @click="configVisible = true"
>列表配置</a-button
>
<!-- 同步数据 --> <!-- 同步数据 -->
<a-tooltip :overlayStyle="{ 'max-width': 'none' }"> <a-tooltip :overlayStyle="{ 'max-width': 'none' }">
<template v-slot:title> 产品测试模块会同步该列表数据并作统计展示 </template> <template v-slot:title> 产品测试模块会同步该列表数据并作统计展示 </template>
@@ -68,7 +83,12 @@
</a-tooltip> </a-tooltip>
</div> </div>
<!-- 配置 --> <!-- 配置 -->
<ColumnConfig v-model:visible="configVisible" :data="answer_columns" :sn="sn" @ok="handleConfig" /> <ColumnConfig
v-model:visible="configVisible"
:data="answer_columns"
:sn="sn"
@ok="handleConfig"
/>
<!-- 导出 --> <!-- 导出 -->
<DownloadData <DownloadData
v-model:visible="downloadVisible" v-model:visible="downloadVisible"
@@ -78,7 +98,10 @@
@ok="handleDownload" @ok="handleDownload"
/> />
<!-- 下载中心 --> <!-- 下载中心 -->
<DownloadCenter v-model:visible="downloadCenterVisible" v-if="downloadCenterVisible"></DownloadCenter> <DownloadCenter
v-model:visible="downloadCenterVisible"
v-if="downloadCenterVisible"
></DownloadCenter>
</template> </template>
<script setup> <script setup>
import { ref, toRefs, reactive, defineProps, watch, onMounted, computed, nextTick } from 'vue'; import { ref, toRefs, reactive, defineProps, watch, onMounted, computed, nextTick } from 'vue';
@@ -115,13 +138,15 @@ const searchData = ref({
test1: null, test1: null,
test2: null test2: null
}); });
/** modify by zhangweiwei 20250331_ai 无效样本标记-批量操作中标记和取消标记按钮删除 start */
const opList = ref([ const opList = ref([
{ value: '3', label: '标记' },
{ value: '4', label: '取消标记' },
{ value: '0', label: '下载答卷' }, { value: '0', label: '下载答卷' },
{ value: '1', label: '导出数据' }, { value: '1', label: '导出数据' },
{ value: '2', label: '删除' } { value: '2', label: '删除' }
]); ]);
/** modify by zhangweiwei 20250331_ai 无效样本标记-批量操作中标记和取消标记按钮删除 end */
const syncDataLoading = ref(false); const syncDataLoading = ref(false);
// 导出选中答卷 // 导出选中答卷

View File

@@ -76,10 +76,19 @@
</div> </div>
</a-modal> </a-modal>
<!-- 导出框 --> <!-- 导出框 -->
<a-modal class="custom-modal" v-model:visible="dataExportVis" title="数据导出" @ok="dataExportOk"> <a-modal
class="custom-modal"
v-model:visible="dataExportVis"
title="数据导出"
@ok="dataExportOk"
>
<a-form ref="formRef" :model="formState" :rules="rules" :labelCol="{ span: 5 }"> <a-form ref="formRef" :model="formState" :rules="rules" :labelCol="{ span: 5 }">
<a-form-item ref="type" label="文件格式" name="type"> <a-form-item ref="type" label="文件格式" name="type">
<a-radio-group v-model:value="formState.type" @change="changeType" class="custom-radio-group"> <a-radio-group
v-model:value="formState.type"
@change="changeType"
class="custom-radio-group"
>
<a-radio class="custom-radio" value="1">Excel(.xlsx)</a-radio> <a-radio class="custom-radio" value="1">Excel(.xlsx)</a-radio>
<a-radio class="custom-radio" value="2">CSV(.csv)</a-radio> <a-radio class="custom-radio" value="2">CSV(.csv)</a-radio>
<a-radio class="custom-radio" value="3">SPSS(.sav)</a-radio> <a-radio class="custom-radio" value="3">SPSS(.sav)</a-radio>
@@ -96,7 +105,9 @@
<a-form-item ref="header_column_type" label="题目行格式" @change="changeHeaderColumnType"> <a-form-item ref="header_column_type" label="题目行格式" @change="changeHeaderColumnType">
<a-radio-group v-model:value="formState.header_column_type" class="custom-radio-group"> <a-radio-group v-model:value="formState.header_column_type" class="custom-radio-group">
<a-radio class="custom-radio" value="0" :disabled="formState.type === '3'">文本</a-radio> <a-radio class="custom-radio" value="0" :disabled="formState.type === '3'"
>文本</a-radio
>
<a-radio class="custom-radio" value="1">序号</a-radio> <a-radio class="custom-radio" value="1">序号</a-radio>
</a-radio-group> </a-radio-group>
<!--<a-select class="custom-select" v-model:value="formState.header_column_type" placeholder="请选择">--> <!--<a-select class="custom-select" v-model:value="formState.header_column_type" placeholder="请选择">-->
@@ -126,6 +137,14 @@
<a-switch v-model:checked="formState.contains_base" /> <a-switch v-model:checked="formState.contains_base" />
</div> </div>
</a-form-item> </a-form-item>
<!-- modify by zhangweiwei 20250331_ai 无效样本标记-数据导出弹窗增加导出有效样本录入项 start -->
<a-form-item label="导出有效样本" name="is_mark">
<div class="switchBox">
<span></span>
<a-switch v-model:checked="formState.is_mark" />
</div>
</a-form-item>
<!-- modify by zhangweiwei 20250331_ai 无效样本标记-数据导出弹窗增加导出有效样本录入项 end -->
<a-form-item v-if="formState.contains_base"> <a-form-item v-if="formState.contains_base">
<a-select <a-select
class="custom-select" class="custom-select"
@@ -139,7 +158,9 @@
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll()">全选</a-button> <a-button size="small" type="primary" @click="selectAll()">全选</a-button>
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll()">清空</a-button> <a-button style="margin-left: 6px" size="small" @click="cancelSelectAll()"
>清空</a-button
>
</div> </div>
<a-divider style="margin: 4px 0" /> <a-divider style="margin: 4px 0" />
<v-nodes :vnodes="VNodes" /> <v-nodes :vnodes="VNodes" />
@@ -154,7 +175,12 @@
</a-form-item> --> </a-form-item> -->
<a-form-item ref="name" label="数据导出范围" v-if="operType !== 1"> <a-form-item ref="name" label="数据导出范围" v-if="operType !== 1">
<a-radio-group v-model:value="formState.isFilter" class="custom-radio-group"> <a-radio-group v-model:value="formState.isFilter" class="custom-radio-group">
<a-radio class="custom-radio" v-for="radio in isFilterData" :key="radio.value" :value="radio.value"> <a-radio
class="custom-radio"
v-for="radio in isFilterData"
:key="radio.value"
:value="radio.value"
>
{{ radio.label }} {{ radio.label }}
</a-radio> </a-radio>
</a-radio-group> </a-radio-group>
@@ -202,7 +228,10 @@
<a-menu-item @click="putSurveysAnswersTop(item.id)" v-if="item.id"> <a-menu-item @click="putSurveysAnswersTop(item.id)" v-if="item.id">
<div>置顶方案</div> <div>置顶方案</div>
</a-menu-item> </a-menu-item>
<a-menu-item @click="postSurveysAnswersClone(item.id)" v-if="planList.length < 10 && item.id"> <a-menu-item
@click="postSurveysAnswersClone(item.id)"
v-if="planList.length < 10 && item.id"
>
<div>复制方案</div> <div>复制方案</div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
@@ -472,6 +501,10 @@ const getSurveysAnswersDown = async () => {
let subData = formState; let subData = formState;
subData.contains_base = subData.contains_base === true ? 1 : 0; subData.contains_base = subData.contains_base === true ? 1 : 0;
subData.include_open = subData.include_open === true ? 1 : 0; subData.include_open = subData.include_open === true ? 1 : 0;
/** modify by zhangweiwei 20250331_ai 无效样本标记-数据导出弹窗,增加【导出有效样本】录入项 start */
subData.is_mark = subData.is_mark === true ? '0' : '0,1,2'; //导出有效样本就是只传”0”;导出全部就是”0,1,2”
/** modify by zhangweiwei 20250331_ai 无效样本标记-数据导出弹窗,增加【导出有效样本】录入项 end */
//subData.is_import = subData.is_import===true?1:0 //subData.is_import = subData.is_import===true?1:0
subData.contains_columns.push('id'); subData.contains_columns.push('id');
subData.sn = sn; subData.sn = sn;
@@ -580,6 +613,7 @@ const formState = reactive({
header_column_type: '0', //答案行格式 header_column_type: '0', //答案行格式
column_type: '0', //答案行格式 column_type: '0', //答案行格式
contains_base: true, //作答信息开关 contains_base: true, //作答信息开关
is_mark: true, // 有效样本开关
contains_columns: [], //作答信息多选 contains_columns: [], //作答信息多选
//is_import:true,//导入数据开关 //is_import:true,//导入数据开关
isFilter: '1', isFilter: '1',
@@ -775,7 +809,9 @@ const onSearch = (e) => {
const planList = ref([]); const planList = ref([]);
// 当前页数据 // 当前页数据
const nowPlanList = computed(() => { const nowPlanList = computed(() => {
return planList.value.filter((el, i) => i >= (nowPage.value - 1) * 5 && i + 1 <= nowPage.value * 5); return planList.value.filter(
(el, i) => i >= (nowPage.value - 1) * 5 && i + 1 <= nowPage.value * 5
);
}); });
// 方案列表 // 方案列表
const getSurveysAnswerFilter = async () => { const getSurveysAnswerFilter = async () => {

View File

@@ -87,8 +87,12 @@
</div> </div>
</div> </div>
<div class="addBtn"> <div class="addBtn">
<div class="addBtnLi" @click="addList"><PlusSquareOutlined class="addIcon" />添加筛选条件</div> <div class="addBtnLi" @click="addList">
<div class="addBtnLi" @click="addGroup"><PlusSquareOutlined class="addIcon" />添加条件集合</div> <PlusSquareOutlined class="addIcon" />添加筛选条件
</div>
<div class="addBtnLi" @click="addGroup">
<PlusSquareOutlined class="addIcon" />添加条件集合
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -109,9 +113,15 @@
@input="timeLang" @input="timeLang"
placeholder="请输入" placeholder="请输入"
/> />
<template v-if="searchData.useTimeVal == 'between' || searchData.useTimeVal == 'notBetween'"> <template
v-if="searchData.useTimeVal == 'between' || searchData.useTimeVal == 'notBetween'"
>
<p class="betweenSymbol">-</p> <p class="betweenSymbol">-</p>
<a-input style="width: 80px" v-model:value="searchData.useTimeInputValL" placeholder="请输入" /> <a-input
style="width: 80px"
v-model:value="searchData.useTimeInputValL"
placeholder="请输入"
/>
</template> </template>
<a-select <a-select
:getPopupContainer="(triggerNode) => triggerNode.parentNode" :getPopupContainer="(triggerNode) => triggerNode.parentNode"
@@ -182,8 +192,15 @@
> >
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll('publish')">全选</a-button> <a-button size="small" type="primary" @click="selectAll('publish')"
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll('publish')">清空</a-button> >全选</a-button
>
<a-button
style="margin-left: 6px"
size="small"
@click="cancelSelectAll('publish')"
>清空</a-button
>
</div> </div>
<a-divider style="margin: 4px 0" /> <a-divider style="margin: 4px 0" />
<v-nodes :vnodes="VNodes" /> <v-nodes :vnodes="VNodes" />
@@ -206,8 +223,13 @@
> >
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll('answerStatus')">全选</a-button> <a-button size="small" type="primary" @click="selectAll('answerStatus')"
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll('answerStatus')" >全选</a-button
>
<a-button
style="margin-left: 6px"
size="small"
@click="cancelSelectAll('answerStatus')"
>清空</a-button >清空</a-button
> >
</div> </div>
@@ -233,8 +255,15 @@
> >
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll('version')">全选</a-button> <a-button size="small" type="primary" @click="selectAll('version')"
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll('version')">清空</a-button> >全选</a-button
>
<a-button
style="margin-left: 6px"
size="small"
@click="cancelSelectAll('version')"
>清空</a-button
>
</div> </div>
<a-divider style="margin: 4px 0" /> <a-divider style="margin: 4px 0" />
<v-nodes :vnodes="VNodes" /> <v-nodes :vnodes="VNodes" />
@@ -271,8 +300,15 @@
> >
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll('logics')">全选</a-button> <a-button size="small" type="primary" @click="selectAll('logics')"
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll('logics')">清空</a-button> >全选</a-button
>
<a-button
style="margin-left: 6px"
size="small"
@click="cancelSelectAll('logics')"
>清空</a-button
>
</div> </div>
<a-divider style="margin: 4px 0" /> <a-divider style="margin: 4px 0" />
<v-nodes :vnodes="VNodes" /> <v-nodes :vnodes="VNodes" />
@@ -297,8 +333,15 @@
> >
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll('isMark')">全选</a-button> <a-button size="small" type="primary" @click="selectAll('isMark')"
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll('isMark')">清空</a-button> >全选</a-button
>
<a-button
style="margin-left: 6px"
size="small"
@click="cancelSelectAll('isMark')"
>清空</a-button
>
</div> </div>
<a-divider style="margin: 4px 0" /> <a-divider style="margin: 4px 0" />
<v-nodes :vnodes="VNodes" /> <v-nodes :vnodes="VNodes" />
@@ -323,7 +366,12 @@
<div class="codeInput" @click="isFocus = true" v-else> <div class="codeInput" @click="isFocus = true" v-else>
<div class="codeContent"> <div class="codeContent">
<span v-if="inputValue.length == 0">{{ placeholder }}</span> <span v-if="inputValue.length == 0">{{ placeholder }}</span>
<div v-else v-for="(item, index) in inputValue" :key="index" style="display: inline-block"> <div
v-else
v-for="(item, index) in inputValue"
:key="index"
style="display: inline-block"
>
<span class="codeSpan">{{ item }}</span> <span class="codeSpan">{{ item }}</span>
</div> </div>
</div> </div>
@@ -351,8 +399,13 @@
> >
<template #dropdownRender="{ menuNode: VNodes }"> <template #dropdownRender="{ menuNode: VNodes }">
<div style="padding: 4px 12px; cursor: pointer"> <div style="padding: 4px 12px; cursor: pointer">
<a-button size="small" type="primary" @click="selectAll('answer_type')">全选</a-button> <a-button size="small" type="primary" @click="selectAll('answer_type')"
<a-button style="margin-left: 6px" size="small" @click="cancelSelectAll('answer_type')" >全选</a-button
>
<a-button
style="margin-left: 6px"
size="small"
@click="cancelSelectAll('answer_type')"
>清空</a-button >清空</a-button
> >
</div> </div>
@@ -412,7 +465,18 @@
<newModal ref="newModalRef" @newSave="newSave" /> <newModal ref="newModalRef" @newSave="newSave" />
</template> </template>
<script setup> <script setup>
import { ref, toRefs, toRaw, reactive, defineProps, onBeforeMount, onMounted, watch, nextTick, computed } from 'vue'; import {
ref,
toRefs,
toRaw,
reactive,
defineProps,
onBeforeMount,
onMounted,
watch,
nextTick,
computed
} from 'vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import moment from 'moment'; import moment from 'moment';
@@ -498,6 +562,9 @@ const logics = computed(() => {
return nVre; return nVre;
}); });
const mark = [ const mark = [
/** add by zhangweiwei 20250331_ai 无效样本标记-筛选标记状态新增【AI标记】选项 start */
{ label: 'AI标记', value: '2' },
/** add by zhangweiwei 20250331_ai 无效样本标记-筛选标记状态新增【AI标记】选项 start */
{ label: '已标记', value: '1' }, { label: '已标记', value: '1' },
{ label: '未标记', value: '0' } { label: '未标记', value: '0' }
]; ];
@@ -711,7 +778,10 @@ const showData = {
// 配额逻辑 // 配额逻辑
logics: [], logics: [],
// 标记状态 // 标记状态
isMark: ['1', '0'], /** modify by zhangweiwei 20250331_ai 无效样本标记-筛选标记状态新增【AI标记】选项 start */
isMark: ['2', '1', '0'],
/** modify by zhangweiwei 20250331_ai 无效样本标记-筛选标记状态新增【AI标记】选项 start */
// 数据类型 // 数据类型
answer_type: ['0', '1'] answer_type: ['0', '1']
}; };
@@ -1119,7 +1189,8 @@ const answerAss = (el) => {
//大于等于啥的 //大于等于啥的
if (el.operVal) data.operator = el.operVal; if (el.operVal) data.operator = el.operVal;
// 最后那个答案 // 最后那个答案
if (el.asVal || el.asVal == 0) data.answer = typeof el.asVal == 'object' ? el.asVal.join() : el.asVal; if (el.asVal || el.asVal == 0)
data.answer = typeof el.asVal == 'object' ? el.asVal.join() : el.asVal;
// 最后那个答案如果是时间 // 最后那个答案如果是时间
if (el?.answerType == 'datetime') { if (el?.answerType == 'datetime') {
data.answer = {}; data.answer = {};

View File

@@ -4,7 +4,7 @@ const defaultOptions = {
left_fixed_key: 'id', // 左侧固定列 left_fixed_key: 'id', // 左侧固定列
right_fixed_key: 'operation', //右侧固定列 right_fixed_key: 'operation', //右侧固定列
show_operator: true, // 是否显示操作列 show_operator: true, // 是否显示操作列
show_check: true, show_check: false, // 去除标记和标记原因列
fixed: 'left' fixed: 'left'
}; };
@@ -21,39 +21,9 @@ export default function useGeneratorTableColumns(columns, options = {}) {
options = { ...defaultOptions, ...options }; options = { ...defaultOptions, ...options };
const tableHeaders = ref([]); const tableHeaders = ref([]);
function flatData(columns) { function flatData(columns) {
let columnsSing = {};
let columnsType = {};
let columnsMemo = {};
if (options.show_check) {
columnsSing = {
title: '',
key: 'is_mark',
dataIndex: 'is_mark',
align: 'left',
width: 50,
slots: { customRender: 'is_mark' },
fixed: 'left'
};
// columnsType = {
// title: '数据类型',
// key: 'type',
// dataIndex: 'type',
// align: 'center',
// width: 100,
// slots: { customRender: 'type' },
// fixed: 'left'
// }
columnsMemo = {
title: '标记原因',
key: 'mark_des',
dataIndex: 'mark_des',
align: 'left',
width: 150,
slots: { customRender: 'mark_des' },
fixed: 'left'
};
}
let result = columns.reduce((previousValue, currentValue) => { let result = columns.reduce((previousValue, currentValue) => {
console.log('useGeneratorTableColumns currentValue---->', currentValue);
const arr = Object.entries(currentValue).map(([key, value]) => ({ const arr = Object.entries(currentValue).map(([key, value]) => ({
...value, ...value,
key, key,
@@ -63,8 +33,10 @@ export default function useGeneratorTableColumns(columns, options = {}) {
// align: 'center', // align: 'center',
slots: { customRender: key } slots: { customRender: key }
})); }));
console.log('useGeneratorTableColumns arr---->', arr);
return previousValue.concat(arr); return previousValue.concat(arr);
}, []); }, []);
console.log('useGeneratorTableColumns result---->', result);
// 手动修改作答ID数组位置 // 手动修改作答ID数组位置
const snData = result.find((el) => el.key == 'id'); const snData = result.find((el) => el.key == 'id');
result = result.filter((el) => el.key != 'id'); result = result.filter((el) => el.key != 'id');
@@ -72,8 +44,37 @@ export default function useGeneratorTableColumns(columns, options = {}) {
const typeData = result.find((el) => el.key == 'answer_type'); const typeData = result.find((el) => el.key == 'answer_type');
result = result.filter((el) => el.key != 'answer_type'); result = result.filter((el) => el.key != 'answer_type');
result.unshift(columnsMemo); /** add by zhangweiwei 20250331_ai 无效样本标记-列表新增AI/人工样本标记 start */
// result.unshift(columnsType) // 添加人工样本标记
const manualSampleMark = {
title: '人工样本标记',
titleType:'withIcon', // 标题类型,带图标
titleIconSrc: require('@/assets/img/mxd/rate2.png'), // 图标资源
titleIconTip: 'AI智筛精准标记', // 图标hover提示
key: 'manual_mark',
dataIndex: 'manual_mark',
align: 'left',
width: 150,
slots: { customRender: 'manual_mark' },
fixed: 'left'
};
result.unshift(manualSampleMark);
// 添加AI样本标记
const aiSampleMark = {
title: 'AI样本标记',
titleType:'withIcon', // 标题类型,带图标
titleIconSrc: require('@/assets/img/mxd/rate1.png'), // 图标资源
titleIconTip: '批量处理,方便快捷', // 图标hover提示
key: 'ai_mark',
dataIndex: 'ai_mark',
align: 'left',
width: 150,
slots: { customRender: 'ai_mark' },
fixed: 'left'
};
result.unshift(aiSampleMark);
/** add by zhangweiwei 20250331_ai 无效样本标记-列表新增AI/人工样本标记 end */
if (typeData) { if (typeData) {
typeData.fixed = 'left'; typeData.fixed = 'left';
typeData.width = 100; typeData.width = 100;
@@ -84,7 +85,6 @@ export default function useGeneratorTableColumns(columns, options = {}) {
snData.fixed = 'left'; snData.fixed = 'left';
result.unshift(snData); result.unshift(snData);
} }
result.unshift(columnsSing);
if (options.show_operator) { if (options.show_operator) {
result.push({ result.push({
title: '操作', title: '操作',

View File

@@ -1,11 +1,19 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import CustomTableHeaderCell from '@/views/DataAnalyse/components/CustomTableHeaderCell'; import CustomTableHeaderCell from '@/views/DataAnalyse/components/CustomTableHeaderCell';
export default function renderCustomHeader(columns, sn, search_params) { export default function renderCustomHeader(columns, sn, search_params, iconClickFunc) {
const customHeaders = ref([]); const customHeaders = ref([]);
function renderCell(data, sn, search_params) { function renderCell(data, sn, search_params, iconClickFunc) {
return <CustomTableHeaderCell data={data} sn={sn} search_params={search_params} />; return (
<CustomTableHeaderCell
data={data}
sn={sn}
search_params={search_params}
onIconClick={iconClickFunc}
/>
);
} }
watch( watch(
columns, columns,
(data) => { (data) => {
@@ -13,7 +21,7 @@ export default function renderCustomHeader(columns, sn, search_params) {
customHeaders.value = data.map((item) => { customHeaders.value = data.map((item) => {
return { return {
...item, ...item,
title: renderCell(item, sn, search_params.value) title: renderCell(item, sn, search_params.value, iconClickFunc)
}; };
}); });
} }
@@ -29,7 +37,7 @@ export default function renderCustomHeader(columns, sn, search_params) {
customHeaders.value = columns.value.map((item) => { customHeaders.value = columns.value.map((item) => {
return { return {
...item, ...item,
title: renderCell(item, sn, data) title: renderCell(item, sn, data, iconClickFunc)
}; };
}); });
} }

View File

@@ -80,6 +80,7 @@
@change="onPageChange" @change="onPageChange"
@select="onSelectChange" @select="onSelectChange"
@sign="Sign" @sign="Sign"
@headerClick="onHeaderClick"
> >
<template #operation="{ record }"> <template #operation="{ record }">
<a @click="openDetailModal(record)" style="margin-right: 5px">查看</a> <a @click="openDetailModal(record)" style="margin-right: 5px">查看</a>
@@ -112,7 +113,10 @@
</DataTable> </DataTable>
</div> </div>
</div> </div>
<sign-invalid-modal v-model:visible="invalidVisible" @ok="handleSignInvalid"></sign-invalid-modal> <sign-invalid-modal
v-model:visible="invalidVisible"
@ok="handleSignInvalid"
></sign-invalid-modal>
<download-data <download-data
v-model:visible="downloadVisible" v-model:visible="downloadVisible"
:params="queryState" :params="queryState"
@@ -126,7 +130,13 @@
:sn="sn" :sn="sn"
@ok="handleConfig" @ok="handleConfig"
></column-config> --> ></column-config> -->
<qs-detail v-model:visible="detailVisible" :data="tableData" :detailId="detailId" :sn="sn" :type="0"></qs-detail> <qs-detail
v-model:visible="detailVisible"
:data="tableData"
:detailId="detailId"
:sn="sn"
:type="0"
></qs-detail>
</div> </div>
</a-spin> </a-spin>
<newModal <newModal
@@ -142,8 +152,17 @@
@resShowFlag="resShowFlag" @resShowFlag="resShowFlag"
/> />
<!-- 下载中心 --> <!-- 下载中心 -->
<DownloadCenter v-model:visible="downloadCenterVisible" v-if="downloadCenterVisible"></DownloadCenter> <DownloadCenter
<a-modal v-model:visible="visible" title="标记原因" ok-text="确认" cancel-text="取消" @ok="hideModal"> v-model:visible="downloadCenterVisible"
v-if="downloadCenterVisible"
></DownloadCenter>
<a-modal
v-model:visible="visible"
title="标记原因"
ok-text="确认"
cancel-text="取消"
@ok="hideModal"
>
<a-input placeholder="请输入标记原因" :maxlength="30" v-model:value="singDesc"> <a-input placeholder="请输入标记原因" :maxlength="30" v-model:value="singDesc">
<template #prefix> <template #prefix>
<user-outlined type="user" /> <user-outlined type="user" />
@@ -173,7 +192,13 @@ import ColumnConfig from './components/ColumnConfig';
import countIcon from '@/assets/img/data_count_icon.png'; import countIcon from '@/assets/img/data_count_icon.png';
import increaseIcon from '@/assets/img/newly_increase_icon.png'; import increaseIcon from '@/assets/img/newly_increase_icon.png';
import { answers_export, getParticularList, delParticularItem } from '@/api/data-analyse'; import { answers_export, getParticularList, delParticularItem } from '@/api/data-analyse';
import { getNullDealConfig, answer_mark_batch, nullDeal, answer_mark, answer_mark_del } from '@/api/qc'; import {
getNullDealConfig,
answer_mark_batch,
nullDeal,
answer_mark,
answer_mark_del
} from '@/api/qc';
import { convertQueryToString } from '@utils/httpFormat'; import { convertQueryToString } from '@utils/httpFormat';
import DataTable from '@views/DataAnalyse/components/DataTable'; import DataTable from '@views/DataAnalyse/components/DataTable';
import newDataTable from '@views/DataAnalyse/components/newDataTable'; import newDataTable from '@views/DataAnalyse/components/newDataTable';
@@ -262,7 +287,9 @@ const selectedRowKeys = ref([]);
const selectedSns = computed(() => { const selectedSns = computed(() => {
return selectedRowKeys.value?.length return selectedRowKeys.value?.length
? tableData.value.filter((item, index) => selectedRowKeys.value.includes(index)).map((i) => i.sn) || [] ? tableData.value
.filter((item, index) => selectedRowKeys.value.includes(index))
.map((i) => i.sn) || []
: []; : [];
}); });
@@ -578,6 +605,21 @@ function onPageChange(data) {
per_page.value = data.pageSize; per_page.value = data.pageSize;
getData({ page: page.value, per_page: per_page.value }); getData({ page: page.value, per_page: per_page.value });
} }
/**
* 监听表头点击事件
* @param column
*/
function onHeaderClick(column) {
console.log('onHeaderClick', column);
if (column.key === 'ai_mark') {
console.log('AI 标记');
} else if (column.key === 'manual_mark') {
console.log('人工标记');
noData();
}
}
const hideModal = (call) => { const hideModal = (call) => {
let memo = (singDesc.value ??= ''); let memo = (singDesc.value ??= '');
saveSing(memo); saveSing(memo);

View File

@@ -194,12 +194,12 @@
> >
</iframe> </iframe>
</el-drawer> --> </el-drawer> -->
<div ref="aiAssistantWrapper" class="ai-assistant-wrapper">
<a-modal <a-modal
v-model:visible="aiDialogVisible" v-model:visible="aiAssistantVisible"
:getContainer="() => $refs.aiAssistantWrapper"
:closable="false" :closable="false"
:footer="null" :footer="null"
wrapClassName="ai-assistant-dialog-wrap"
dialogClass="ai-assistant-dialog"
> >
<iframe <iframe
:src="aiAssistantUrl" :src="aiAssistantUrl"
@@ -208,6 +208,7 @@
> >
</iframe> </iframe>
</a-modal> </a-modal>
</div>
<!-- add by zhangweiwei 20250331_ai 智能创建助手 end --> <!-- add by zhangweiwei 20250331_ai 智能创建助手 end -->
<!-- 产品测试(口味和包装) --> <!-- 产品测试(口味和包装) -->
<CreateSurveyProduct <CreateSurveyProduct
@@ -279,7 +280,7 @@ const normalRef = ref([]);
const count = ref(5); const count = ref(5);
const tempVisible = ref(false); const tempVisible = ref(false);
/** add by zhangweiwei 20250331_ai 智能创建助手 start */ /** add by zhangweiwei 20250331_ai 智能创建助手 start */
const aiDialogVisible = ref(false); // AI iFrame 是否显示 const aiAssistantVisible = ref(false); // AI iFrame 是否显示
// const aiDrawerOpened = ref(false); // AI iFrame 是否打开 // const aiDrawerOpened = ref(false); // AI iFrame 是否打开
/** add by zhangweiwei 20250331_ai 智能创建助手 end */ /** add by zhangweiwei 20250331_ai 智能创建助手 end */
const visible = ref(false); const visible = ref(false);
@@ -469,7 +470,7 @@ function createCustom(itemId) {
if (itemId === 1) { if (itemId === 1) {
console.log('智能创建'); console.log('智能创建');
// 显示AI iFrame // 显示AI iFrame
aiDialogVisible.value = true; aiAssistantVisible.value = true;
} else if (itemId === 2) { } else if (itemId === 2) {
console.log('空白创建'); console.log('空白创建');
onCreate(); onCreate();
@@ -941,13 +942,20 @@ function professionalPrev() {
// } // }
// } // }
.ant-modal-root .ant-modal-mask .ant-modal-wrap .ant-modal { $ai-assistant-modal-top: 24px;
top: 24px !important;
width: 780px !important; .ai-assistant-wrapper {
:deep(.ant-modal) {
top: $ai-assistant-modal-top !important;
width: auto !important;
max-width: 780px !important;
.ant-modal-body { .ant-modal-body {
height: calc(100vh - 24px) !important; height: calc(100vh - $ai-assistant-modal-top * 2);
padding: 0 !important; padding: 0;
} }
}
} }
/** add by zhangweiwei 20250331_ai 智能创建助手 end */ /** add by zhangweiwei 20250331_ai 智能创建助手 end */
</style> </style>

View File

@@ -1,11 +1,11 @@
<!-- add by zhangweiwei 20250331_ai AI 质检 新增 质检结果组件 start --> <!-- add by zhangweiwei 20250331_ai AI 质检 新增 质检结果组件 start -->
<template> <template>
<div class="ai-inspection-result" > <div class="ai-inspection-result">
<i class="iconfont icon-yulan"></i> <i class="iconfont icon-yulan"></i>
<div class="result-content"> <div class="result-wrap">
<div class="result-item" v-for="(item, index) in result" :key="index"> <div class="result-item" v-for="(advice, index) in result" :key="index">
<span class="result-item__index">{{ index + 1 }}</span> <span class="result-item__index">{{ index + 1 }}</span>
<span class="result-item__text">{{ item.text }}</span> <span class="result-item__text">{{ advice }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -22,27 +22,30 @@ const props = defineProps({
.ai-inspection-result { .ai-inspection-result {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: first baseline;
padding-left: calc(16px + 20px); padding-left: calc(16px + 20px);
color: red; color: red;
font-size: 14px; font-size: 14px;
} }
.esult-content { .result-wrap {
margin-left: 6px; margin-left: 6px;
}
.result-item { .result-item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-top: 5px; margin-top: 5px;
align-items: first center;
&__index { &__index {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
white-space: nowrap; white-space: nowrap;
width: 18px; width: 15px;
min-width: 18px; min-width: 15px;
height: 18px; height: 15px;
border: 1px solid red; border: 1px solid red;
border-radius: 50%; border-radius: 50%;
font-size: 12px; font-size: 12px;
@@ -50,7 +53,6 @@ const props = defineProps({
&__text { &__text {
margin-left: 6px; margin-left: 6px;
} }
}
} }
</style> </style>
<!-- add by zhangweiwei 20250331_ai AI 质检 新增 AI 质检结果组件 end --> <!-- add by zhangweiwei 20250331_ai AI 质检 新增 AI 质检结果组件 end -->