This commit is contained in:
zpc 2026-02-22 22:25:41 +08:00
parent 2d4ffabd9e
commit d292368dd3
3 changed files with 577 additions and 667 deletions

View File

@ -50,6 +50,21 @@
"autoApprove": [
"execute_sql"
]
},
"lanhu": {
"url": "http://localhost:8000/mcp?role=开发&name=developer",
"disabled": false,
"autoApprove": [
"lanhu_get_pages",
"lanhu_get_designs",
"lanhu_get_design_slices",
"lanhu_get_ai_analyze_page_result",
"lanhu_get_ai_analyze_design_result",
"lanhu_resolve_invite_link",
"lanhu_say_list",
"lanhu_say_detail",
"lanhu_get_members"
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
* - 未答题弹窗提示
*/
import { ref, computed, onMounted } from 'vue'
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { getQuestionList, submitAnswers } from '@/api/assessment.js'
@ -35,6 +35,9 @@ const submitting = ref(false)
const showUnansweredPopup = ref(false)
const unansweredQuestions = ref([])
// scroll-view
const scrollTarget = ref('')
//
const scoreOptions = [
{ score: 1, label: '极弱', desc: '完全不符合' },
@ -49,23 +52,13 @@ const scoreOptions = [
{ score: 10, label: '极强', desc: '完全符合' }
]
/**
* 已答题数量
*/
const answeredCount = computed(() => {
return Object.keys(answers.value).length
})
/** 已答题数量 */
const answeredCount = computed(() => Object.keys(answers.value).length)
/**
* 总题目数量
*/
const totalCount = computed(() => {
return questions.value.length
})
/** 总题目数量 */
const totalCount = computed(() => questions.value.length)
/**
* 加载题目列表
*/
/** 加载题目列表 */
async function loadQuestions() {
pageLoading.value = true
try {
@ -83,18 +76,15 @@ async function loadQuestions() {
}
}
/**
* 生成模拟题目数据开发测试用
*/
/** 生成模拟题目数据(开发测试用) */
function generateMockQuestions() {
const mockQuestions = [
'注重细节,主动比较不同环境中动物、植物的适应性特征(干旱、雨季、雷雨等),对比并分析这些不同特征对动植物的影响。',
'精细操作类游戏或活动表现好(如转笔、游戏操作)或在美术课中能够画出细节丰富的作品或能否熟练使用某种乐器或喜欢组装复杂的手工制作或模型。',
'在超市买水果时,喜欢主动在心里按颜色、形状或软硬程度给它们分类或整理个人收藏(如树叶标本、玩具)时,习惯按自定的标准进行系统排列。',
'喜欢自然科学类的课程或喜欢进行自主的自然观察和记录。',
'精细操作类游戏或活动表现好(如转笔、游戏操作) 或在美术课中能够画出细节丰富的作品 或能否熟练使用某种乐器 或喜欢组装复杂的手工制作或模型。',
'在超市买水果时,喜欢主动在心里按颜色、形状或软硬程度给它们分类 整理个人收藏(如树叶标本、玩具)时,习惯按自定的标准进行系统排列。',
'喜欢自然科学类的课程 喜欢进行自主的自然观察和记录。',
'能理解并讨论音乐作品的情感和意义或能演奏一种或多种乐器或能识别和分析音乐中的和声和节奏变化。'
]
return mockQuestions.map((content, index) => ({
id: index + 1,
questionNo: index + 1,
@ -103,23 +93,17 @@ function generateMockQuestions() {
}))
}
/**
* 选择答案
*/
/** 选择答案 */
function selectAnswer(questionId, scoreIndex) {
answers.value[questionId] = scoreIndex
}
/**
* 检查是否选中
*/
/** 检查是否选中 */
function isSelected(questionId, scoreIndex) {
return answers.value[questionId] === scoreIndex
}
/**
* 提交答案
*/
/** 提交答案 */
async function handleSubmit() {
//
const unanswered = []
@ -129,75 +113,58 @@ async function handleSubmit() {
unanswered.push(q.questionNo || index + 1)
}
})
if (unanswered.length > 0) {
unansweredQuestions.value = unanswered
showUnansweredPopup.value = true
return
}
submitting.value = true
try {
const answerList = questions.value.map((q, index) => {
const qId = q.id || index + 1
const scoreIndex = answers.value[qId]
return {
questionId: qId,
score: scoreOptions[scoreIndex].score
}
return { questionId: qId, score: scoreOptions[scoreIndex].score }
})
const res = await submitAnswers({
typeId: typeId.value,
orderId: orderId.value,
inviteCode: inviteCode.value,
answers: answerList
})
if (res && res.code === 0) {
const recordId = res.data?.recordId || res.data?.id || ''
uni.redirectTo({
url: `/pages/assessment/loading/index?recordId=${recordId}`
})
uni.redirectTo({ url: `/pages/assessment/loading/index?recordId=${recordId}` })
} else {
uni.showToast({
title: res?.message || '提交失败,请重试',
icon: 'none'
})
uni.showToast({ title: res?.message || '提交失败,请重试', icon: 'none' })
}
} catch (error) {
console.error('提交答案失败:', error)
uni.showToast({
title: '提交失败,请重试',
icon: 'none'
})
uni.showToast({ title: '提交失败,请重试', icon: 'none' })
} finally {
submitting.value = false
}
}
/**
* 关闭未答题弹窗
*/
/** 关闭未答题弹窗 */
function closeUnansweredPopup() {
showUnansweredPopup.value = false
}
/**
* 滚动到未答题目
*/
/** 滚动到未答题目 */
function scrollToQuestion(questionNo) {
closeUnansweredPopup()
uni.pageScrollTo({
selector: `#question-${questionNo}`,
duration: 300
})
// id
scrollTarget.value = ''
setTimeout(() => {
scrollTarget.value = `question-${questionNo}`
}, 50)
}
/**
* 页面加载
*/
/** 页面加载 */
onLoad((options) => {
typeId.value = Number(options.typeId) || 1
orderId.value = options.orderId || ''
@ -208,90 +175,81 @@ onLoad((options) => {
</script>
<template>
<view class="assessment-questions-page">
<view class="questions-page">
<!-- 导航栏 -->
<Navbar title="多元智能测评" :showBack="true" backgroundColor="rgba(255, 241, 231, 1)" />
<Navbar title="多元智能测评" :showBack="true" />
<!-- 加载状态 -->
<view v-if="pageLoading" class="loading-container">
<view v-if="pageLoading" class="loading-wrap">
<view class="loading-spinner"></view>
<text class="loading-text">加载题目中...</text>
</view>
<!-- 题目列表 - 所有题目在一个白色卡片内 -->
<view v-else class="questions-wrapper">
<view class="questions-card">
<view
v-for="(question, index) in questions"
:key="question.id || index"
:id="'question-' + (question.questionNo || index + 1)"
class="question-item"
>
<!-- 题目标题 -->
<view class="question-header">
<text class="question-no">{{ question.questionNo || index + 1 }}</text>
<text class="question-content">{{ question.content }}</text>
</view>
<!-- 选项列表 -->
<view class="options-list">
<view
v-for="(option, optIndex) in scoreOptions"
:key="optIndex"
class="option-item"
@click="selectAnswer(question.id || index + 1, optIndex)"
>
<view class="option-radio" :class="{ 'radio-selected': isSelected(question.id || index + 1, optIndex) }">
<view v-if="isSelected(question.id || index + 1, optIndex)" class="radio-inner"></view>
<!-- 题目滚动区域 -->
<scroll-view v-else class="scroll-area" scroll-y :scroll-into-view="scrollTarget">
<view class="scroll-inner">
<view class="questions-card">
<view
v-for="(question, index) in questions"
:key="question.id || index"
:id="'question-' + (question.questionNo || index + 1)"
class="question-block"
>
<!-- 题目标题 -->
<view class="question-title">
<view class="question-no">{{ question.questionNo || index + 1 }}</view>
<text class="question-text">{{ question.content }}</text>
</view>
<!-- 选项列表 - 全部10个 -->
<view class="options-list">
<view
v-for="(option, optIdx) in scoreOptions"
:key="optIdx"
class="option-row"
@click="selectAnswer(question.id || index + 1, optIdx)"
>
<view class="radio" :class="{ 'radio-active': isSelected(question.id || index + 1, optIdx) }">
<view v-if="isSelected(question.id || index + 1, optIdx)" class="radio-dot"></view>
</view>
<text class="option-label">{{ option.label }}{{ option.desc }}</text>
</view>
<text class="option-text">{{ option.label }}{{ option.desc }}</text>
</view>
</view>
<!-- 题目之间的分隔 -->
<view v-if="index < questions.length - 1" class="question-divider"></view>
</view>
</view>
</view>
<!-- 底部提交按钮固定在底部 -->
<view v-if="!pageLoading" class="submit-section">
<view
class="submit-btn"
:class="{ 'btn-loading': submitting }"
@click="handleSubmit"
>
</scroll-view>
<!-- 底部固定提交按钮 -->
<view v-if="!pageLoading" class="submit-fixed">
<view class="submit-btn" :class="{ 'btn-loading': submitting }" @click="handleSubmit">
<text>{{ submitting ? '提交中...' : '提交' }}</text>
</view>
</view>
<!-- 未答题弹窗 -->
<view v-if="showUnansweredPopup" class="popup-mask" @click="closeUnansweredPopup">
<view class="popup-container" @click.stop>
<view class="popup-box" @click.stop>
<view class="popup-header">
<text class="popup-title">提示</text>
<view class="popup-close" @click="closeUnansweredPopup">
<text>×</text>
</view>
<view class="popup-close" @click="closeUnansweredPopup"><text>×</text></view>
</view>
<view class="popup-body">
<view class="popup-message">以下题目尚未作答请完成后再提交</view>
<scroll-view class="unanswered-list" scroll-y>
<view
v-for="qNo in unansweredQuestions"
<view class="popup-msg">以下题目尚未作答请完成后再提交</view>
<scroll-view class="unanswered-scroll" scroll-y>
<view
v-for="qNo in unansweredQuestions"
:key="qNo"
class="unanswered-item"
class="unanswered-row"
@click="scrollToQuestion(qNo)"
>
<text> {{ qNo }} </text>
<view class="goto-icon">></view>
<text class="goto-arrow"></text>
</view>
</scroll-view>
</view>
<view class="popup-footer">
<view class="popup-btn" @click="closeUnansweredPopup">
<text>我知道了</text>
</view>
<view class="popup-btn" @click="closeUnansweredPopup"><text>我知道了</text></view>
</view>
</view>
</view>
@ -301,29 +259,31 @@ onLoad((options) => {
<style lang="scss" scoped>
@import '@/styles/variables.scss';
.assessment-questions-page {
min-height: 100vh;
background-color: rgba(255, 241, 231, 1);
padding-bottom: env(safe-area-inset-bottom);
.questions-page {
height: 100vh;
display: flex;
flex-direction: column;
background-color: rgba(255, 234, 231, 1);
overflow: hidden;
}
//
.loading-container {
// ========== ==========
.loading-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid $border-color;
border-top-color: rgba(255, 107, 96, 1);
border-top-color: #FF6B60;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loading-text {
margin-top: $spacing-md;
font-size: $font-size-md;
@ -335,150 +295,153 @@ onLoad((options) => {
to { transform: rotate(360deg); }
}
//
.questions-wrapper {
padding: $spacing-lg;
padding-top: $spacing-xl;
padding-bottom: 180rpx;
// ========== ==========
.scroll-area {
flex: 1;
overflow: hidden;
}
.scroll-inner {
padding: $spacing-lg;
}
//
.questions-card {
background-color: $bg-white;
border-radius: $border-radius-xl;
padding: $spacing-lg;
box-shadow: $shadow-md;
padding: $spacing-xl $spacing-lg;
}
//
.question-item {
padding: $spacing-md 0;
// ========== ==========
.question-block {
margin-bottom: $spacing-xl;
&:last-child {
margin-bottom: 0;
}
}
.question-header {
.question-title {
display: flex;
align-items: flex-start;
margin-bottom: $spacing-md;
.question-no {
color: rgba(255, 107, 96, 1);
font-size: $font-size-lg;
font-weight: $font-weight-bold;
flex-shrink: 0;
margin-right: $spacing-sm;
line-height: 1.6;
}
.question-content {
flex: 1;
font-size: $font-size-md;
color: $text-color;
line-height: 1.6;
}
margin-bottom: $spacing-lg;
}
//
.question-no {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
background-color: rgba(255, 234, 231, 1);
color: #FF6B60;
font-size: $font-size-md;
font-weight: $font-weight-bold;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: $spacing-sm;
margin-top: 4rpx;
}
.question-text {
flex: 1;
font-size: $font-size-md;
color: $text-color;
font-weight: $font-weight-bold;
line-height: 1.7;
}
// ========== ==========
.options-list {
padding-left: 40rpx;
padding-left: 60rpx;
}
.option-item {
.option-row {
display: flex;
align-items: center;
padding: $spacing-sm 0;
&:active {
opacity: 0.7;
}
}
.option-radio {
.radio {
width: 32rpx;
height: 32rpx;
border: 2rpx solid $border-color;
border: 3rpx solid $border-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: $spacing-sm;
&.radio-selected {
border-color: rgba(255, 107, 96, 1);
.radio-inner {
width: 20rpx;
height: 20rpx;
background-color: rgba(255, 107, 96, 1);
&.radio-active {
border-color: #FF6B60;
.radio-dot {
width: 18rpx;
height: 18rpx;
background-color: #FF6B60;
border-radius: 50%;
}
}
}
.option-text {
.option-label {
font-size: $font-size-sm;
color: $text-color;
line-height: 1.5;
}
// 线
.question-divider {
height: 1rpx;
background-color: $border-light;
margin: $spacing-md 0;
}
//
.submit-section {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: $spacing-lg $spacing-lg calc(#{$spacing-lg} + env(safe-area-inset-bottom));
z-index: 100;
background-color: rgba(255, 241, 231, 1);
// ========== ==========
.submit-fixed {
flex-shrink: 0;
padding: $spacing-lg $spacing-lg;
padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom));
background-color: rgba(255, 234, 231, 1);
}
.submit-btn {
height: 96rpx;
background-color: rgba(255, 107, 96, 1);
background-color: #FF6B60;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $text-white;
}
&:active {
opacity: 0.9;
transform: scale(0.98);
}
&.btn-loading {
opacity: 0.7;
pointer-events: none;
}
}
//
// ========== ==========
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
background-color: $bg-mask;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.popup-container {
.popup-box {
width: 600rpx;
max-height: 80vh;
background-color: $bg-white;
@ -493,13 +456,13 @@ onLoad((options) => {
padding: $spacing-lg;
text-align: center;
border-bottom: 1rpx solid $border-light;
.popup-title {
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: $text-color;
}
.popup-close {
position: absolute;
top: $spacing-md;
@ -509,7 +472,7 @@ onLoad((options) => {
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 48rpx;
color: $text-placeholder;
@ -522,19 +485,19 @@ onLoad((options) => {
padding: $spacing-lg;
flex: 1;
overflow: hidden;
.popup-message {
.popup-msg {
font-size: $font-size-md;
color: $text-secondary;
margin-bottom: $spacing-md;
}
}
.unanswered-list {
.unanswered-scroll {
max-height: 400rpx;
}
.unanswered-item {
.unanswered-row {
display: flex;
align-items: center;
justify-content: space-between;
@ -542,17 +505,16 @@ onLoad((options) => {
background-color: $bg-gray;
border-radius: $border-radius-md;
margin-bottom: $spacing-sm;
text {
font-size: $font-size-md;
color: rgba(255, 107, 96, 1);
color: #FF6B60;
}
.goto-icon {
font-size: $font-size-md;
.goto-arrow {
color: $text-placeholder;
}
&:active {
background-color: $border-light;
}
@ -564,18 +526,18 @@ onLoad((options) => {
.popup-btn {
height: 88rpx;
background-color: rgba(255, 107, 96, 1);
background-color: #FF6B60;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: $text-white;
}
&:active {
opacity: 0.8;
}