Compare commits

...

2 Commits

Author SHA1 Message Date
9b8e655f3d Merge remote-tracking branch 'origin/master' 2025-12-29 10:28:44 +08:00
45f5bcf764 问卷调整 2025-12-29 10:28:31 +08:00
4 changed files with 1442 additions and 123 deletions

View File

@ -74,3 +74,17 @@ export const questionnaireAnalyzeWithAIApi = async (params: any) => {
return await post("/api/questionnaireAnswer/analyzeWithAI", params);
};
/**
* AI分析结果
*/
export const questionnaireAnalysisSaveApi = async (params: any) => {
return await post("/api/questionnaireAnalysis/save", params);
};
/**
* AI分析结果
*/
export const questionnaireAnalysisFindPageApi = async (params: any) => {
return await get("/api/questionnaireAnalysis/findPage", params);
};

View File

@ -157,6 +157,13 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/wj/wjEvaluate",
"style": {
"navigationBarTitleText": "AI分析",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xk/index",
"style": {

View File

@ -284,28 +284,17 @@
<text class="success-time">{{ formatTime(new Date()) }}</text>
<text class="success-tip">您已成功提交问卷</text>
<!-- 添加AI分析按钮 -->
<button v-if="!analysisResult && !analyzing" @click="analyzeWithAI" class="ai-analyze-btn">
<!-- 查看AI分析报告按钮 -->
<button v-if="showAIAnalysisButton" @click="viewAIAnalysisReport" class="ai-analyze-btn">
<u-icon name="file-text" size="18" color="#fff" style="margin-right: 8rpx;"></u-icon>
AI智能分析
查看AI分析报告
</button>
<!-- 分析中状态 -->
<view v-if="analyzing" class="analyzing-container">
<u-loading-icon mode="spinner" size="24" color="#409EFF"></u-loading-icon>
<text class="analyzing-text">AI分析中请稍候...</text>
</view>
<!-- 分析结果 -->
<view v-if="analysisResult" class="analysis-result">
<view class="analysis-header">
<u-icon name="file-text" size="20" color="#409EFF"></u-icon>
<text class="analysis-title">AI分析报告</text>
</view>
<view class="analysis-content">
<text class="analysis-text">{{ analysisResult }}</text>
</view>
</view>
<!-- 重新填写按钮当允许重复提交时显示 -->
<button v-if="allowResubmit" @click="handleResubmit" class="resubmit-btn">
<u-icon name="reload" size="18" color="#fff" style="margin-right: 8rpx;"></u-icon>
重新填写
</button>
<button @click="goBack" class="back-btn">返回</button>
</view>
@ -327,6 +316,19 @@
<u-icon name="info-circle" size="80" color="#409EFF"></u-icon>
<text class="already-filled-title">已填写</text>
<text class="already-filled-subtitle">您已经填写过此问卷</text>
<!-- 查看AI分析报告按钮 -->
<button v-if="showAIAnalysisButton" @click="viewAIAnalysisReport" class="ai-analyze-btn">
<u-icon name="file-text" size="18" color="#fff" style="margin-right: 8rpx;"></u-icon>
查看AI分析报告
</button>
<!-- 重新填写按钮当允许重复提交时显示 -->
<button v-if="allowResubmit" @click="handleResubmit" class="resubmit-btn">
<u-icon name="reload" size="18" color="#fff" style="margin-right: 8rpx;"></u-icon>
重新填写
</button>
<button @click="goBack" class="back-btn">返回</button>
</view>
</view>
@ -352,7 +354,7 @@ import {
questionnaireQuestionFindPageApi,
questionnaireAnswerSaveApi,
questionnaireAnswerFindByUserApi,
questionnaireAnalyzeWithAIApi
questionnaireAnswerFindDetailApi
} from '@/api/base/wjApi';
import { useUserStore } from '@/store/modules/user';
import { attachmentUpload } from '@/api/system/upload';
@ -394,10 +396,6 @@ const fileUploadData = reactive<Record<string, {
//
const compressConfig = ref(COMPRESS_PRESETS.high);
// AI
const analyzing = ref(false);
const analysisResult = ref('');
//
const signCompRef = ref<any>(null);
const signTitle = ref<string>("问卷签名");
@ -405,6 +403,14 @@ const sign_file = ref<string>("");
const showSignature = ref<boolean>(false);
const currentSignatureQuestionId = ref<string>(""); // ID
// IDAI
const submitId = ref<string>('');
// AI
const showAIAnalysisButton = ref(false);
//
const allowResubmit = ref(false);
onLoad(async (params) => {
options.value = params;
openId.value = params?.openId || '';
@ -445,6 +451,17 @@ const initializePage = async () => {
const questionnaireResult = await questionnaireFindByIdApi({ id: questionnaireId.value });
if (questionnaireResult && questionnaireResult.resultCode === 1) {
questionnaireInfo.value = questionnaireResult.result || questionnaireResult.data;
console.log('【问卷信息】questionnaireInfo.value:', questionnaireInfo.value);
console.log('【allowAnalysis值】', questionnaireInfo.value?.allowAnalysis);
// AI allowAnalysis = 1
const allowAnalysisValue = questionnaireInfo.value?.allowAnalysis;
showAIAnalysisButton.value = allowAnalysisValue === 1 || allowAnalysisValue === '1';
console.log('【是否显示AI按钮】showAIAnalysisButton.value:', showAIAnalysisButton.value);
// allowResubmit = 1
const allowResubmitValue = questionnaireInfo.value?.allowResubmit;
allowResubmit.value = allowResubmitValue === 1 || allowResubmitValue === '1';
console.log('【是否允许重复提交】allowResubmit.value:', allowResubmit.value);
} else {
throw new Error('获取问卷信息失败');
}
@ -481,8 +498,8 @@ const initializePage = async () => {
throw new Error('检查权限失败');
}
// 4.
if (questionnaireInfo.value.allowResubmit !== 1) {
// 4.
if (!allowResubmit.value) {
const answerResult = await questionnaireAnswerFindByUserApi({
questionnaireId: questionnaireId.value
});
@ -577,6 +594,11 @@ const initializePage = async () => {
};
}
});
// 6.
if (allowResubmit.value) {
await loadExistingAnswers(allQuestions);
}
}
currentStep.value = 'fill';
@ -1074,6 +1096,11 @@ const doSubmitQuestionnaire = async () => {
const result = await questionnaireAnswerSaveApi(submitParams);
if (result && result.resultCode === 1) {
// submitId
const resultData = result.result || result.data || {};
if (resultData.submitId) {
submitId.value = resultData.submitId;
}
currentStep.value = 'success';
} else {
throw new Error(result?.message || '提交失败');
@ -1102,70 +1129,117 @@ const formatTime = (time: string | Date) => {
});
};
// AI
const analyzeWithAI = async () => {
try {
analyzing.value = true;
analysisResult.value = '';
// AI
const viewAIAnalysisReport = () => {
// ID
const currentUserId = userStore.userdata?.id || userStore.userdata?.userId || '';
// AI
uni.navigateTo({
url: `/pages/base/wj/wjEvaluate?questionnaireId=${questionnaireId.value}&userId=${currentUserId}`
});
};
// AI
const allQuestions: any[] = [];
pageList.value.forEach(page => {
allQuestions.push(...page.questions);
//
const loadExistingAnswers = async (allQuestions: any[]) => {
try {
// ID
const currentUserId = userStore.userdata?.id || userStore.userdata?.userId || '';
if (!currentUserId) {
console.log('未找到用户ID跳过加载已填写答案');
return;
}
//
const answerResult = await questionnaireAnswerFindDetailApi({
questionnaireId: questionnaireId.value,
userId: currentUserId
});
const questionnaireData = {
questionnaireId: questionnaireId.value,
title: questionnaireInfo.value?.title,
description: questionnaireInfo.value?.description,
questions: allQuestions.map((q: any) => {
let answerValue = answers[q.id]?.value;
if (answerResult && answerResult.resultCode === 1) {
const answerList = answerResult.result || answerResult.data || [];
if (answerList && answerList.length > 0) {
console.log('找到已填写的答案,开始回显:', answerList);
//
if (q.questionType === 'CHECKBOX' && Array.isArray(answerValue)) {
answerValue = answerValue.join(',');
} else if (q.questionType === 'FILE') {
const uploadData = fileUploadData[q.id];
const imageUrls = (uploadData?.imageList || []).filter(img => img.url).map(img => img.url);
const fileUrls = (uploadData?.fileList || []).filter(file => file.url).map(file => file.url);
answerValue = [...imageUrls, ...fileUrls].join(',');
} else if (q.questionType === 'VIDEO') {
const uploadData = fileUploadData[q.id];
const videoUrls = (uploadData?.videoList || []).filter(video => video.url).map(video => video.url);
answerValue = videoUrls.join(',');
} else if (q.questionType === 'SIGNATURE') {
answerValue = answerValue ? '[已签名]' : '[未签名]';
// ID
const answerMap = new Map<string, any>();
answerList.forEach((answer: any) => {
answerMap.set(answer.questionId, answer);
});
//
allQuestions.forEach((question: any) => {
const existingAnswer = answerMap.get(question.id);
if (existingAnswer && existingAnswer.answerContent) {
const answerContent = existingAnswer.answerContent;
if (question.questionType === 'CHECKBOX') {
//
if (answerContent) {
answers[question.id].value = answerContent.split(',').map((v: string) => v.trim()).filter((v: string) => v);
}
} else if (question.questionType === 'FILE') {
// URL
if (answerContent) {
const urls = answerContent.split(',').map((v: string) => v.trim()).filter((v: string) => v);
const imageUrls: string[] = [];
const fileUrls: string[] = [];
//
const pageName = q.pageName || '第一部分';
const pageSortOrder = q.pageSortOrder !== undefined ? q.pageSortOrder : 0;
return {
content: q.questionContent,
type: q.questionType,
answer: answerValue || '[未填写]',
pageName: pageName,
pageSortOrder: pageSortOrder
};
})
};
// AI
const response = await questionnaireAnalyzeWithAIApi(questionnaireData);
if (response && response.resultCode === 1) {
analysisResult.value = response.result || response.data || '分析完成';
urls.forEach((url: string) => {
const lowerUrl = url.toLowerCase();
//
if (lowerUrl.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/i)) {
imageUrls.push(url);
} else {
throw new Error(response?.message || 'AI分析失败');
fileUrls.push(url);
}
});
//
if (fileUploadData[question.id]) {
fileUploadData[question.id].imageList = imageUrls.map(url => ({ url, name: url.split('/').pop() || '' }));
fileUploadData[question.id].fileList = fileUrls.map(url => ({ url, name: url.split('/').pop() || '' }));
}
}
} else if (question.questionType === 'VIDEO') {
// URL
if (answerContent) {
const urls = answerContent.split(',').map((v: string) => v.trim()).filter((v: string) => v);
if (fileUploadData[question.id]) {
fileUploadData[question.id].videoList = urls.map(url => ({ url, name: url.split('/').pop() || '' }));
}
}
} else if (question.questionType === 'SIGNATURE') {
// 使base64
answers[question.id].value = answerContent;
} else {
// TEXT, TEXTAREA, NUMBER, DATE, RADIO, SELECT
answers[question.id].value = answerContent;
}
}
});
//
//
//
console.log('答案回显完成');
} else {
console.log('未找到已填写的答案');
}
}
} catch (error: any) {
console.error('AI分析失败:', error);
uni.showToast({ title: error.message || 'AI分析失败请稍后重试', icon: 'none', duration: 2000 });
} finally {
analyzing.value = false;
console.error('加载已填写答案失败:', error);
//
}
};
//
const handleResubmit = async () => {
//
currentStep.value = 'fill';
//
await initializePage();
};
</script>
<style lang="scss" scoped>
@ -1695,54 +1769,25 @@ const analyzeWithAI = async () => {
}
}
.analyzing-container {
.resubmit-btn {
width: 320rpx;
height: 80rpx;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
font-size: 30rpx;
font-weight: 600;
border: none;
border-radius: 40rpx;
margin-bottom: 30rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
padding: 30rpx;
background: #f5f7fa;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(245, 87, 108, 0.4);
transition: all 0.3s;
.analyzing-text {
margin-left: 20rpx;
font-size: 28rpx;
color: #409EFF;
}
}
.analysis-result {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
text-align: left;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
max-width: 100%;
.analysis-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #eee;
.analysis-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-left: 12rpx;
}
}
.analysis-content {
.analysis-text {
font-size: 28rpx;
color: #666;
line-height: 1.8;
word-break: break-word;
white-space: pre-wrap;
}
&:active {
transform: scale(0.96);
box-shadow: 0 2rpx 8rpx rgba(245, 87, 108, 0.3);
}
}

File diff suppressed because it is too large Load Diff