|
|
@@ -1,289 +0,0 @@
|
|
|
-<template>
|
|
|
- <view class="container">
|
|
|
- <!-- 场景选择 -->
|
|
|
- <view v-if="!sceneId" class="pick-wrap">
|
|
|
- <view class="pick-title">选择问卷场景</view>
|
|
|
- <view class="scene-item" v-for="(s, i) in scenes" :key="getSceneKey(i)" @click="chooseScene(s)">
|
|
|
- <text class="scene-name">{{ s.sceneName }}</text>
|
|
|
- <text class="scene-desc">{{ s.description }}</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 成员选择 -->
|
|
|
- <view v-else-if="!memberId" class="pick-wrap">
|
|
|
- <view class="pick-title">选择填写人</view>
|
|
|
- <view class="scene-item" v-for="(m, i) in members" :key="getMemberKey(i)" @click="chooseMember(m)">
|
|
|
- <text class="scene-name">{{ m.nickname || m.name || '成员' }}</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 答题 -->
|
|
|
- <view v-else class="qa-wrap">
|
|
|
- <view class="qa-header">
|
|
|
- <text class="qa-progress">已答 {{ answeredCount }} / {{ maxQuestions || '-' }} 题</text>
|
|
|
- <text class="qa-finish" @click="onFinishEarly">结束并生成画像</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view v-if="loading" class="qa-loading">
|
|
|
- <text class="qa-loading-text">正在思考下一题...</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view v-else-if="currentQuestion" class="qa-card">
|
|
|
- <text class="qa-text">{{ currentQuestion.text }}</text>
|
|
|
-
|
|
|
- <!-- 单选/多选 -->
|
|
|
- <view v-if="currentQuestion.type === 'single' || currentQuestion.type === 'multi'" class="qa-options">
|
|
|
- <view class="qa-option" v-for="(opt, oi) in options" :key="getOptKey(oi)"
|
|
|
- :class="{ selected: isSelected(opt.id) }" @click="toggleOption(opt)">
|
|
|
- <text class="qa-option-label">{{ opt.label }}</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 量表 -->
|
|
|
- <view v-if="currentQuestion.type === 'scale' && currentQuestion.scale" class="qa-scale">
|
|
|
- <text class="qa-scale-label">{{ currentQuestion.scale.minLabel }}</text>
|
|
|
- <slider :min="currentQuestion.scale.min" :max="currentQuestion.scale.max" :value="scaleValue"
|
|
|
- activeColor="#F97316" @change="onScaleChange" class="qa-slider" />
|
|
|
- <text class="qa-scale-label">{{ currentQuestion.scale.maxLabel }}</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 文本 -->
|
|
|
- <view v-if="currentQuestion.type === 'text'" class="qa-textarea-wrap">
|
|
|
- <textarea v-model="textAnswer" class="qa-textarea" placeholder="请输入您的回答" />
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view v-if="!loading && currentQuestion" class="qa-submit">
|
|
|
- <button class="btn-next" :disabled="!canSubmit" @click="onAnswer">回答并继续</button>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import { aiQSceneList, aiQStart, aiQAnswer, aiQFinish, getFamilyMemberList } from '../../utils/api.js'
|
|
|
-
|
|
|
-export default {
|
|
|
- data() {
|
|
|
- return {
|
|
|
- scenes: [],
|
|
|
- members: [],
|
|
|
- sceneId: null,
|
|
|
- sceneName: '',
|
|
|
- memberId: null,
|
|
|
- sessionId: null,
|
|
|
- currentQuestion: null,
|
|
|
- options: [],
|
|
|
- selected: {},
|
|
|
- scaleValue: 5,
|
|
|
- textAnswer: '',
|
|
|
- answeredCount: 0,
|
|
|
- maxQuestions: 12,
|
|
|
- loading: false
|
|
|
- }
|
|
|
- },
|
|
|
- computed: {
|
|
|
- canSubmit: function() {
|
|
|
- var q = this.currentQuestion
|
|
|
- if (!q) return false
|
|
|
- if (q.type === 'text') return this.textAnswer && this.textAnswer.trim().length > 0
|
|
|
- if (q.type === 'scale') return true
|
|
|
- if (q.type === 'multi') {
|
|
|
- for (var k in this.selected) { if (this.selected[k]) return true }
|
|
|
- return false
|
|
|
- }
|
|
|
- return this.selected[q.id || 'k'] === true
|
|
|
- }
|
|
|
- },
|
|
|
- onLoad: function() {
|
|
|
- this.loadScenes()
|
|
|
- this.loadMembers()
|
|
|
- },
|
|
|
- methods: {
|
|
|
- getSceneKey: function(i) { return 'scene' + i },
|
|
|
- getMemberKey: function(i) { return 'mem' + i },
|
|
|
- getOptKey: function(i) { return 'opt' + i },
|
|
|
- async loadScenes() {
|
|
|
- try {
|
|
|
- var res = await aiQSceneList({ enabledOnly: true })
|
|
|
- if (res.code === 200) {
|
|
|
- this.scenes = (res.data && res.data.items) || res.data || []
|
|
|
- } else {
|
|
|
- uni.showToast({ title: res.message || '加载场景失败', icon: 'none' })
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: '加载场景失败', icon: 'none' })
|
|
|
- }
|
|
|
- },
|
|
|
- loadMembers: function() {
|
|
|
- var self = this
|
|
|
- getFamilyMemberList({ visibleOnly: true }).then(function(res) {
|
|
|
- if (res && res.code === 200 && res.data) {
|
|
|
- var list = res.data instanceof Array ? res.data : []
|
|
|
- self.members = list.filter(function(m) { return m.id })
|
|
|
- }
|
|
|
- }).catch(function() { self.members = [] })
|
|
|
- },
|
|
|
- chooseScene: function(s) {
|
|
|
- this.sceneId = s.id
|
|
|
- this.sceneName = s.sceneName
|
|
|
- this.maxQuestions = s.maxQuestions || 12
|
|
|
- this.startQuestionnaire()
|
|
|
- },
|
|
|
- chooseMember: function(m) {
|
|
|
- this.memberId = m.id
|
|
|
- this.startQuestionnaire()
|
|
|
- },
|
|
|
- async startQuestionnaire() {
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- var res = await aiQStart({ sceneId: this.sceneId, memberId: this.memberId })
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- this.sessionId = res.data.sessionId
|
|
|
- this.currentQuestion = res.data.question
|
|
|
- this.answeredCount = res.data.answeredCount || 0
|
|
|
- this.resetAnswer()
|
|
|
- } else {
|
|
|
- uni.showToast({ title: res.message || '开始失败', icon: 'none' })
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: '开始失败', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- isSelected: function(optId) {
|
|
|
- return this.selected[optId] === true
|
|
|
- },
|
|
|
- toggleOption: function(opt) {
|
|
|
- if (this.currentQuestion.type === 'single') {
|
|
|
- var single = {}
|
|
|
- single[opt.id] = true
|
|
|
- this.selected = single
|
|
|
- } else {
|
|
|
- this.selected[opt.id] = !this.selected[opt.id]
|
|
|
- }
|
|
|
- },
|
|
|
- onScaleChange: function(e) {
|
|
|
- this.scaleValue = e.detail.value
|
|
|
- },
|
|
|
- resetAnswer: function() {
|
|
|
- this.selected = {}
|
|
|
- this.textAnswer = ''
|
|
|
- this.scaleValue = 5
|
|
|
- if (this.currentQuestion && this.currentQuestion.options) {
|
|
|
- this.options = this.currentQuestion.options
|
|
|
- } else {
|
|
|
- this.options = []
|
|
|
- }
|
|
|
- },
|
|
|
- buildAnswer: function() {
|
|
|
- var q = this.currentQuestion
|
|
|
- if (!q) return ''
|
|
|
- if (q.type === 'text') return this.textAnswer.trim()
|
|
|
- if (q.type === 'scale') return String(this.scaleValue)
|
|
|
- var labels = []
|
|
|
- if (q.type === 'single') {
|
|
|
- for (var k in this.selected) {
|
|
|
- if (this.selected[k]) {
|
|
|
- var opt = this.options.find(function(o) { return o.id === k })
|
|
|
- labels.push(opt ? opt.label : k)
|
|
|
- }
|
|
|
- }
|
|
|
- return labels[0] || ''
|
|
|
- }
|
|
|
- var multiLabels = []
|
|
|
- for (var k2 in this.selected) {
|
|
|
- if (this.selected[k2]) {
|
|
|
- var opt2 = this.options.find(function(o) { return o.id === k2 })
|
|
|
- multiLabels.push(opt2 ? opt2.label : k2)
|
|
|
- }
|
|
|
- }
|
|
|
- return multiLabels.join(',')
|
|
|
- },
|
|
|
- async onAnswer() {
|
|
|
- var answer = this.buildAnswer()
|
|
|
- if (!answer) return
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- var res = await aiQAnswer({ sessionId: this.sessionId, answer: answer })
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- if (res.data.action === 'finish' || res.data.finished) {
|
|
|
- this.goResult(res.data.profile || {})
|
|
|
- } else {
|
|
|
- this.currentQuestion = res.data.question
|
|
|
- this.answeredCount = res.data.answeredCount || 0
|
|
|
- this.resetAnswer()
|
|
|
- }
|
|
|
- } else {
|
|
|
- uni.showToast({ title: res.message || '提交失败', icon: 'none' })
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- onFinishEarly: function() {
|
|
|
- var self = this
|
|
|
- uni.showModal({
|
|
|
- title: '结束问卷',
|
|
|
- content: '将基于当前回答生成画像,确定结束吗?',
|
|
|
- success: function(r) {
|
|
|
- if (r.confirm) self.finishQuestionnaire()
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- async finishQuestionnaire() {
|
|
|
- this.loading = true
|
|
|
- try {
|
|
|
- var res = await aiQFinish({ sessionId: this.sessionId })
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- this.goResult(res.data)
|
|
|
- } else {
|
|
|
- uni.showToast({ title: res.message || '生成失败', icon: 'none' })
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- goResult: function(profile) {
|
|
|
- uni.redirectTo({
|
|
|
- url: '/pages/health/ai-questionnaire-result?profile=' + encodeURIComponent(JSON.stringify(profile)) +
|
|
|
- '&sessionId=' + (this.sessionId || '')
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
-.container { min-height: 100vh; background: #f5f5f5; padding: 24rpx; }
|
|
|
-.pick-wrap { padding: 40rpx 0; }
|
|
|
-.pick-title { font-size: 34rpx; font-weight: bold; color: #333; margin-bottom: 24rpx; }
|
|
|
-.scene-item { background: #fff; border-radius: 16rpx; padding: 28rpx; margin-bottom: 20rpx; }
|
|
|
-.scene-name { font-size: 30rpx; color: #333; font-weight: bold; display: block; }
|
|
|
-.scene-desc { font-size: 26rpx; color: #999; margin-top: 8rpx; display: block; }
|
|
|
-.qa-header { display: flex; justify-content: space-between; align-items: center; padding: 10rpx 4rpx 20rpx; }
|
|
|
-.qa-progress { font-size: 26rpx; color: #999; }
|
|
|
-.qa-finish { font-size: 26rpx; color: #F97316; }
|
|
|
-.qa-loading { text-align: center; padding: 120rpx 0; }
|
|
|
-.qa-loading-text { font-size: 28rpx; color: #999; }
|
|
|
-.qa-card { background: #fff; border-radius: 16rpx; padding: 32rpx; }
|
|
|
-.qa-text { font-size: 32rpx; color: #333; line-height: 1.6; display: block; }
|
|
|
-.qa-option { padding: 22rpx; border: 2rpx solid #e0e0e0; border-radius: 12rpx; margin-top: 20rpx;
|
|
|
- background: #fafafa; }
|
|
|
-.qa-option.selected { border-color: #F97316; background: #FFF7ED; }
|
|
|
-.qa-option-label { font-size: 28rpx; color: #333; }
|
|
|
-.qa-scale { display: flex; align-items: center; margin-top: 30rpx; }
|
|
|
-.qa-scale-label { font-size: 24rpx; color: #666; width: 80rpx; }
|
|
|
-.qa-slider { flex: 1; margin: 0 12rpx; }
|
|
|
-.qa-textarea-wrap { margin-top: 24rpx; }
|
|
|
-.qa-textarea { width: 100%; height: 200rpx; background: #fafafa; border: 2rpx solid #e0e0e0;
|
|
|
- border-radius: 12rpx; padding: 16rpx; font-size: 28rpx; box-sizing: border-box; }
|
|
|
-.qa-submit { margin-top: 30rpx; }
|
|
|
-.btn-next { background: #F97316; color: #fff; border-radius: 12rpx; }
|
|
|
-.btn-next[disabled] { background: #ccc; }
|
|
|
-</style>
|