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); 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 "enablePullDownRefresh": false
} }
}, },
{
"path": "pages/base/wj/wjEvaluate",
"style": {
"navigationBarTitleText": "AI分析",
"enablePullDownRefresh": false
}
},
{ {
"path": "pages/base/xk/index", "path": "pages/base/xk/index",
"style": { "style": {

View File

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

File diff suppressed because it is too large Load Diff