问卷调整

This commit is contained in:
hebo 2025-12-24 19:42:28 +08:00
parent 2a6fb3d916
commit 904471d156
18 changed files with 356 additions and 220 deletions

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8"/>
<!-- 版本号:每次发布时自动更新 -->
<meta name="version" content="20251219-120015">
<meta name="version" content="20251223-185504">
<!-- HTML文件不缓存但允许静态资源缓存 -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
@ -14,7 +14,7 @@
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
log=false
var log = false;
// 版本检查:如果检测到新版本,清除缓存并刷新
(function() {

View File

@ -78,6 +78,10 @@ export const findAllZw = () => {

View File

@ -19,6 +19,7 @@
:mode="attrs.componentProps.mode"
:title="attrs.label"
:value="newValue ? newValue : undefined"
:onPopupChange="handlePopupChange"
@confirm="confirm"
/>
</view>
@ -94,4 +95,14 @@ function confirm(e: any) {
attrs.componentProps.ok(e, attrs);
}
}
//
function handlePopupChange(e: any) {
if (
attrs.componentProps.onPopupChange &&
typeof attrs.componentProps.onPopupChange === "function"
) {
attrs.componentProps.onPopupChange(e);
}
}
</script>

View File

@ -35,6 +35,7 @@
v-model="pickerKey"
@ok="ok"
@change="change"
@popupChange="handlePopupChange"
/>
</view>
</template>
@ -161,6 +162,16 @@ function change(e: any) {
}
}
//
function handlePopupChange(e: any) {
if (
attrs.componentProps.onPopupChange &&
typeof attrs.componentProps.onPopupChange === "function"
) {
attrs.componentProps.onPopupChange(e);
}
}
const pickerValue = ref<any>("");
watchEffect(() => {

View File

@ -19,7 +19,8 @@
:rangeKey="rangeKey"
:title="'选择'+ attrs.label"
@confirm="confirm"
v-bind="attrs.componentProps"
:onPopupChange="handlePopupChange"
v-bind="treeProps"
/>
</view>
</template>
@ -60,6 +61,12 @@
const rangeKey = ref(attrs.componentProps && attrs.componentProps.rangeKey || '')
const savaKey = ref(attrs.componentProps && attrs.componentProps.savaKey || '')
// onPopupChange
const treeProps = computed(() => {
const { onPopupChange, ...rest } = attrs.componentProps || {}
return rest
})
const range = computed({
get() {
if (attrs.componentProps && attrs.componentProps.range) {
@ -106,6 +113,16 @@
}
}
//
function handlePopupChange(e: any) {
if (
attrs.componentProps.onPopupChange &&
typeof attrs.componentProps.onPopupChange === "function"
) {
attrs.componentProps.onPopupChange(e);
}
}
function hx(rangeData: any, data: any) {
let valueList = data.split(',')
for (const key in rangeData) {

View File

@ -81,6 +81,11 @@ const props = defineProps({
type: Array,
default: [0],
},
// props
onPopupChange: {
type: Function,
default: null,
},
});
const emits = defineEmits([
@ -132,6 +137,14 @@ function ok() {
function popupChange(e) {
emits("popupChange", e);
//
if (props.onPopupChange) {
try {
props.onPopupChange(e);
} catch (err) {
console.error('[BasicPicker] 调用 onPopupChange 失败:', err);
}
}
}
defineExpose({ open, close });

View File

@ -1,5 +1,5 @@
<template>
<uni-popup ref="popup" background-color="#fff" class="popup">
<uni-popup ref="popup" background-color="#fff" class="popup" @change="popupChange">
<view class="center flex-col wh-full">
<view class="flex-row justify-between p-15">
<view @click="close">
@ -115,6 +115,11 @@ export default {
defaultIndex: {
type: Array,
default: () => []
},
// props
onPopupChange: {
type: Function,
default: null,
}
},
data() {
@ -156,6 +161,16 @@ export default {
emits: ['close', 'canel', 'confirm', 'change'],
// #endif
methods: {
popupChange(e) {
//
if (this.onPopupChange) {
try {
this.onPopupChange(e)
} catch (err) {
console.error('[DatetimePicker] 调用 onPopupChange 失败:', err)
}
}
},
init() {
this.innerValue = this.correctValue(this.value)
this.updateColumnValue(this.innerValue)

View File

@ -118,6 +118,11 @@ export default {
type: Boolean,
default: false
},
// props
onPopupChange: {
type: Function,
default: null,
},
},
data() {
return {
@ -130,9 +135,25 @@ export default {
methods: {
_show() {
this.showTree = true
//
if (this.onPopupChange && typeof this.onPopupChange === 'function') {
try {
this.onPopupChange({ show: true, type: 'bottom' })
} catch (err) {
console.error('[BasicTree] 调用 onPopupChange 失败:', err)
}
}
},
_hide() {
this.showTree = false
//
if (this.onPopupChange && typeof this.onPopupChange === 'function') {
try {
this.onPopupChange({ show: false, type: 'bottom' })
} catch (err) {
console.error('[BasicTree] 调用 onPopupChange 失败:', err)
}
}
},
_cancel() {
this._hide()

View File

@ -6,7 +6,7 @@
<text class="loading-text">{{ loadingText }}</text>
</view>
</view>
<scroll-view scroll-y class="form-scroll" :enable-back-to-top="false">
<scroll-view scroll-y class="form-scroll" :class="{ 'full-height': hideBottomBtn }" :enable-back-to-top="false">
<view class="form-container">
<!-- 积分标准区域 -->
<view class="section">
@ -148,13 +148,11 @@
</view>
</scroll-view>
<template #bottom>
<view class="fixed-bottom">
<button class="submit-btn" @click="handleSubmit" :disabled="submitting">
<view class="bottom-btn-area" :class="{ 'hide-bottom': hideBottomBtn }">
<button class="save-button" @click="handleSubmit" :disabled="submitting">
{{ submitting ? '保存中...' : '保存' }}
</button>
</view>
</template>
</BasicLayout>
<view v-else class="edit-content-wrapper">
<view v-if="loading" class="loading-mask">
@ -326,6 +324,15 @@ import CertificateUpload from "@/components/CertificateUpload/index.vue";
// 使
const showSaveButton = ref(true);
//
const hideBottomBtn = ref(false);
//
const handlePickerPopupChange = (e: any) => {
// e.show true false
hideBottomBtn.value = e.show;
};
const formData = reactive<any>({
id: '',
jfTypeId: '',
@ -1092,7 +1099,8 @@ const formSchema = computed(() => {
selectParent: false,
defaultExpandLevel: 0,
confirmColor: "#3b82f6",
ok: handleJfTypeTreeConfirm
ok: handleJfTypeTreeConfirm,
onPopupChange: handlePickerPopupChange
}
},
{
@ -1150,7 +1158,8 @@ const assessmentSchema = computed(() => {
range: hjlxOptions.value,
rangeKey: "label",
savaKey: "value",
placeholder: '请选择获奖类型'
placeholder: '请选择获奖类型',
onPopupChange: handlePickerPopupChange
}
},
{
@ -1174,7 +1183,8 @@ const assessmentSchema = computed(() => {
mode: "date",
minDate: dayjs('1950-01-01').valueOf(),
maxDate: dayjs().valueOf(),
placeholder: '请选择获奖时间'
placeholder: '请选择获奖时间',
onPopupChange: handlePickerPopupChange
}
},
// showStudentAward
@ -1206,6 +1216,7 @@ const assessmentSchema = computed(() => {
rangeKey: "label",
savaKey: "value",
placeholder: '请选择类别',
onPopupChange: handlePickerPopupChange,
ok: () => {
if (formData.category && scoreConfig.value) {
checkGradeFieldVisibility(scoreConfig.value, formData.category);
@ -1222,7 +1233,8 @@ const assessmentSchema = computed(() => {
range: defaultLevels,
rangeKey: "label",
savaKey: "value",
placeholder: '请选择级别'
placeholder: '请选择级别',
onPopupChange: handlePickerPopupChange
}
}
];
@ -1241,7 +1253,8 @@ const assessmentSchema = computed(() => {
],
rangeKey: "label",
savaKey: "value",
placeholder: '请选择等级'
placeholder: '请选择等级',
onPopupChange: handlePickerPopupChange
}
});
}
@ -1754,8 +1767,13 @@ onLoad(async (options: any) => {
<style scoped lang="scss">
.form-scroll {
flex: 1;
height: calc(100vh - 120rpx);
height: calc(100vh - 140rpx);
max-height: 100vh;
transition: height 0.3s ease;
}
.form-scroll.full-height {
height: 100vh;
}
.edit-content-wrapper {
@ -1809,38 +1827,46 @@ onLoad(async (options: any) => {
}
}
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 24rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 100;
.bottom-btn-area {
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
border-top: 1rpx solid #e5e5e5;
transition: all 0.2s ease;
&.hide-bottom {
display: none !important;
height: 0 !important;
padding: 0 !important;
margin: 0 !important;
border: none !important;
overflow: hidden;
}
}
.submit-btn {
.save-button {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #3b82f6;
color: #ffffff;
height: 80rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
font-size: 30rpx;
font-weight: 500;
border-radius: 40rpx;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 4rpx 16rpx rgba(59, 130, 246, 0.3);
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
&:disabled {
background: #9ca3af;
box-shadow: none;
opacity: 0.6;
background: #ccc;
}
&:active:not(:disabled) {
transform: scale(0.98);
background: #1d4ed8;
opacity: 0.8;
}
}

View File

@ -6,7 +6,7 @@
<text class="loading-text">{{ loadingText }}</text>
</view>
</view>
<scroll-view scroll-y class="form-scroll">
<scroll-view scroll-y class="form-scroll" :class="{ 'full-height': hideBottomBtn }" :enable-back-to-top="false">
<view class="form-container">
<!-- 积分标准区域 -->
<view class="section">
@ -165,7 +165,7 @@
</scroll-view>
<!-- 固定在底部的按钮组 -->
<view class="fixed-bottom">
<view class="fixed-bottom" :class="{ 'hide-bottom': hideBottomBtn }">
<view class="button-group">
<button class="save-btn" @click="handleSave" :disabled="saving || submitting">
{{ saving ? '保存中...' : '暂存' }}
@ -250,6 +250,7 @@ const loading = ref(false); // 页面加载状态
const loadingText = ref('加载中...'); //
const isResubmit = ref(false); //
const jfId = ref(''); // ID使
const hideBottomBtn = ref(false); // /
// key
const formSchemaKey = ref(0);
// key
@ -537,6 +538,12 @@ function findNodeById(nodes: any[], id: string, path: string[] = []): { node: an
//
const selectedJfTypeText = ref('');
//
const handlePickerPopupChange = (e: any) => {
// e.show true false
hideBottomBtn.value = e.show;
};
// formData.jfTypeId
const initJfTypeDisplay = () => {
if (formData.jfTypeId && jfTypeTree.value.length > 0) {
@ -1018,7 +1025,8 @@ const formSchema = computed(() => {
selectParent: false, //
defaultExpandLevel: 0, //
confirmColor: "#3b82f6",
ok: handleJfTypeTreeConfirm
ok: handleJfTypeTreeConfirm,
onPopupChange: handlePickerPopupChange
}
},
{
@ -1067,7 +1075,8 @@ const assessmentSchema = computed(() => {
range: hjlxOptions.value,
rangeKey: "label",
savaKey: "value",
placeholder: '请选择获奖类型'
placeholder: '请选择获奖类型',
onPopupChange: handlePickerPopupChange
}
},
{
@ -1091,7 +1100,8 @@ const assessmentSchema = computed(() => {
mode: "date",
minDate: dayjs('1950-01-01').valueOf(),
maxDate: dayjs().valueOf(),
placeholder: '请选择获奖时间'
placeholder: '请选择获奖时间',
onPopupChange: handlePickerPopupChange
}
},
// showStudentAward
@ -1123,6 +1133,7 @@ const assessmentSchema = computed(() => {
rangeKey: "label",
savaKey: "value",
placeholder: '请选择类别',
onPopupChange: handlePickerPopupChange,
ok: () => {
//
if (formData.category && scoreConfig.value) {
@ -1140,7 +1151,8 @@ const assessmentSchema = computed(() => {
range: defaultLevels,
rangeKey: "label",
savaKey: "value",
placeholder: '请选择级别'
placeholder: '请选择级别',
onPopupChange: handlePickerPopupChange
}
}
];
@ -1160,7 +1172,8 @@ const assessmentSchema = computed(() => {
],
rangeKey: "label",
savaKey: "value",
placeholder: '请选择等级'
placeholder: '请选择等级',
onPopupChange: handlePickerPopupChange
}
});
}
@ -1903,6 +1916,10 @@ onLoad(async (options: any) => {
.form-scroll {
flex: 1;
height: calc(100vh - 120rpx);
&.full-height {
height: 100vh;
}
}
.form-container {
@ -1952,6 +1969,12 @@ onLoad(async (options: any) => {
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 1;
&.hide-bottom {
display: none !important;
height: 0 !important;
padding: 0 !important;
}
.button-group {
display: flex;
gap: 24rpx;

View File

@ -1,6 +1,6 @@
<template>
<BasicLayout v-if="showSaveButton">
<scroll-view scroll-y class="form-scroll" :enable-back-to-top="false">
<scroll-view scroll-y class="form-scroll" :class="{ 'full-height': hideBottomBtn }" :enable-back-to-top="false">
<view class="form-container">
<!-- 积分标准区域 -->
<view class="section">
@ -134,7 +134,7 @@
</scroll-view>
<template #bottom>
<view class="fixed-bottom">
<view class="fixed-bottom" :class="{ 'hide-bottom': hideBottomBtn }">
<button class="submit-btn" @click="handleSubmit" :disabled="submitting">
{{ submitting ? '保存中...' : '保存' }}
</button>
@ -310,6 +310,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
(e: 'saved'): void;
(e: 'loaded'): void;
(e: 'popup-change', show: boolean): void;
}>();
// 访
@ -365,6 +366,16 @@ const formSchemaKey = ref(0);
const assessmentSchemaKey = ref(0);
//
const certificateUploadRef = ref<any>(null);
// /
const hideBottomBtn = ref(false);
//
const handlePickerPopupChange = (e: any) => {
// e.show true false
hideBottomBtn.value = e.show;
//
emit('popup-change', e.show);
};
// key
let updateSchemaKeyTimer: any = null;
@ -1043,7 +1054,8 @@ const assessmentSchema = computed(() => {
range: hjlxOptions.value,
rangeKey: "label",
savaKey: "value",
placeholder: '请选择获奖类型'
placeholder: '请选择获奖类型',
onPopupChange: handlePickerPopupChange
}
},
{
@ -1067,7 +1079,8 @@ const assessmentSchema = computed(() => {
mode: "date",
minDate: dayjs('1950-01-01').valueOf(),
maxDate: dayjs().valueOf(),
placeholder: '请选择获奖时间'
placeholder: '请选择获奖时间',
onPopupChange: handlePickerPopupChange
}
},
// showStudentAward
@ -1099,6 +1112,7 @@ const assessmentSchema = computed(() => {
rangeKey: "label",
savaKey: "value",
placeholder: '请选择类别',
onPopupChange: handlePickerPopupChange,
ok: () => {
if (formData.category && scoreConfig.value) {
checkGradeFieldVisibility(scoreConfig.value, formData.category);
@ -1115,7 +1129,8 @@ const assessmentSchema = computed(() => {
range: defaultLevels,
rangeKey: "label",
savaKey: "value",
placeholder: '请选择级别'
placeholder: '请选择级别',
onPopupChange: handlePickerPopupChange
}
}
];
@ -1134,7 +1149,8 @@ const assessmentSchema = computed(() => {
],
rangeKey: "label",
savaKey: "value",
placeholder: '请选择等级'
placeholder: '请选择等级',
onPopupChange: handlePickerPopupChange
}
});
}
@ -1683,6 +1699,10 @@ defineExpose({
flex: 1;
height: calc(100vh - 120rpx);
max-height: 100vh;
&.full-height {
height: 100vh;
}
}
.edit-content-wrapper {
@ -1745,6 +1765,12 @@ defineExpose({
padding: 24rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
z-index: 100;
&.hide-bottom {
display: none !important;
height: 0 !important;
padding: 0 !important;
}
}
.submit-btn {

View File

@ -1,9 +1,9 @@
<template>
<BasicLayout>
<!-- 统一的滚动容器包含编辑组件和流程组件 -->
<scroll-view scroll-y class="unified-scroll">
<scroll-view scroll-y class="unified-scroll" :class="{ 'full-height': hideBottomBtn }">
<!-- 引入编辑组件不包含滚动 -->
<JfEdit v-if="isLoginReady" ref="jfEditRef" :jf-id="jfId" @saved="handleDataSaved" @loaded="handleJfEditLoaded" />
<JfEdit v-if="isLoginReady" ref="jfEditRef" :jf-id="jfId" @saved="handleDataSaved" @loaded="handleJfEditLoaded" @popup-change="handlePickerPopupChange" />
<!-- 审批流程展示 -->
<view class="flow-section" v-if="jfId">
@ -12,6 +12,7 @@
</scroll-view>
<template #bottom>
<view :class="{ 'hide-bottom': hideBottomBtn }">
<YwConfirm
v-if="showButton"
:spApi="wrappedJfSpApi"
@ -32,6 +33,7 @@
@transfer="handleTransfer"
@stop="handleStop"
/>
</view>
</template>
<!-- 遮罩层 -->
@ -64,6 +66,8 @@ const showButton = ref<boolean>(false);
const isLoginReady = ref<boolean>(false); //
//
const jfEditRef = ref<any>(null);
// /
const hideBottomBtn = ref(false);
//
const loading = ref<boolean>(false);
const loadingText = ref<string>("处理中...");
@ -401,6 +405,11 @@ const wrappedJfStopApi = async (params: any) => {
}
};
//
const handlePickerPopupChange = (show: boolean) => {
hideBottomBtn.value = show;
};
//
const handleJfEditLoaded = () => {
jfEditLoaded.value = true;
@ -553,6 +562,10 @@ onLoad(async (options: any) => {
flex: 1;
height: calc(100vh - 120rpx);
overflow-y: auto;
&.full-height {
height: 100vh;
}
}
.flow-section {
@ -626,5 +639,12 @@ onLoad(async (options: any) => {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
//
.hide-bottom {
display: none !important;
height: 0 !important;
padding: 0 !important;
}
</style>

View File

@ -300,7 +300,7 @@
<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>
<text class="analysis-title">AI数字分析</text>
</view>
<view class="analysis-content">
<text class="analysis-text">{{ analysisResult }}</text>

View File

@ -306,7 +306,7 @@
<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>
<text class="analysis-title">AI数字分析</text>
</view>
<view class="analysis-content">
<text class="analysis-text">{{ analysisResult }}</text>

View File

@ -284,29 +284,12 @@
<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 @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 @click="goBack" class="back-btn">返回</button>
</view>
</view>
@ -327,6 +310,13 @@
<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 @click="viewAIAnalysisReport" class="ai-analyze-btn">
<u-icon name="file-text" size="18" color="#fff" style="margin-right: 8rpx;"></u-icon>
查看AI分析报告
</button>
<button @click="goBack" class="back-btn">返回</button>
</view>
</view>
@ -351,8 +341,7 @@ import {
questionnaireCheckPermissionApi,
questionnaireQuestionFindPageApi,
questionnaireAnswerSaveApi,
questionnaireAnswerFindByUserApi,
questionnaireAnalyzeWithAIApi
questionnaireAnswerFindByUserApi
} from '@/api/base/wjApi';
import { useUserStore } from '@/store/modules/user';
import { attachmentUpload } from '@/api/system/upload';
@ -363,6 +352,7 @@ const userStore = useUserStore();
//
const options = ref<any>({});
const questionnaireId = ref('');
const xxtsId = ref<string>(""); // ID
const openId = ref<string>(""); // openId
const loading = ref(true);
const submitting = ref(false); //
@ -392,9 +382,8 @@ const fileUploadData = reactive<Record<string, {
//
const compressConfig = ref(COMPRESS_PRESETS.high);
// AI
const analyzing = ref(false);
const analysisResult = ref('');
// IDAI
const submitId = ref<string>('');
//
const signCompRef = ref<any>(null);
@ -406,6 +395,7 @@ const currentSignatureQuestionId = ref<string>(""); // 当前正在签名的题
onLoad(async (params) => {
options.value = params;
openId.value = params?.openId || '';
xxtsId.value = params?.id || ''; // xxts id
// openId
if (openId.value) {
@ -1062,10 +1052,20 @@ const doSubmitQuestionnaire = async () => {
submitParams.signatureImage = sign_file.value;
}
// xxtsId
if (xxtsId.value) {
submitParams.xxtsId = xxtsId.value;
}
//
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 || '提交失败');
@ -1094,69 +1094,14 @@ const formatTime = (time: string | Date) => {
});
};
// AI
const analyzeWithAI = async () => {
try {
analyzing.value = true;
analysisResult.value = '';
// AI
const allQuestions: any[] = [];
pageList.value.forEach(page => {
allQuestions.push(...page.questions);
// AI
const viewAIAnalysisReport = () => {
// ID wjDetail.vue
const currentUserId = userStore.userdata?.id || userStore.userdata?.userId || '';
// wjEvaluatedl.vue questionnaireId userId wjDetail.vue
uni.navigateTo({
url: `/pages/view/routine/wj/wjEvaluatedl?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 (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 ? '[已签名]' : '[未签名]';
}
//
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 || '分析完成';
} else {
throw new Error(response?.message || 'AI分析失败');
}
} catch (error: any) {
console.error('AI分析失败:', error);
uni.showToast({ title: error.message || 'AI分析失败请稍后重试', icon: 'none', duration: 2000 });
} finally {
analyzing.value = false;
}
};
</script>
@ -1687,57 +1632,6 @@ const analyzeWithAI = async () => {
}
}
.analyzing-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
padding: 30rpx;
background: #f5f7fa;
border-radius: 16rpx;
.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;
}
}
}
.back-btn {
width: 300rpx;
height: 80rpx;

View File

@ -168,7 +168,7 @@
<view class="bottom-button-container">
<button class="evaluate-btn" @click="handleEvaluate">
<u-icon name="file-text" size="18" color="#fff" style="margin-right: 8rpx;"></u-icon>
<text>问卷评估</text>
<text>AI数字问卷</text>
</button>
</view>
</view>

View File

@ -461,6 +461,33 @@ const convertHTMLToUrl = (html: string): string => {
// HTML
let processedHtml = html;
// CDN CDN
processedHtml = processedHtml.replace(
/https:\/\/cdn\.jsdelivr\.net\/npm\/chart\.js/g,
'https://cdn.bootcdn.net/ajax/libs/Chart.js/3.9.1/chart.min.js'
);
// CDN 使 Vue
const headEndTag = [60, 47, 104, 101, 97, 100, 62].map(c => String.fromCharCode(c)).join('');
if (processedHtml.includes(headEndTag)) {
//
const scriptOpen = [60, 115, 99, 114, 105, 112, 116, 62].map(c => String.fromCharCode(c)).join('');
const scriptClose = [60, 47, 115, 99, 114, 105, 112, 116, 62].map(c => String.fromCharCode(c)).join('');
//
const script1 = scriptOpen + 'window.chartLoadError=false;' + scriptClose;
// CDN
const script2 = '<script src="https://cdn.bootcdn.net/ajax/libs/Chart.js/3.9.1/chart.min.js" onerror="window.chartLoadError=true;"><' + '/script>';
//
const script3Content = 'setTimeout(function(){if(window.chartLoadError||typeof Chart==="undefined"){var s=document.createElement("script");s.src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js";s.onerror=function(){var s2=document.createElement("script");s2.src="https://unpkg.com/chart.js@3.9.1/dist/chart.min.js";document.head.appendChild(s2);};document.head.appendChild(s);}},2000);';
const script3 = scriptOpen + script3Content + scriptClose;
const fallbackScript = script1 + script2 + script3;
processedHtml = processedHtml.replace(headEndTag, fallbackScript + headEndTag);
}
//
if (!processedHtml.includes('postMessage')) {
// 使 Vue
@ -468,7 +495,8 @@ const convertHTMLToUrl = (html: string): string => {
const scriptClose = [60, 47, 115, 99, 114, 105, 112, 116, 62].map(c => String.fromCharCode(c)).join('');
//
// resize
const scriptContent = '(function(){let hasSent=false;let sentHeight=0;function sendHeightOnce(){try{if(hasSent)return;const h=Math.max(document.body.scrollHeight||0,document.documentElement.scrollHeight||0);if(h>100&&h!==sentHeight&&window.parent&&window.parent!==window){sentHeight=h;hasSent=true;window.parent.postMessage({type:"iframe-height",height:h},"*");}}catch(e){}}function sendWithDelay(){setTimeout(sendHeightOnce,800);}if(document.readyState==="complete"){sendWithDelay();}else{window.addEventListener("load",sendWithDelay);}if(typeof Chart!=="undefined"){const o=Chart;Chart=function(...a){const c=new o(...a);sendWithDelay();return c;};}})();';
// 1500ms Chart
const scriptContent = '(function(){let hasSent=false;let sentHeight=0;function sendHeightOnce(){try{if(hasSent)return;const h=Math.max(document.body.scrollHeight||0,document.documentElement.scrollHeight||0);if(h>100&&h!==sentHeight&&window.parent&&window.parent!==window){sentHeight=h;hasSent=true;window.parent.postMessage({type:"iframe-height",height:h},"*");}}catch(e){}}function sendWithDelay(){setTimeout(sendHeightOnce,1500);}if(document.readyState==="complete"){sendWithDelay();}else{window.addEventListener("load",sendWithDelay);}if(typeof Chart!=="undefined"){const o=Chart;Chart=function(...a){const c=new o(...a);sendWithDelay();return c;};}else{setTimeout(function(){if(typeof Chart!=="undefined"){const o=Chart;Chart=function(...a){const c=new o(...a);sendWithDelay();return c;};}},3000);}})();';
const autoResizeScript = scriptOpen + scriptContent + scriptClose;
// body

View File

@ -500,6 +500,33 @@ const convertHTMLToUrl = (html: string): string => {
// HTML
let processedHtml = html;
// CDN CDN
processedHtml = processedHtml.replace(
/https:\/\/cdn\.jsdelivr\.net\/npm\/chart\.js/g,
'https://cdn.bootcdn.net/ajax/libs/Chart.js/3.9.1/chart.min.js'
);
// CDN 使 Vue
const headEndTag = [60, 47, 104, 101, 97, 100, 62].map(c => String.fromCharCode(c)).join('');
if (processedHtml.includes(headEndTag)) {
//
const scriptOpen = [60, 115, 99, 114, 105, 112, 116, 62].map(c => String.fromCharCode(c)).join('');
const scriptClose = [60, 47, 115, 99, 114, 105, 112, 116, 62].map(c => String.fromCharCode(c)).join('');
//
const script1 = scriptOpen + 'window.chartLoadError=false;' + scriptClose;
// CDN
const script2 = '<script src="https://cdn.bootcdn.net/ajax/libs/Chart.js/3.9.1/chart.min.js" onerror="window.chartLoadError=true;"><' + '/script>';
//
const script3Content = 'setTimeout(function(){if(window.chartLoadError||typeof Chart==="undefined"){var s=document.createElement("script");s.src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js";s.onerror=function(){var s2=document.createElement("script");s2.src="https://unpkg.com/chart.js@3.9.1/dist/chart.min.js";document.head.appendChild(s2);};document.head.appendChild(s);}},2000);';
const script3 = scriptOpen + script3Content + scriptClose;
const fallbackScript = script1 + script2 + script3;
processedHtml = processedHtml.replace(headEndTag, fallbackScript + headEndTag);
}
//
if (!processedHtml.includes('postMessage')) {
// 使 Vue
@ -507,7 +534,7 @@ const convertHTMLToUrl = (html: string): string => {
const scriptClose = [60, 47, 115, 99, 114, 105, 112, 116, 62].map(c => String.fromCharCode(c)).join('');
//
// resize
const scriptContent = '(function(){let hasSent=false;let sentHeight=0;function sendHeightOnce(){try{if(hasSent)return;const h=Math.max(document.body.scrollHeight||0,document.documentElement.scrollHeight||0);if(h>100&&h!==sentHeight&&window.parent&&window.parent!==window){sentHeight=h;hasSent=true;window.parent.postMessage({type:"iframe-height",height:h},"*");}}catch(e){}}function sendWithDelay(){setTimeout(sendHeightOnce,800);}if(document.readyState==="complete"){sendWithDelay();}else{window.addEventListener("load",sendWithDelay);}if(typeof Chart!=="undefined"){const o=Chart;Chart=function(...a){const c=new o(...a);sendWithDelay();return c;};}})();';
const scriptContent = '(function(){let hasSent=false;let sentHeight=0;function sendHeightOnce(){try{if(hasSent)return;const h=Math.max(document.body.scrollHeight||0,document.documentElement.scrollHeight||0);if(h>100&&h!==sentHeight&&window.parent&&window.parent!==window){sentHeight=h;hasSent=true;window.parent.postMessage({type:"iframe-height",height:h},"*");}}catch(e){}}function sendWithDelay(){setTimeout(sendHeightOnce,1500);}if(document.readyState==="complete"){sendWithDelay();}else{window.addEventListener("load",sendWithDelay);}if(typeof Chart!=="undefined"){const o=Chart;Chart=function(...a){const c=new o(...a);sendWithDelay();return c;};}else{setTimeout(function(){if(typeof Chart!=="undefined"){const o=Chart;Chart=function(...a){const c=new o(...a);sendWithDelay();return c;};}},3000);}})();';
const autoResizeScript = scriptOpen + scriptContent + scriptClose;
// body