feat-实现AI洞察功能
This commit is contained in:
@@ -60,3 +60,4 @@ See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
- keepAlive: 是否页面使用 keepAlive 缓存
|
||||
- shared: 是否页面为分享功能页面,不需要登录即可访问
|
||||
- showAiInspection: 问卷设计是否显示AI质检按钮
|
||||
- showAiInsight: 数据分析是否显示AI洞察按钮
|
||||
|
||||
17
src/App.vue
17
src/App.vue
@@ -26,12 +26,12 @@
|
||||
<script>
|
||||
import locale from 'ant-design-vue/es/locale/zh_CN';
|
||||
import 'moment/locale/zh-cn';
|
||||
import { watch, onMounted, ref } from 'vue';
|
||||
import { watch, onMounted,onUnmounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import Loading from '@/components/layout/loading/Loading.vue';
|
||||
import useEmitter from '@/composables/useEmitter';
|
||||
|
||||
import aiTaskManager from '@/utils/ai/ai-task-manager'; // 引入AI任务管理器
|
||||
export default {
|
||||
components: {
|
||||
Loading
|
||||
@@ -76,13 +76,12 @@ export default {
|
||||
// window.localStorage.plantId = data?.id
|
||||
// window.localStorage.plantUserInfo = JSON.stringify(data)
|
||||
// }
|
||||
// onMounted(()=>{
|
||||
// router.beforeEach((to, from) => {
|
||||
// if(to.path!= "/answer"){
|
||||
// getNewToken()
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
onMounted(() => {
|
||||
aiTaskManager.runTasks();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
aiTaskManager.saveTasks();
|
||||
});
|
||||
const clickAction = (e) => {
|
||||
if (e.target.getAttribute('data-linkType') === '1') {
|
||||
show.value = true;
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 质检 start -->
|
||||
|
||||
<div class="right" v-if="showAiInspection || showPreview || isRenderBtn || showShare">
|
||||
<template v-if="!showLoading">
|
||||
<!-- add by zhangweiwei 20250331_ai AI 质检 start -->
|
||||
<div v-show="showAiInspection" class="preview-btn" @click="toAiInspection">
|
||||
<img class="ai-inspection-btn" :src="require('@/assets/img/ai/ai-inspection-btn.png')" />
|
||||
<img class="ai-btn" :src="require('@/assets/img/ai/ai-inspection-btn.png')" />
|
||||
</div>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 质检 end -->
|
||||
<div v-show="showPreview" class="preview-btn" @click="toPreview">
|
||||
@@ -88,7 +89,12 @@
|
||||
</a-button>
|
||||
<!-- <Avatar :onlyUserShow="false" /> -->
|
||||
</div>
|
||||
<div class="right" v-if="!showPreview && !isRenderBtn && !showShare">
|
||||
<div class="right" v-if="(!showPreview && !isRenderBtn && !showShare) || showAiInsight">
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 start -->
|
||||
<div v-show="showAiInsight" class="preview-btn" @click="toAiInsight">
|
||||
<img class="ai-btn" :src="require('@/assets/img/ai/data-analyse-ai-insight-btn.png')" />
|
||||
</div>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 end -->
|
||||
<a-button
|
||||
class="publish-btn share-button"
|
||||
style="display: flex; align-items: center"
|
||||
@@ -138,10 +144,7 @@
|
||||
<!-- 下载中心 -->
|
||||
<DownloadCenter v-model:visible="downloadVisible" v-if="downloadVisible"></DownloadCenter>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 质检 start -->
|
||||
<ai-loading
|
||||
:visible="showAiLoading"
|
||||
desc="我正在为您检查问卷内容,大概需要20-30s,请稍等哦~"
|
||||
/>
|
||||
<ai-loading :visible="showAiLoading" :desc="aiLoadingDesc" />
|
||||
<!-- add by zhangweiwei 20250331_ai AI 质检 end -->
|
||||
</template>
|
||||
|
||||
@@ -160,6 +163,7 @@ import { useRouter, useRoute } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import useEmitter from '@/composables/useEmitter';
|
||||
import { publishSurvey, getSurveyInfo, aiQualityInspection } from '@/api/publish';
|
||||
import { aiInsight } from '@/api/data-analyse';
|
||||
import { useStore } from 'vuex';
|
||||
import {
|
||||
ExclamationCircleFilled,
|
||||
@@ -176,6 +180,8 @@ import { canPlanetPublish } from './utils';
|
||||
import { checkShowInsightTab } from '@/views/DataAnalyse/insight/consts';
|
||||
import { text } from 'cheerio/lib/static';
|
||||
import AiLoading from '@/components/layout/loading/AiLoading.vue';
|
||||
import aiTaskManager from '@/utils/ai/ai-task-manager'; // 引入AI任务管理器
|
||||
import { TASK_TYPE, Task } from '@/utils/ai/ai-task';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -244,14 +250,24 @@ const isRenderBtn = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
/** add by zhangweiwei AI 质检 start */
|
||||
/** add by zhangweiwei AI 质检/洞察 start */
|
||||
/**
|
||||
* 是否展示 AI 质检
|
||||
*/
|
||||
const showAiInspection = computed(() => {
|
||||
return route.matched.some((item) => item.meta?.showAiInspection);
|
||||
});
|
||||
/** add by zhangweiwei AI 质检 end */
|
||||
|
||||
const showAiInsight = computed(() => {
|
||||
return route.matched.some((item) => item.meta?.showAiInsight);
|
||||
});
|
||||
|
||||
const aiLoadingDesc = computed(() => {
|
||||
return showAiInspection.value
|
||||
? '我正在为您检查问卷内容,大概需要20-30s,请稍等哦~'
|
||||
: '正在激活AI显做镜,为您解析问卷背后的情感温度与数据密码,大约需要1-2min,请您先忙,一会记得刷新页面查看结果哦~';
|
||||
});
|
||||
/** add by zhangweiwei AI 质检/洞察 end */
|
||||
|
||||
const showPreview = computed(() => {
|
||||
return route.matched.some((item) => item.meta?.showPreview);
|
||||
@@ -362,6 +378,40 @@ const toAiInspection = () => {
|
||||
};
|
||||
/** add by zhangweiwei 20250331_ai AI 质检 end */
|
||||
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 start */
|
||||
/**
|
||||
* AI 洞察
|
||||
*/
|
||||
const toAiInsight = () => {
|
||||
const task = aiTaskManager.findTask(TASK_TYPE.INSIGHT, sn);
|
||||
if (task) {
|
||||
// 已经在AI洞察中
|
||||
message.warn(
|
||||
'正在为您进行AI洞察,这次需要多分析一会儿才能确保洞察精准度,认真工作的我马上带着结果回来哦~'
|
||||
);
|
||||
return;
|
||||
}
|
||||
aiInsight(route.query.sn, { sn })
|
||||
.then(() => {
|
||||
showAiLoading.value = true;
|
||||
// 10s后关闭
|
||||
setTimeout(() => {
|
||||
// 开启轮询模式
|
||||
aiTaskManager.addTask(new Task(TASK_TYPE.INSIGHT, sn));
|
||||
showAiLoading.value = false;
|
||||
}, 10000);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.data?.code === 10015) {
|
||||
// 正在AI洞察中
|
||||
message.warn(error.data?.message);
|
||||
} else {
|
||||
message.error(error.data?.message || '服务器错误');
|
||||
}
|
||||
});
|
||||
};
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 end */
|
||||
|
||||
const toPreview = async () => {
|
||||
var res = await canPlanetPublish(route.query.sn, 1);
|
||||
if (res) {
|
||||
@@ -614,7 +664,7 @@ onUnmounted(() => store.dispatch('polling/stopPollingSurveyState', { sn }));
|
||||
.preview-btn:hover {
|
||||
color: #70b936;
|
||||
}
|
||||
.ai-inspection-btn {
|
||||
.ai-btn {
|
||||
width: 82px;
|
||||
}
|
||||
.publish-btn {
|
||||
|
||||
@@ -208,3 +208,27 @@ export function cancelAiMark(sn, data) {
|
||||
});
|
||||
}
|
||||
/** add by zhangweiwei 20250331_ai AI 样本标记 end */
|
||||
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 start */
|
||||
/**
|
||||
* 数据分析:AI 洞察
|
||||
* @param {String} sn 问卷编号
|
||||
* @param {Object} data
|
||||
*/
|
||||
export function aiInsight(sn, data) {
|
||||
return request({
|
||||
url: `/console/surveys/${sn}/analysis_insights`,
|
||||
method: 'post',
|
||||
toastError: false,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function queryAiStatus(sn, data) {
|
||||
return request({
|
||||
url: `/console/surveys/${sn}/status`,
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 end */
|
||||
|
||||
BIN
src/assets/img/ai/ai-loading-transparent.png
Normal file
BIN
src/assets/img/ai/ai-loading-transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
BIN
src/assets/img/ai/data-analyse-ai-insight-btn.png
Normal file
BIN
src/assets/img/ai/data-analyse-ai-insight-btn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
BIN
src/assets/img/ai/data-analyse-ai-insight-icon.png
Normal file
BIN
src/assets/img/ai/data-analyse-ai-insight-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/img/ai/data-analyse-ai-insight-result.png
Normal file
BIN
src/assets/img/ai/data-analyse-ai-insight-result.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -260,8 +260,9 @@ const constantRoutes = [
|
||||
{
|
||||
path: 'diagram',
|
||||
name: 'Diagram',
|
||||
meta: { keepAlive: true, showDownload: true },
|
||||
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 新增 showAiInsight 属性 start */
|
||||
meta: { keepAlive: true, showDownload: true, showAiInsight: true },
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 新增 showAiInsight 属性 start */
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "analyse" */ '@/views/DataAnalyse/diagram/test')
|
||||
},
|
||||
|
||||
@@ -18,3 +18,34 @@
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.ant-notification-notice {
|
||||
padding: 6px 10px 10px !important;
|
||||
border-radius: 8px !important;
|
||||
background-color: #f9fff5 !important;
|
||||
|
||||
&-icon {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
&-close{
|
||||
top: 10px !important;
|
||||
}
|
||||
|
||||
&-message {
|
||||
margin-left: 22px !important;
|
||||
font-size: 12px !important;
|
||||
color: #000000 !important;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
||||
&-with-icon &-description {
|
||||
margin-top: 8px;
|
||||
margin-left: 0 !important;
|
||||
font-size: 12px !important;
|
||||
color: #6d6d6d !important;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
45
src/utils/ai/ai-task-manager.js
Normal file
45
src/utils/ai/ai-task-manager.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ref } from 'vue';
|
||||
export class TaskManager {
|
||||
constructor() {
|
||||
this.tasks = ref(this.loadTasks());
|
||||
}
|
||||
|
||||
loadTasks() {
|
||||
const storedTasks = localStorage.getItem('ai-tasks');
|
||||
return storedTasks ? JSON.parse(storedTasks) : [];
|
||||
}
|
||||
|
||||
saveTasks() {
|
||||
localStorage.setItem('tasks', JSON.stringify(this.tasks.value));
|
||||
}
|
||||
|
||||
addTask(task) {
|
||||
this.tasks.value.push(task);
|
||||
this.saveTasks();
|
||||
task.run();
|
||||
}
|
||||
|
||||
removeTask(taskId) {
|
||||
this.tasks.value = this.tasks.value.filter((task) => task.id !== taskId);
|
||||
this.saveTasks();
|
||||
}
|
||||
|
||||
findTask(aiType,sn) {
|
||||
return this.tasks.value.find((task) => task.id === `${aiType}-${sn}`);
|
||||
}
|
||||
|
||||
runTasks() {
|
||||
if (this.tasks.value.length === 0) {
|
||||
console.log('No ai tasks to run');
|
||||
return;
|
||||
}
|
||||
this.tasks.value.forEach((task) => {
|
||||
if (task.isRunning) {
|
||||
task.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const taskManager = new TaskManager();
|
||||
export default taskManager;
|
||||
89
src/utils/ai/ai-task.js
Normal file
89
src/utils/ai/ai-task.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { queryAiStatus } from '@/api/data-analyse'; // 引入查询AI状态的API函数
|
||||
import taskManager from './ai-task-manager'; // 引入任务管理器
|
||||
import { notification } from 'ant-design-vue';
|
||||
|
||||
import InfoIcon from '@/assets/img/ai/data-analyse-ai-insight-icon.png'; // 导入你的自定义图标
|
||||
|
||||
export const TASK_TYPE = {
|
||||
MARK: 'mark',
|
||||
INSIGHT: 'insight'
|
||||
};
|
||||
|
||||
const TYPE_CODE = {
|
||||
[TASK_TYPE.MARK]: 1,
|
||||
[TASK_TYPE.INSIGHT]: 2
|
||||
};
|
||||
|
||||
const TASK_SUCCESS_NOTICE = {
|
||||
[TASK_TYPE.MARK]: 'AI小助手已经完成了数据洞察工作,请前往数据分析-基础分析页面进行查看吧',
|
||||
[TASK_TYPE.INSIGHT]: 'AI小助手已经完成了无效样本标记工作,请前往数据分析-明细数据页面进行查看吧'
|
||||
};
|
||||
|
||||
export class Task {
|
||||
constructor(aiType, sn) {
|
||||
this.id = `${aiType}-${sn}`;
|
||||
this.sn = sn;
|
||||
this.aiType = aiType;
|
||||
this.interval = 15000;
|
||||
this.maxRetries = 3;
|
||||
this.status = null;
|
||||
this.isPolling = true;
|
||||
this.retryCount = 0;
|
||||
this.timer = null;
|
||||
}
|
||||
run() {
|
||||
const poll = async () => {
|
||||
if (this.status === 1 || this.status === 2) {
|
||||
this.stopPolling();
|
||||
}
|
||||
if (!this.isPolling) return;
|
||||
try {
|
||||
// type 1=AI样本标记;2=AI洞察
|
||||
const response = await queryAiStatus(this.sn, {
|
||||
surveySn: this.sn,
|
||||
type: TYPE_CODE[this.aiType]
|
||||
});
|
||||
console.log('response--->', response);
|
||||
// 标记状态:0=标记中,1=标记完成,2=标记失败
|
||||
this.status = response.data?.status;
|
||||
// 如果状态为成功,停止轮询
|
||||
if (this.status === 1 || this.status === 2) {
|
||||
this.stopPolling();
|
||||
taskManager.removeTask(this.id); // 从任务管理器中移除任务
|
||||
this.showNotice(); // 显示通知
|
||||
} else {
|
||||
this.retryCount = 0; // 重置重试计数
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Polling error:', error);
|
||||
// 当发生错误时,增加重试计数
|
||||
this.retryCount++;
|
||||
// 如果超过最大重试次数,停止轮询
|
||||
if (this.retryCount >= this.maxRetries) {
|
||||
console.log('已达到最大重试次数,Stopping polling.');
|
||||
this.stopPolling();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
poll();
|
||||
this.timer = setInterval(() => {
|
||||
poll(); // 定时执行任务
|
||||
}, this.interval);
|
||||
}
|
||||
|
||||
stopPolling() {
|
||||
this.isPolling = false;
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
showNotice() {
|
||||
notification.info({
|
||||
message: '提示',
|
||||
duration: null,
|
||||
key: this.aiType,
|
||||
icon: <img src={InfoIcon} alt="" style="width:12px; height: 12px " />,
|
||||
description: TASK_SUCCESS_NOTICE[this.aiType]
|
||||
});
|
||||
}
|
||||
}
|
||||
46
src/views/DataAnalyse/diagram/components/AiInsightResult.vue
Normal file
46
src/views/DataAnalyse/diagram/components/AiInsightResult.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 start -->
|
||||
<template>
|
||||
<div class="ai-insight-item">
|
||||
<div>
|
||||
<img
|
||||
class="ai-insight-item_icon"
|
||||
:src="require('@/assets/img/ai/data-analyse-ai-insight-icon.png')"
|
||||
/>
|
||||
</div>
|
||||
<div class="ai-insight-item_result" v-html="result"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
result: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.ai-insight-item {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 15px;
|
||||
padding: 4px 9px;
|
||||
background: linear-gradient(124deg, #fdfaff 0%, #fbf9ff 1%, #f8f9ff 100%);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&_icon {
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
&_result {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 end -->
|
||||
@@ -0,0 +1,81 @@
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 start -->
|
||||
<template>
|
||||
<div class="ai-insight" :class="{ 'loading-height': loading }">
|
||||
<img
|
||||
class="ai-insight_icon"
|
||||
:src="require('@/assets/img/ai/data-analyse-ai-insight-result.png')"
|
||||
/>
|
||||
<div v-if="loading" class="ai-insight_loading">
|
||||
<img
|
||||
class="ai-insight_loading--img"
|
||||
:src="require('@/assets/img/ai/ai-loading-transparent.png')"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<span class="ai-insight_loading--text">AI洞察加速中...</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="ai-insight_result" v-html="result"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
result: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.ai-insight {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 32px 10px;
|
||||
background: linear-gradient(124deg, #fdfaff 0%, #fbf9ff 1%, #f8f9ff 100%);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&_icon {
|
||||
width: 38px;
|
||||
margin-left: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&_loading {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
top: 36px;
|
||||
bottom: 36px;
|
||||
|
||||
&--img {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
&--text {
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
&_result {
|
||||
padding: 15px 28px;
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
.loading-height {
|
||||
height: 150px;
|
||||
}
|
||||
</style>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 end -->
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="table-container">
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<a-table
|
||||
:align="'center'"
|
||||
:columns="columns"
|
||||
@@ -37,6 +41,7 @@ import { defineComponent, ref, watch, inject, computed } from 'vue';
|
||||
import { getFileExtension } from '@/utils/file';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { downloadFile } from '@/views/DataAnalyse/composables/downloadFile';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
import { useStore } from 'vuex';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { downloadAllFile, downloadFileSheet, getDiagramAnalysis } from '@/api/data-analyse';
|
||||
@@ -57,7 +62,7 @@ export default defineComponent({
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
components: { ImagePreview },
|
||||
components: { ImagePreview, AiInsightResult },
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="table-container">
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<a-table
|
||||
:align="'center'"
|
||||
:columns="columns"
|
||||
@@ -21,6 +25,7 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch, inject } from 'vue';
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
import { getDiagramAnalysis } from '@/api/data-analyse';
|
||||
import { convertQueryToString } from '@/utils/httpFormat';
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
/>
|
||||
<div class="chart-container">
|
||||
<ChartAction @update:action="handleUpdateAction" :chartTypeOptions="chartTypeOptions" />
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<div class="chart-wrapper">
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
</div>
|
||||
@@ -39,6 +43,7 @@ import pieChart from '@/components/chart/PieChart';
|
||||
import ChartAction from '@/views/DataAnalyse/diagram/components/ChartAction';
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import useMultiChartOption from '@/views/DataAnalyse/diagram/composables/useMultiChartOption';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
import { generateTableCustomRender } from './renderTableGroup';
|
||||
|
||||
function clearData(data) {
|
||||
@@ -68,7 +73,8 @@ export default defineComponent({
|
||||
},
|
||||
components: {
|
||||
RenderTableTitle,
|
||||
ChartAction
|
||||
ChartAction,
|
||||
AiInsightResult
|
||||
},
|
||||
setup(props) {
|
||||
const chartInstance = inject('chartInstance');
|
||||
@@ -379,7 +385,8 @@ export default defineComponent({
|
||||
height: 100%;
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
min-height: 300px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<div class="container">
|
||||
<div class="chart-container">
|
||||
<ChartAction @update:action="handleUpdateAction" :chartTypeOptions="chartTypeOptions" />
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<div class="chart-wrapper">
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
</div>
|
||||
@@ -33,6 +37,7 @@ import ChartAction from '@/views/DataAnalyse/diagram/components/ChartAction';
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import RenderTableTitleLo from '@/views/DataAnalyse/components/RenderTableTitleLo';
|
||||
import useChartOption from '@/views/DataAnalyse/diagram/composables/useChartOption';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { generateTableCustomRender } from './renderTableGroup';
|
||||
|
||||
@@ -77,7 +82,8 @@ export default defineComponent({
|
||||
components: {
|
||||
RenderTableTitle,
|
||||
ChartAction,
|
||||
pieChart
|
||||
pieChart,
|
||||
AiInsightResult
|
||||
},
|
||||
setup(props) {
|
||||
const chartInstance = inject('chartInstance');
|
||||
@@ -211,7 +217,8 @@ export default defineComponent({
|
||||
.container {
|
||||
height: 100%;
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
min-height: 300px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
|
||||
@@ -31,6 +31,7 @@ import Header from '@/views/DataAnalyse/diagram/components/Layouts/Header';
|
||||
import Main from '@/views/DataAnalyse/diagram/components/Layouts/Main';
|
||||
import Footer from '@/views/DataAnalyse/diagram/components/Layouts/Footer';
|
||||
import useQuestionInfo from '@/views/DataAnalyse/diagram/composables/useQuestionInfo';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
import spliceChart from '../../components/spliceChart';
|
||||
import generatorTable from './generatorTable';
|
||||
import { exportChart } from '@/api/data-analyse';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="container">
|
||||
<div class="chart-container">
|
||||
<ChartAction @update:action="handleUpdateAction" :chartTypeOptions="chartTypeOptions" />
|
||||
<AiInsightResult v-if="data.question_conclusion" :result="data.question_conclusion" />
|
||||
<div class="chart-wrapper">
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
</div>
|
||||
@@ -31,6 +32,7 @@ import pieChart from '@/components/chart/PieChart';
|
||||
import ChartAction from '@/views/DataAnalyse/diagram/components/ChartAction';
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import useConstantOption from '@/views/DataAnalyse/diagram/composables/useConstantOption';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
|
||||
function clearData(data) {
|
||||
return data.filter((item) => item.avg !== '0.00');
|
||||
@@ -49,7 +51,8 @@ export default defineComponent({
|
||||
},
|
||||
components: {
|
||||
RenderTableTitle,
|
||||
ChartAction
|
||||
ChartAction,
|
||||
AiInsightResult,
|
||||
// pieChart,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
@@ -168,7 +171,8 @@ export default defineComponent({
|
||||
.container {
|
||||
height: 100%;
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
min-height: 300px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<AiInsightResult v-if="data.question_conclusion" :result="data.question_conclusion" />
|
||||
<div id="location-map" class="location-map" ref="locationMap"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, watchEffect, computed } from 'vue';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -13,6 +15,9 @@ export default defineComponent({
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AiInsightResult
|
||||
},
|
||||
setup(props) {
|
||||
let count = 0;
|
||||
const map = ref(null);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<div style="height: 32px">
|
||||
<CascaderSelect :data="data.optionAll" :max="3" @onOptionIndexChange="getData"></CascaderSelect>
|
||||
</div>
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
<div class="table-container">
|
||||
<a-table
|
||||
@@ -32,6 +36,7 @@ import { useRoute } from 'vue-router';
|
||||
import useChartOption from '@/views/DataAnalyse/diagram/composables/useChartOption';
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import RenderTableTitleLo from '@/views/DataAnalyse/components/RenderTableTitleLo';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
|
||||
function clearData(data) {
|
||||
return data.filter((item) => item.number !== 0);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<div style="height: 32px">
|
||||
<CascaderSelect :data="university" :max="2" @onOptionIndexChange="getData"></CascaderSelect>
|
||||
</div>
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
<div class="table-container">
|
||||
<a-table
|
||||
@@ -33,6 +37,7 @@ import useChartOption from '@/views/DataAnalyse/diagram/composables/useChartOpti
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import RenderTableTitleLo from '@/views/DataAnalyse/components/RenderTableTitleLo';
|
||||
import university from '@/views/Answer/questions/json/university.json';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
|
||||
function clearData(data) {
|
||||
return data.filter((item) => item.number !== 0);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<div style="height: 32px">
|
||||
<CascaderSelect :data="major" :max="1" @onOptionIndexChange="getData"></CascaderSelect>
|
||||
</div>
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
<div class="table-container">
|
||||
<a-table
|
||||
@@ -33,6 +37,7 @@ import useChartOption from '@/views/DataAnalyse/diagram/composables/useChartOpti
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import RenderTableTitleLo from '@/views/DataAnalyse/components/RenderTableTitleLo';
|
||||
import major from '@/views/Answer/questions/json/major.json';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
|
||||
function clearData(data) {
|
||||
return data.filter((item) => item.number !== 0);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<div style="height: 32px">
|
||||
<CascaderSelect :data="cityes" :max="1" @onOptionIndexChange="getData"></CascaderSelect>
|
||||
</div>
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
<div class="table-container">
|
||||
<a-table
|
||||
@@ -33,6 +37,7 @@ import useChartOption from '@/views/DataAnalyse/diagram/composables/useChartOpti
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import RenderTableTitleLo from '@/views/DataAnalyse/components/RenderTableTitleLo';
|
||||
import cityes from '@/views/Answer/questions/json/cityes.json';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
|
||||
function clearData(data) {
|
||||
return data.filter((item) => item.number !== 0);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<div style="height: 32px">
|
||||
<CascaderSelect :data="cityes" :max="2" @onOptionIndexChange="getData"></CascaderSelect>
|
||||
</div>
|
||||
<AiInsightResult
|
||||
v-if="data.question_conclusion"
|
||||
:result="data.question_conclusion"
|
||||
/>
|
||||
<pieChart :chart-option="option" ref="chart" />
|
||||
<div class="table-container">
|
||||
<a-table
|
||||
@@ -33,6 +37,7 @@ import useChartOption from '@/views/DataAnalyse/diagram/composables/useChartOpti
|
||||
import RenderTableTitle from '@/views/DataAnalyse/components/RenderTableTitle';
|
||||
import RenderTableTitleLo from '@/views/DataAnalyse/components/RenderTableTitleLo';
|
||||
import cityes from '@/views/Answer/questions/json/cityes.json';
|
||||
import AiInsightResult from '@/views/DataAnalyse/diagram/components/AiInsightResult';
|
||||
|
||||
function clearData(data) {
|
||||
return data.filter((item) => item.number !== 0);
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
></newSearch>
|
||||
</div>
|
||||
<div v-if="diagramList.length > 0">
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 start -->
|
||||
<ai-insight-result-overall
|
||||
v-if="aiInsightResult || (typeof aiInsightLoading !== 'undefined')"
|
||||
:result="aiInsightResult"
|
||||
:loading="aiInsightLoading"
|
||||
/>
|
||||
<!-- add by zhangweiwei 20250331_ai AI 洞察 end -->
|
||||
<div class="diagram-container">
|
||||
<BasicDiagramItem
|
||||
@remove-chart="onRemove"
|
||||
@@ -75,21 +82,25 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import 'element-plus/theme-chalk/el-loading.css';
|
||||
import { onMounted, ref, computed, provide, nextTick } from 'vue';
|
||||
import { onMounted, ref, computed, provide, nextTick, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import BasicDiagramItem from '@/views/DataAnalyse/diagram/components/diagram-item-new.vue';
|
||||
import SubTitle from '../components/SubTitle.vue';
|
||||
import search from '../components/diagram/search.vue';
|
||||
import AiInsightResultOverall from '@views/DataAnalyse/diagram/components/AiInsightResultOverall.vue';
|
||||
import FullScreenModal from '@views/DataAnalyse/diagram/components/FullScreenModal';
|
||||
import newSearch from '@/views/DataAnalyse/components/diagram/newSearch';
|
||||
|
||||
import { getDiagramAnalysis } from '@/api/data-analyse';
|
||||
import { aiInsight, getDiagramAnalysis } from '@/api/data-analyse';
|
||||
import { getAuthRoles } from '@/api/role';
|
||||
import { convertQueryToString } from '@/utils/httpFormat';
|
||||
import { useStore } from 'vuex';
|
||||
import useEmitter from '@/composables/useEmitter';
|
||||
import { chartList } from '@/views/DataAnalyse/diagram/composables/useInstance';
|
||||
import newModal from '@/views/DataAnalyse/components/diagram/newModal';
|
||||
import taskManager from '@/utils/ai/ai-task-manager';
|
||||
import { TASK_TYPE } from '@/utils/ai/ai-task';
|
||||
|
||||
const store = useStore();
|
||||
const emitter = useEmitter();
|
||||
|
||||
@@ -107,6 +118,12 @@ provide('exportList', exportList);
|
||||
|
||||
const diagramItem = ref(null);
|
||||
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 start */
|
||||
const aiInsightResult = ref('');
|
||||
const aiInsightLoading = ref(undefined);
|
||||
// const tasks = taskManager.tasks.value;
|
||||
/** add by zhangweiwei 20250331_ai AI 洞察 end */
|
||||
|
||||
function getPermissionInfo() {
|
||||
getAuthRoles({
|
||||
flag: 2,
|
||||
@@ -167,6 +184,7 @@ function getDiagramData() {
|
||||
total.value = _total;
|
||||
per_page.value = res.meta.per_page;
|
||||
dataCount.value = res.other.answerCount;
|
||||
aiInsightResult.value = res.other.overall_conclusion;
|
||||
hasElementAfterRemove.value = _total === 0 ? true : false;
|
||||
|
||||
let response_data = res.data
|
||||
@@ -347,6 +365,18 @@ onMounted(async () => {
|
||||
spinning.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => taskManager.tasks.value,
|
||||
(newTasks) => {
|
||||
console.log('任务列表发生变化:', newTasks);
|
||||
aiInsightLoading.value = taskManager.findTask(TASK_TYPE.INSIGHT, sn)?.isPolling;
|
||||
if (aiInsightLoading.value) {
|
||||
aiInsightResult.value = '';
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -648,17 +648,14 @@ function aiMark(sn) {
|
||||
aiSampleMark(sn, {
|
||||
surveySn: sn
|
||||
})
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
showAiLoading.value = true;
|
||||
// 5s后关闭
|
||||
// 10s后关闭
|
||||
setTimeout(() => {
|
||||
showAiLoading.value = false;
|
||||
// getData();
|
||||
// resetSelection();
|
||||
}, 10000);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('----->', error);
|
||||
if (error.data?.code === 10014) {
|
||||
// 正在AI标记中
|
||||
message.warn(error.data?.message);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<!-- add by zhangweiwei 20250331_ai AI 质检 新增 质检结果组件 start -->
|
||||
<template>
|
||||
<div class="ai-inspection-result">
|
||||
<img :src="require('@/assets/img/ai/ai-inspection-result-icon.png')" class="result-icon" />
|
||||
|
||||
<div>
|
||||
<img class="result-icon" :src="require('@/assets/img/ai/ai-inspection-result-icon.png')" />
|
||||
</div>
|
||||
<div class="result-wrap">
|
||||
<div class="result-item" v-for="(advice, index) in result" :key="index">
|
||||
<span class="result-item__index" v-if="result.length > 1">{{ index + 1 }}</span>
|
||||
@@ -35,7 +36,6 @@ const props = defineProps({
|
||||
.result-icon {
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.result-wrap {
|
||||
|
||||
Reference in New Issue
Block a user