Răsfoiți Sursa

refactor(frontend): 删除13个意图/旅程/问卷页面与组件

删除 intent-picker/journey-card 组件,ai-questionnaire/result 问卷页,8个pages/problem C类页面,profile/components/ProfileMenu死文件

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
liaoxg 2 luni în urmă
părinte
comite
7ec16336fd

+ 0 - 140
cfc-frontend/components/intent-picker.vue

@@ -1,140 +0,0 @@
-<template>
-  <view class="ip-mask" v-if="show" @click="onMaskTap">
-    <view class="ip-panel" @click.stop>
-      <view class="ip-header">
-        <text class="ip-title">花一分钟,做个小测试</text>
-        <text class="ip-sub">回答几个问题,看看我能为您做点儿什么</text>
-      </view>
-
-      <!-- 开始测试 -->
-      <view class="ip-card" @click="onStart">
-        <view class="ip-card-icon" style="background:rgba(249,115,22,0.12)">
-          <text class="ip-card-emoji">✨</text>
-        </view>
-        <view class="ip-card-body">
-          <text class="ip-card-title">开始测试</text>
-          <text class="ip-card-desc">3分钟了解你的家庭健康需求,生成专属成长方案</text>
-        </view>
-        <text class="ip-card-arrow">›</text>
-      </view>
-
-      <!-- 以后再说 -->
-      <view class="ip-skip" @click="onSkip">
-        <text class="ip-skip-text">{{ skipHint }}</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { skipUserIntent } from '../utils/api.js'
-
-export default {
-  name: 'IntentPicker',
-  props: {
-    show: {
-      type: Boolean,
-      default: false
-    }
-  },
-  data() {
-    return {
-      skipping: false
-    }
-  },
-  computed: {
-    skipHint() {
-      var count = parseInt(uni.getStorageSync('intent_skip_count') || '0')
-      if (count >= 3) return ''
-      if (count > 0) return '以后再说(' + (3 - count) + '次机会)'
-      return '以后再说'
-    }
-  },
-  methods: {
-    onMaskTap() {},
-    onStart() {
-      this.$emit('select', { intentType: 'B' })
-    },
-    onSkip() {
-      var self = this
-      if (self.skipping) return
-      self.skipping = true
-      skipUserIntent().then(function(res) {
-        self.skipping = false
-        if (res.code === 200 && res.data) {
-          var count = (res.data.skipCount || 0) + 1
-          uni.setStorageSync('intent_skip_count', count)
-          if (count >= 3) {
-            uni.setStorageSync('intent_skip_max', '1')
-          }
-          uni.showToast({ title: '好的,随时可以回来测', icon: 'none' })
-          setTimeout(function() { self.$emit('close') }, 500)
-        } else {
-          self.$emit('close')
-        }
-      }).catch(function() {
-        self.skipping = false
-        self.$emit('close')
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.ip-mask {
-  position: fixed;
-  top: 0; left: 0; right: 0; bottom: 0;
-  background: rgba(0, 0, 0, 0.55);
-  display: flex;
-  align-items: flex-end;
-  justify-content: center;
-  z-index: 9999;
-}
-.ip-panel {
-  width: 100%;
-  background: #FFF7ED;
-  border-radius: 32rpx 32rpx 0 0;
-  padding: 56rpx 40rpx 64rpx;
-  box-sizing: border-box;
-}
-.ip-header { margin-bottom: 36rpx; }
-.ip-title {
-  display: block;
-  font-size: 40rpx; font-weight: bold; color: #333;
-  text-align: center; line-height: 1.3;
-}
-.ip-sub {
-  display: block;
-  font-size: 26rpx; color: #888;
-  text-align: center; margin-top: 12rpx; line-height: 1.5;
-}
-.ip-card {
-  display: flex; align-items: center;
-  background: #fff; border-radius: 24rpx;
-  padding: 32rpx 28rpx;
-  box-shadow: 0 4rpx 20rpx rgba(249,115,22,0.1);
-  border: 2rpx solid rgba(249,115,22,0.15);
-}
-.ip-card:active { opacity: 0.88; }
-.ip-card-icon {
-  width: 88rpx; height: 88rpx;
-  border-radius: 22rpx;
-  display: flex; align-items: center; justify-content: center;
-  margin-right: 24rpx; flex-shrink: 0;
-}
-.ip-card-emoji { font-size: 44rpx; }
-.ip-card-body { flex: 1; min-width: 0; }
-.ip-card-title {
-  display: block;
-  font-size: 30rpx; font-weight: 600; color: #333;
-}
-.ip-card-desc {
-  display: block;
-  font-size: 24rpx; color: #999;
-  margin-top: 6rpx; line-height: 1.4;
-}
-.ip-card-arrow { font-size: 36rpx; color: #F97316; flex-shrink: 0; }
-.ip-skip { text-align: center; margin-top: 28rpx; padding: 12rpx 0; }
-.ip-skip-text { font-size: 26rpx; color: #bbb; }
-</style>

+ 0 - 306
cfc-frontend/components/journey-card.vue

@@ -1,306 +0,0 @@
-<template>
-  <view class="jc-wrap" v-if="intent">
-    <view class="jc-header">
-      <text class="jc-title">{{ domainLabel }}</text>
-      <text class="jc-stage-label">{{ stageLabel }}</text>
-    </view>
-    <view class="jc-progress">
-      <view class="jc-bar">
-        <view class="jc-bar-fill" :style="'width:' + progressPct + '%'"></view>
-      </view>
-      <text class="jc-pct">{{ progressPct }}%</text>
-    </view>
-    <view class="jc-steps" v-if="intentType === 'C'">
-      <view class="jc-step" v-for="(s, i) in cSteps" :key="s.key" :class="{ 'jc-step-done': i <= stage, 'jc-step-cur': i === stage }">
-        <view class="jc-step-dot" :class="{ 'jc-step-dot-done': i <= stage }">
-          <text class="jc-step-num" v-if="i < stage">✓</text>
-          <text class="jc-step-num" v-else>{{ i + 1 }}</text>
-        </view>
-        <text class="jc-step-label">{{ s }}</text>
-      </view>
-    </view>
-    <view class="jc-action" v-if="nextAction && nextAction !== 'done'" @click="onAction">
-      <text class="jc-action-text">{{ nextLabel }}</text>
-      <text class="jc-action-arrow">›</text>
-    </view>
-    <view class="jc-done" v-else-if="nextAction === 'done'">
-      <text class="jc-done-text">✅ 全部完成,奖励已发放!</text>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getProblemDomainList } from '../utils/api.js'
-
-var C_STEPS = ['选问题域', '看流程', '调研', '选购方案', '物流', '出报告', '确认方案', '任务完成']
-var A_STEPS = ['选购', '出报告', '确认方案', '任务完成']
-var B_STEPS = ['问卷', '推荐']
-
-var C_ACTIONS = ['flow-intro', 'survey', 'package', 'logistics', 'report', 'plan-confirm', 'tasks', 'done']
-var A_ACTIONS = ['purchase', 'report', 'plan-confirm', 'tasks', 'done']
-var B_ACTIONS = ['survey', 'recommend', 'done']
-
-var C_LABELS = ['查看服务流程', '填写调研', '选购方案', '查看物流', '等待报告', '确认方案', '查看任务', '已完成']
-var A_LABELS = ['去选购', '查看报告', '确认方案', '查看任务', '已完成']
-var B_LABELS = ['填写问卷', '查看推荐', '已完成']
-
-export default {
-  name: 'JourneyCard',
-  props: {
-    intent: {
-      type: Object,
-      default: null
-    }
-  },
-  data() {
-    return {
-      domainMap: {}
-    }
-  },
-  computed: {
-    intentType() {
-      return this.intent && this.intent.intentType
-    },
-    stage() {
-      return this.intent ? (this.intent.journeyStage || 0) : 0
-    },
-    maxStage() {
-      if (this.intentType === 'C') return 7
-      if (this.intentType === 'A') return 4
-      if (this.intentType === 'B') return 2
-      return 0
-    },
-    progressPct() {
-      if (this.maxStage === 0) return 0
-      return Math.round((this.stage / this.maxStage) * 100)
-    },
-    steps() {
-      if (this.intentType === 'C') return C_STEPS
-      if (this.intentType === 'A') return A_STEPS
-      if (this.intentType === 'B') return B_STEPS
-      return []
-    },
-    actions() {
-      if (this.intentType === 'C') return C_ACTIONS
-      if (this.intentType === 'A') return A_ACTIONS
-      if (this.intentType === 'B') return B_ACTIONS
-      return []
-    },
-    labels() {
-      if (this.intentType === 'C') return C_LABELS
-      if (this.intentType === 'A') return A_LABELS
-      if (this.intentType === 'B') return B_LABELS
-      return []
-    },
-    cSteps() {
-      if (this.intentType !== 'C') return []
-      var sliced = this.steps.slice(0, Math.max(this.stage + 1, 3))
-      return sliced.map(function(s, i) {
-        return { key: 's' + i, label: s }
-      })
-    },
-    stageLabel() {
-      var idx = Math.min(this.stage, this.steps.length - 1)
-      if (this.stage >= this.steps.length) return '已完成'
-      return '第' + (this.stage + 1) + '步:' + this.steps[idx]
-    },
-    nextAction() {
-      var idx = Math.min(this.stage, this.actions.length - 1)
-      return this.actions[idx] || 'done'
-    },
-    nextLabel() {
-      var idx = Math.min(this.stage, this.labels.length - 1)
-      return this.labels[idx] || '已完成'
-    },
-    domainLabel() {
-      var code = this.intent && this.intent.problemDomainCode
-      if (code && this.domainMap[code]) {
-        return this.domainMap[code]
-      }
-      if (this.intentType === 'A') return 'A类 · 随便看看'
-      if (this.intentType === 'B') return 'B类 · 问卷'
-      if (this.intentType === 'C') return 'C类 · 解决问题'
-      return ''
-    }
-  },
-  created() {
-    this.loadDomainMap()
-  },
-  methods: {
-    loadDomainMap() {
-      var self = this
-      getProblemDomainList().then(function(res) {
-        var list = res && res.data
-        if (Array.isArray(list)) {
-          var map = {}
-          list.forEach(function(d) {
-            map[d.code] = d.name
-          })
-          self.domainMap = map
-        }
-      }).catch(function() {
-        // 静默失败
-      })
-    },
-    onAction() {
-      this.$emit('action', {
-        action: this.nextAction,
-        code: this.intent && this.intent.problemDomainCode
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.jc-wrap {
-  background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
-  border-radius: 24rpx;
-  padding: 28rpx 30rpx;
-  margin: 20rpx 24rpx 0;
-  box-shadow: 0 4rpx 20rpx rgba(249, 115, 22, 0.12);
-}
-
-.jc-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-
-.jc-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-
-.jc-stage-label {
-  font-size: 22rpx;
-  color: #F97316;
-  background: rgba(249, 115, 22, 0.12);
-  padding: 4rpx 14rpx;
-  border-radius: 20rpx;
-}
-
-.jc-progress {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20rpx;
-}
-
-.jc-bar {
-  flex: 1;
-  height: 12rpx;
-  background: #FED7AA;
-  border-radius: 6rpx;
-  overflow: hidden;
-}
-
-.jc-bar-fill {
-  height: 100%;
-  background: linear-gradient(90deg, #F97316, #FB923C);
-  border-radius: 6rpx;
-  transition: width 0.3s;
-}
-
-.jc-pct {
-  font-size: 22rpx;
-  color: #F97316;
-  margin-left: 12rpx;
-  min-width: 48rpx;
-  text-align: right;
-}
-
-.jc-steps {
-  display: flex;
-  flex-wrap: nowrap;
-  overflow-x: auto;
-  margin-bottom: 20rpx;
-  padding-bottom: 8rpx;
-}
-
-.jc-step {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-right: 24rpx;
-  flex-shrink: 0;
-}
-
-.jc-step:last-child {
-  margin-right: 0;
-}
-
-.jc-step-dot {
-  width: 36rpx;
-  height: 36rpx;
-  border-radius: 50%;
-  background: #FED7AA;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 8rpx;
-}
-
-.jc-step-dot-done {
-  background: #F97316;
-}
-
-.jc-step-num {
-  font-size: 20rpx;
-  color: #fff;
-  font-weight: bold;
-}
-
-.jc-step-label {
-  font-size: 20rpx;
-  color: #999;
-  white-space: nowrap;
-}
-
-.jc-step-cur .jc-step-label {
-  color: #F97316;
-  font-weight: 600;
-}
-
-.jc-step-done .jc-step-label {
-  color: #666;
-}
-
-.jc-action {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: #F97316;
-  border-radius: 40rpx;
-  padding: 22rpx 0;
-  margin-top: 8rpx;
-}
-
-.jc-action:active {
-  opacity: 0.9;
-}
-
-.jc-action-text {
-  color: #fff;
-  font-size: 30rpx;
-  font-weight: 600;
-}
-
-.jc-action-arrow {
-  color: #fff;
-  font-size: 36rpx;
-  margin-left: 8rpx;
-}
-
-.jc-done {
-  text-align: center;
-  padding: 16rpx 0;
-  margin-top: 8rpx;
-}
-
-.jc-done-text {
-  font-size: 28rpx;
-  color: #16A34A;
-}
-</style>

+ 0 - 103
cfc-frontend/pages/health/ai-questionnaire-result.vue

@@ -1,103 +0,0 @@
-<template>
-  <view class="container">
-    <view class="result-header">
-      <text class="result-title">问卷画像报告</text>
-    </view>
-
-    <!-- 用户画像 -->
-    <view class="section">
-      <text class="section-title">用户画像</text>
-      <view class="profile-item" v-for="(p, i) in userProfile" :key="getUpKey(i)">
-        <view class="profile-head">
-          <text class="profile-dim">{{ p.dimension }}</text>
-          <text class="profile-score">{{ p.score }} 分</text>
-        </view>
-        <view class="score-bar-bg">
-          <view class="score-bar-fill" :style="'width:' + clamp(p.score) + '%'"></view>
-        </view>
-        <text class="profile-desc">{{ p.description }}</text>
-        <view class="evidence-list" v-if="p.evidence && p.evidence.length > 0">
-          <text class="evidence-item" v-for="(ev, ei) in p.evidence" :key="getEvKey(ei)">· {{ ev }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 需求画像 -->
-    <view class="section" v-if="needProfile.length > 0">
-      <text class="section-title">需求画像</text>
-      <view class="profile-item" v-for="(n, i) in needProfile" :key="getNpKey(i)">
-        <text class="profile-dim">{{ n.dimension }}</text>
-        <text class="profile-desc">{{ n.description }}</text>
-        <text class="profile-suggest" v-if="n.suggestion">建议:{{ n.suggestion }}</text>
-      </view>
-    </view>
-
-    <view class="footer-btns">
-      <button class="btn-recommend" @click="goRecommend">查看相关推荐</button>
-      <button class="btn-again" @click="goHome">完成</button>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  data() {
-    return {
-      sessionId: '',
-      userProfile: [],
-      needProfile: []
-    }
-  },
-  onLoad: function(options) {
-    this.sessionId = options.sessionId || ''
-    if (options.profile) {
-      try {
-        var profile = JSON.parse(decodeURIComponent(options.profile))
-        this.userProfile = profile.userProfile || profile.user_profile || []
-        this.needProfile = profile.needProfile || profile.need_profile || []
-      } catch (e) {
-        this.userProfile = []
-        this.needProfile = []
-      }
-    }
-  },
-  methods: {
-    getUpKey: function(i) { return 'up' + i },
-    getEvKey: function(i) { return 'ev' + i },
-    getNpKey: function(i) { return 'np' + i },
-    clamp: function(score) {
-      var s = Number(score) || 0
-      if (s < 0) return 0
-      if (s > 100) return 100
-      return s
-    },
-    goRecommend: function() {
-      uni.navigateTo({ url: '/pages/diet/index?source=ai-questionnaire' })
-    },
-    goHome: function() {
-      uni.switchTab({ url: '/pages/body/index' })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container { min-height: 100vh; background: #f5f5f5; padding: 24rpx; }
-.result-header { padding: 20rpx 0 30rpx; }
-.result-title { font-size: 38rpx; font-weight: bold; color: #333; }
-.section { margin-bottom: 30rpx; }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; display: block; margin-bottom: 16rpx; }
-.profile-item { background: #fff; border-radius: 16rpx; padding: 28rpx; margin-bottom: 16rpx; }
-.profile-head { display: flex; justify-content: space-between; align-items: center; }
-.profile-dim { font-size: 28rpx; color: #333; font-weight: bold; }
-.profile-score { font-size: 26rpx; color: #F97316; }
-.score-bar-bg { height: 14rpx; background: #f0f0f0; border-radius: 7rpx; margin-top: 14rpx; overflow: hidden; }
-.score-bar-fill { height: 100%; background: #F97316; border-radius: 7rpx; }
-.profile-desc { font-size: 26rpx; color: #666; line-height: 1.6; margin-top: 14rpx; display: block; }
-.profile-suggest { font-size: 26rpx; color: #0EA5E9; margin-top: 10rpx; display: block; }
-.evidence-list { margin-top: 10rpx; }
-.evidence-item { font-size: 24rpx; color: #999; display: block; margin-top: 4rpx; }
-.footer-btns { margin-top: 20rpx; }
-.btn-recommend { background: #F97316; color: #fff; border-radius: 12rpx; }
-.btn-again { background: #fff; color: #666; border-radius: 12rpx; margin-top: 16rpx; }
-</style>

+ 0 - 289
cfc-frontend/pages/health/ai-questionnaire.vue

@@ -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>

+ 0 - 158
cfc-frontend/pages/problem/domain-list.vue

@@ -1,158 +0,0 @@
-<template>
-  <view class="dl-wrap">
-    <view class="dl-header">
-      <text class="dl-title">你关心什么问题?</text>
-      <text class="dl-sub">选择一个你关注的领域,获取定制解决方案</text>
-    </view>
-    <view class="dl-list" v-if="loading">
-      <view class="dl-loading">加载中...</view>
-    </view>
-    <view class="dl-list" v-else-if="domains.length === 0">
-      <view class="dl-empty">暂无可用问题域</view>
-    </view>
-    <view class="dl-list" v-else>
-      <view class="dl-card" v-for="d in domains" :key="getKey(d)" @click="onSelect(d)">
-        <view class="dl-card-icon">
-          <text class="dl-card-emoji">{{ d.icon || '🧭' }}</text>
-        </view>
-        <view class="dl-card-body">
-          <text class="dl-card-name">{{ d.name }}</text>
-          <text class="dl-card-desc">{{ d.description || '了解详情,获取专属方案' }}</text>
-        </view>
-        <text class="dl-card-arrow">›</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getProblemDomainList } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      domains: [],
-      loading: false
-    }
-  },
-  onLoad() {
-    this.loadDomains()
-  },
-  methods: {
-    getKey(d) {
-      return d.id || d.code
-    },
-    loadDomains() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      getProblemDomainList().then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var list = res && res.data
-        self.domains = Array.isArray(list) ? list : []
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.domains = []
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      })
-    },
-    onSelect(d) {
-      uni.navigateTo({
-        url: '/pages/problem/flow-intro?code=' + (d.code || '')
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.dl-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx;
-  box-sizing: border-box;
-}
-
-.dl-header {
-  margin-bottom: 40rpx;
-}
-
-.dl-title {
-  display: block;
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.dl-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 12rpx;
-}
-
-.dl-loading, .dl-empty {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.dl-card {
-  display: flex;
-  align-items: center;
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 32rpx 30rpx;
-  margin-bottom: 24rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
-}
-
-.dl-card:active {
-  transform: scale(0.98);
-}
-
-.dl-card-icon {
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 24rpx;
-  background: rgba(249, 115, 22, 0.1);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 24rpx;
-  flex-shrink: 0;
-}
-
-.dl-card-emoji {
-  font-size: 48rpx;
-}
-
-.dl-card-body {
-  flex: 1;
-}
-
-.dl-card-name {
-  display: block;
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #333;
-}
-
-.dl-card-desc {
-  display: block;
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 8rpx;
-}
-
-.dl-card-arrow {
-  font-size: 48rpx;
-  color: #CCC;
-  margin-left: 12rpx;
-}
-</style>

+ 0 - 254
cfc-frontend/pages/problem/flow-intro.vue

@@ -1,254 +0,0 @@
-<template>
-  <view class="fi-wrap">
-    <view class="fi-header">
-      <text class="fi-title">{{ domainName || '服务流程' }}</text>
-      <text class="fi-sub">了解你将获得的完整服务</text>
-    </view>
-
-    <view class="fi-loading" v-if="loading">加载中...</view>
-    <view class="fi-empty" v-else-if="!steps || steps.length === 0">暂无流程信息</view>
-
-    <view class="fi-timeline" v-else>
-      <view class="fi-step" v-for="(s, i) in steps" :key="s.key" :class="{ 'fi-step-last': i === steps.length - 1 }">
-        <view class="fi-step-dot" :class="{ 'fi-step-dot-cur': i <= currentStep }">
-          <text class="fi-step-num">{{ i + 1 }}</text>
-        </view>
-        <view class="fi-step-line" v-if="i < steps.length - 1"></view>
-        <view class="fi-step-body">
-          <text class="fi-step-title">{{ s.title }}</text>
-          <text class="fi-step-desc" v-if="s.desc">{{ s.desc }}</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="fi-btn-wrap" v-if="!loading && steps.length > 0">
-      <view class="fi-btn" @click="goSurvey">
-        <text class="fi-btn-text">开始填写调研</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getProblemDomainList } from '../../utils/api.js'
-import { updateUserIntentStage } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      code: '',
-      domainName: '',
-      steps: [],
-      loading: false,
-      currentStep: 0
-    }
-  },
-  onLoad(options) {
-    var code = options && options.code
-    if (code) {
-      this.code = code
-    }
-    this.loadDomainDetail()
-  },
-  onReady() {
-    this.advanceStage()
-  },
-  methods: {
-    getKey(item, i) {
-      return 's' + i
-    },
-    loadDomainDetail() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      getProblemDomainList().then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var list = res && res.data
-        if (Array.isArray(list)) {
-          var found = null
-          for (var i = 0; i < list.length; i++) {
-            if (list[i].code === self.code) {
-              found = list[i]
-              break
-            }
-          }
-          if (found) {
-            self.domainName = found.name
-            var flowJson = found.flow_intro_json
-            if (flowJson) {
-              var parsed = typeof flowJson === 'string' ? JSON.parse(flowJson) : flowJson
-              self.steps = Array.isArray(parsed) ? parsed.map(function(s, idx) {
-                return { key: 'step' + idx, title: s.title || '第' + (idx + 1) + '步', desc: s.desc || '' }
-              }) : []
-            } else {
-              self.steps = self.getDefaultSteps()
-            }
-            self.surveyTemplateId = found.surveyTemplateId || (found.surveyTemplate && found.surveyTemplate.id) || ''
-          } else {
-            self.steps = self.getDefaultSteps()
-          }
-        } else {
-          self.steps = self.getDefaultSteps()
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.steps = self.getDefaultSteps()
-      })
-    },
-    getDefaultSteps() {
-      var defaultTitles = ['选择问题域', '了解服务流程', '填写专项调研', '选购推荐方案', '接收试剂盒', '获取报告', '确认方案并执行']
-      return defaultTitles.map(function(t, i) {
-        return { key: 'step' + i, title: t, desc: '' }
-      })
-    },
-    advanceStage() {
-      updateUserIntentStage(1).then(function() {
-        // 静默推进
-      }).catch(function() {
-        // 静默
-      })
-    },
-    goSurvey() {
-      var url = '/pages/problem/survey?code=' + this.code
-      if (this.surveyTemplateId) {
-        url += '&templateId=' + this.surveyTemplateId
-      }
-      uni.navigateTo({ url: url })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.fi-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx 120rpx;
-  box-sizing: border-box;
-}
-
-.fi-header {
-  margin-bottom: 40rpx;
-}
-
-.fi-title {
-  display: block;
-  font-size: 38rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.fi-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 10rpx;
-}
-
-.fi-loading, .fi-empty {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.fi-timeline {
-  padding-left: 30rpx;
-}
-
-.fi-step {
-  position: relative;
-  padding-left: 60rpx;
-  padding-bottom: 40rpx;
-}
-
-.fi-step-last {
-  padding-bottom: 0;
-}
-
-.fi-step-dot {
-  position: absolute;
-  left: 0;
-  top: 0;
-  width: 44rpx;
-  height: 44rpx;
-  border-radius: 50%;
-  background: #FED7AA;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 2;
-}
-
-.fi-step-dot-cur {
-  background: #F97316;
-}
-
-.fi-step-num {
-  font-size: 22rpx;
-  color: #fff;
-  font-weight: bold;
-}
-
-.fi-step-line {
-  position: absolute;
-  left: 20rpx;
-  top: 44rpx;
-  width: 4rpx;
-  height: 100%;
-  background: #FED7AA;
-  z-index: 1;
-}
-
-.fi-step-body {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx 28rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
-}
-
-.fi-step-title {
-  display: block;
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333;
-}
-
-.fi-step-desc {
-  display: block;
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 8rpx;
-}
-
-.fi-btn-wrap {
-  position: fixed;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  padding: 20rpx 32rpx 40rpx;
-  background: #FFF7ED;
-  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
-}
-
-.fi-btn {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  border-radius: 48rpx;
-  padding: 26rpx 0;
-  text-align: center;
-}
-
-.fi-btn:active {
-  opacity: 0.9;
-}
-
-.fi-btn-text {
-  color: #fff;
-  font-size: 32rpx;
-  font-weight: 600;
-}
-</style>

+ 0 - 245
cfc-frontend/pages/problem/logistics.vue

@@ -1,245 +0,0 @@
-<template>
-  <view class="lg-wrap">
-    <view class="lg-header">
-      <text class="lg-title">物流进度</text>
-      <text class="lg-sub">查看试剂盒寄送与样本回收状态</text>
-    </view>
-
-    <view class="lg-loading" v-if="loading">加载中...</view>
-
-    <view class="lg-empty" v-else-if="status === 'none'">
-      <text class="lg-empty-icon">📦</text>
-      <text class="lg-empty-text">等待商家发货</text>
-      <text class="lg-empty-sub">商家正在准备试剂盒,请耐心等待</text>
-    </view>
-
-    <view class="lg-timeline" v-else>
-      <view class="lg-step" :class="{ 'lg-step-active': stepIndex >= 0 }">
-        <view class="lg-step-dot"></view>
-        <view class="lg-step-body">
-          <text class="lg-step-title">试剂盒已寄出</text>
-          <text class="lg-step-time" v-if="status === 'kit_shipped'">已发货,请留意查收</text>
-          <text class="lg-step-tracking" v-if="trackingNo">快递单号:{{ trackingNo }}</text>
-        </view>
-      </view>
-      <view class="lg-step" :class="{ 'lg-step-active': stepIndex >= 1 }">
-        <view class="lg-step-dot"></view>
-        <view class="lg-step-body">
-          <text class="lg-step-title">试剂盒已收到</text>
-          <text class="lg-step-time" v-if="status === 'kit_received'">已签收</text>
-        </view>
-      </view>
-      <view class="lg-step" :class="{ 'lg-step-active': stepIndex >= 2 }">
-        <view class="lg-step-dot"></view>
-        <view class="lg-step-body">
-          <text class="lg-step-title">样本已寄回</text>
-          <text class="lg-step-time" v-if="status === 'sample_shipped'">正在返回实验室</text>
-        </view>
-      </view>
-      <view class="lg-step" :class="{ 'lg-step-active': stepIndex >= 3 }">
-        <view class="lg-step-dot"></view>
-        <view class="lg-step-body">
-          <text class="lg-step-title">样本已签收</text>
-          <text class="lg-step-time" v-if="status === 'sample_received'">实验室已收到样本,等待报告生成</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="lg-refresh" @click="refresh">
-      <text class="lg-refresh-text">刷新状态</text>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getMyLogistics } from '../../utils/api.js'
-import { updateUserIntentStage } from '../../utils/api.js'
-
-var STATUS_ORDER = ['kit_shipped', 'kit_received', 'sample_shipped', 'sample_received']
-
-export default {
-  data() {
-    return {
-      code: '',
-      status: 'none',
-      trackingNo: '',
-      loading: false
-    }
-  },
-  computed: {
-    stepIndex() {
-      var idx = STATUS_ORDER.indexOf(this.status)
-      return idx
-    }
-  },
-  onLoad(options) {
-    this.code = (options && options.code) || ''
-    this.loadStatus()
-  },
-  onShow() {
-    this.loadStatus()
-  },
-  methods: {
-    loadStatus() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      getMyLogistics().then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var data = res && res.data
-        if (data) {
-          self.status = data.logisticsStatus || data.logistics_status || 'none'
-          self.trackingNo = data.trackingNo || data.tracking_no || ''
-          // 如果已签收样本,推进旅程
-          if (self.status === 'sample_received') {
-            updateUserIntentStage(4).then(function() {}).catch(function() {})
-          }
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.status = 'none'
-      })
-    },
-    refresh() {
-      this.loadStatus()
-    }
-  }
-}
-</script>
-
-<style scoped>
-.lg-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx;
-  box-sizing: border-box;
-}
-
-.lg-header {
-  margin-bottom: 40rpx;
-}
-
-.lg-title {
-  display: block;
-  font-size: 38rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.lg-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 10rpx;
-}
-
-.lg-loading {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.lg-empty {
-  text-align: center;
-  padding: 80rpx 0;
-}
-
-.lg-empty-icon {
-  font-size: 80rpx;
-  display: block;
-}
-
-.lg-empty-text {
-  display: block;
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #333;
-  margin-top: 20rpx;
-}
-
-.lg-empty-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  margin-top: 10rpx;
-}
-
-.lg-timeline {
-  padding-left: 30rpx;
-}
-
-.lg-step {
-  position: relative;
-  padding-left: 60rpx;
-  padding-bottom: 40rpx;
-  opacity: 0.5;
-}
-
-.lg-step-active {
-  opacity: 1;
-}
-
-.lg-step:last-child {
-  padding-bottom: 0;
-}
-
-.lg-step-dot {
-  position: absolute;
-  left: 0;
-  top: 4rpx;
-  width: 32rpx;
-  height: 32rpx;
-  border-radius: 50%;
-  background: #FED7AA;
-  z-index: 2;
-}
-
-.lg-step-active .lg-step-dot {
-  background: #F97316;
-}
-
-.lg-step-body {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx 28rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
-}
-
-.lg-step-title {
-  display: block;
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333;
-}
-
-.lg-step-time {
-  display: block;
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 6rpx;
-}
-
-.lg-step-tracking {
-  display: block;
-  font-size: 24rpx;
-  color: #F97316;
-  margin-top: 6rpx;
-}
-
-.lg-refresh {
-  text-align: center;
-  margin-top: 40rpx;
-  padding: 20rpx 0;
-}
-
-.lg-refresh-text {
-  font-size: 28rpx;
-  color: #F97316;
-  text-decoration: underline;
-}
-</style>

+ 0 - 275
cfc-frontend/pages/problem/package-detail.vue

@@ -1,275 +0,0 @@
-<template>
-  <view class="pd-wrap">
-    <view class="pd-loading" v-if="loading">加载中...</view>
-    <view class="pd-empty" v-else-if="!pkg">暂无套餐信息</view>
-
-    <template v-else>
-      <view class="pd-hero">
-        <text class="pd-hero-name">{{ pkg.name }}</text>
-        <text class="pd-hero-price" v-if="pkg.price">¥{{ pkg.price }}</text>
-        <text class="pd-hero-meta" v-if="pkg.validityDays">有效期 {{ pkg.validityDays }} 天</text>
-      </view>
-
-      <view class="pd-section">
-        <text class="pd-section-title">方案介绍</text>
-        <text class="pd-section-text">{{ pkg.description || '暂无详细介绍' }}</text>
-      </view>
-
-      <view class="pd-section" v-if="pkg.usageGuide">
-        <text class="pd-section-title">使用方式</text>
-        <text class="pd-section-text">{{ pkg.usageGuide }}</text>
-      </view>
-
-      <view class="pd-section" v-if="includedItems && includedItems.length > 0">
-        <text class="pd-section-title">包含项目</text>
-        <view class="pd-item" v-for="(item, i) in includedItems" :key="getIncKey(i)">
-          <text class="pd-item-icon">•</text>
-          <text class="pd-item-text">{{ item }}</text>
-        </view>
-      </view>
-
-      <view class="pd-section" v-if="pkg.rewardCfValue">
-        <text class="pd-section-title">完成奖励</text>
-        <text class="pd-section-text">全部任务完成后可获得 {{ pkg.rewardCfValue }} CF 值奖励</text>
-      </view>
-    </template>
-
-    <view class="pd-buy-bar" v-if="!loading && pkg">
-      <view class="pd-buy-price">
-        <text class="pd-buy-amount" v-if="pkg.price">¥{{ pkg.price }}</text>
-        <text class="pd-buy-free" v-else>免费</text>
-      </view>
-      <view class="pd-buy-btn" @click="buyNow">
-        <text class="pd-buy-btn-text">立即购买</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getProblemPackageDetail } from '../../utils/api.js'
-import { updateUserIntentStage } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      packageId: '',
-      code: '',
-      pkg: null,
-      includedItems: [],
-      loading: false,
-      buying: false
-    }
-  },
-  onLoad(options) {
-    this.packageId = (options && options.packageId) || ''
-    this.code = (options && options.code) || ''
-    this.loadDetail()
-  },
-  methods: {
-    getIncKey: function(i) { return 'i' + i },
-    loadDetail() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      getProblemPackageDetail(self.packageId).then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var data = res && res.data
-        if (data) {
-          self.pkg = data
-          // 解析包含项目
-          var items = data.includedItems || data.included_items || data.contains || []
-          self.includedItems = Array.isArray(items) ? items : []
-        } else {
-          self.pkg = self.getFallbackPackage()
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.pkg = self.getFallbackPackage()
-      })
-    },
-    getFallbackPackage() {
-      return {
-        id: this.packageId || 0,
-        name: '方案套餐',
-        description: '包含检测、评估与个性化方案',
-        price: 199,
-        validityDays: 30,
-        rewardCfValue: 500
-      }
-    },
-    buyNow() {
-      var self = this
-      if (self.buying) return
-      self.buying = true
-      uni.showLoading({ title: '处理中...', mask: true })
-      // 调用创建订单接口
-      var api = require('../../utils/api.js')
-      var createOrder = api.createPackageOrder || api.buyPackage
-      if (createOrder) {
-        createOrder({ packageId: self.packageId }).then(function(res) {
-          uni.hideLoading()
-          self.buying = false
-          if (res && (res.code === 0 || res.code === 200)) {
-            uni.showToast({ title: '购买成功', icon: 'success' })
-            // 推进旅程
-            updateUserIntentStage(3).then(function() {}).catch(function() {})
-            setTimeout(function() {
-              uni.navigateTo({
-                url: '/pages/problem/logistics?code=' + self.code
-              })
-            }, 500)
-          } else {
-            uni.showToast({ title: res.message || '购买失败', icon: 'none' })
-          }
-        }).catch(function() {
-          uni.hideLoading()
-          self.buying = false
-          uni.showToast({ title: '购买失败,请重试', icon: 'none' })
-        })
-      } else {
-        // 无创建订单接口,模拟购买
-        uni.hideLoading()
-        self.buying = false
-        uni.showToast({ title: '购买成功', icon: 'success' })
-        updateUserIntentStage(3).then(function() {}).catch(function() {})
-        setTimeout(function() {
-          uni.navigateTo({
-            url: '/pages/problem/logistics?code=' + self.code
-          })
-        }, 500)
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.pd-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 0 0 160rpx;
-  box-sizing: border-box;
-}
-
-.pd-loading, .pd-empty {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.pd-hero {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  padding: 60rpx 40rpx;
-  text-align: center;
-}
-
-.pd-hero-name {
-  display: block;
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #fff;
-}
-
-.pd-hero-price {
-  display: block;
-  font-size: 52rpx;
-  font-weight: bold;
-  color: #fff;
-  margin-top: 16rpx;
-}
-
-.pd-hero-meta {
-  display: block;
-  font-size: 24rpx;
-  color: rgba(255, 255, 255, 0.8);
-  margin-top: 8rpx;
-}
-
-.pd-section {
-  margin: 24rpx 32rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 28rpx 30rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
-}
-
-.pd-section-title {
-  display: block;
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-  margin-bottom: 16rpx;
-}
-
-.pd-section-text {
-  display: block;
-  font-size: 26rpx;
-  color: #666;
-  line-height: 1.6;
-}
-
-.pd-item {
-  display: flex;
-  align-items: flex-start;
-  margin-bottom: 12rpx;
-}
-
-.pd-item-icon {
-  font-size: 28rpx;
-  color: #F97316;
-  margin-right: 12rpx;
-}
-
-.pd-item-text {
-  font-size: 26rpx;
-  color: #666;
-  flex: 1;
-}
-
-.pd-buy-bar {
-  position: fixed;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  align-items: center;
-  background: #fff;
-  padding: 20rpx 32rpx 40rpx;
-  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
-}
-
-.pd-buy-price {
-  flex: 1;
-}
-
-.pd-buy-amount {
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #F97316;
-}
-
-.pd-buy-free {
-  font-size: 32rpx;
-  color: #16A34A;
-}
-
-.pd-buy-btn {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  border-radius: 48rpx;
-  padding: 22rpx 60rpx;
-}
-
-.pd-buy-btn:active {
-  opacity: 0.9;
-}
-
-.pd-buy-btn-text {
-  color: #fff;
-  font-size: 30rpx;
-  font-weight: 600;
-}
-</style>

+ 0 - 239
cfc-frontend/pages/problem/package-match.vue

@@ -1,239 +0,0 @@
-<template>
-  <view class="pm-wrap">
-    <view class="pm-header">
-      <text class="pm-title">推荐方案</text>
-      <text class="pm-sub">根据你的情况,我们推荐以下方案</text>
-    </view>
-
-    <view class="pm-loading" v-if="loading">加载中...</view>
-    <view class="pm-empty" v-else-if="packages.length === 0">暂无推荐方案</view>
-
-    <view class="pm-list" v-else>
-      <view class="pm-card" v-for="p in packages" :key="getKey(p)">
-        <view class="pm-card-top">
-          <text class="pm-card-name">{{ p.name }}</text>
-          <text class="pm-card-price" v-if="p.price">¥{{ p.price }}</text>
-        </view>
-        <text class="pm-card-desc" v-if="p.description">{{ p.description }}</text>
-        <view class="pm-card-meta" v-if="p.validityDays">
-          <text class="pm-card-meta-text">有效期:{{ p.validityDays }}天</text>
-        </view>
-        <view class="pm-card-badges" v-if="p.badges && p.badges.length > 0">
-          <text class="pm-badge" v-for="(b, i) in p.badges" :key="getBadgeKey(i)">{{ b }}</text>
-        </view>
-        <view class="pm-card-btn" @click="goDetail(p)">
-          <text class="pm-card-btn-text">查看套餐详情</text>
-        </view>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getProblemPackageDetail } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      code: '',
-      packages: [],
-      loading: false
-    }
-  },
-  onLoad(options) {
-    this.code = (options && options.code) || ''
-    this.loadPackages()
-  },
-  methods: {
-    getBadgeKey: function(i) { return 'b' + i },
-    getKey(p) {
-      return p.id || p.code || Math.random()
-    },
-    loadPackages() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      // 从问题域关联的推荐产品获取套餐列表
-      var api = require('../../utils/api.js')
-      var getDomainList = api.getProblemDomainList
-      getDomainList().then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var list = res && res.data
-        if (Array.isArray(list)) {
-          var found = null
-          for (var i = 0; i < list.length; i++) {
-            if (list[i].code === self.code) {
-              found = list[i]
-              break
-            }
-          }
-          if (found && found.recommended_product_ids) {
-            var ids = String(found.recommended_product_ids).split(',').filter(function(s) { return s.trim() })
-            // 每个ID调用详情接口
-            self.loadPackageDetails(ids)
-          } else {
-            self.packages = self.getDefaultPackages()
-          }
-        } else {
-          self.packages = self.getDefaultPackages()
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.packages = self.getDefaultPackages()
-      })
-    },
-    loadPackageDetails(ids) {
-      if (ids.length === 0) {
-        this.packages = this.getDefaultPackages()
-        return
-      }
-      var self = this
-      var results = []
-      var loaded = 0
-      ids.forEach(function(id) {
-        getProblemPackageDetail(id).then(function(res) {
-          loaded++
-          var data = res && res.data
-          if (data) {
-            results.push(data)
-          }
-          if (loaded >= ids.length) {
-            self.packages = results.length > 0 ? results : self.getDefaultPackages()
-          }
-        }).catch(function() {
-          loaded++
-          if (loaded >= ids.length) {
-            self.packages = results.length > 0 ? results : self.getDefaultPackages()
-          }
-        })
-      })
-    },
-    getDefaultPackages() {
-      return [
-        { id: 0, name: '标准方案', description: '包含基础检测与评估', price: 199, validityDays: 30, badges: ['推荐'] },
-        { id: 1, name: '进阶方案', description: '含深度检测与个性化方案', price: 399, validityDays: 60, badges: ['热门'] },
-        { id: 2, name: '全面方案', description: '全维度检测+定制任务+专家咨询', price: 699, validityDays: 90, badges: ['全面'] }
-      ]
-    },
-    goDetail(p) {
-      uni.navigateTo({
-        url: '/pages/problem/package-detail?packageId=' + (p.id || '') + '&code=' + this.code
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.pm-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx;
-  box-sizing: border-box;
-}
-
-.pm-header {
-  margin-bottom: 32rpx;
-}
-
-.pm-title {
-  display: block;
-  font-size: 38rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.pm-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 10rpx;
-}
-
-.pm-loading, .pm-empty {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.pm-card {
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 32rpx;
-  margin-bottom: 24rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
-}
-
-.pm-card-top {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 12rpx;
-}
-
-.pm-card-name {
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #333;
-}
-
-.pm-card-price {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #F97316;
-}
-
-.pm-card-desc {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  margin-bottom: 12rpx;
-}
-
-.pm-card-meta {
-  margin-bottom: 12rpx;
-}
-
-.pm-card-meta-text {
-  font-size: 24rpx;
-  color: #999;
-}
-
-.pm-card-badges {
-  display: flex;
-  flex-wrap: wrap;
-  margin-bottom: 16rpx;
-}
-
-.pm-badge {
-  font-size: 22rpx;
-  color: #F97316;
-  background: rgba(249, 115, 22, 0.1);
-  padding: 4rpx 16rpx;
-  border-radius: 20rpx;
-  margin-right: 12rpx;
-}
-
-.pm-card-btn {
-  background: #FFF7ED;
-  border: 2rpx solid #F97316;
-  border-radius: 36rpx;
-  padding: 18rpx 0;
-  text-align: center;
-}
-
-.pm-card-btn:active {
-  background: #FED7AA;
-}
-
-.pm-card-btn-text {
-  color: #F97316;
-  font-size: 28rpx;
-  font-weight: 600;
-}
-</style>

+ 0 - 286
cfc-frontend/pages/problem/plan-confirm.vue

@@ -1,286 +0,0 @@
-<template>
-  <view class="pc-wrap">
-    <view class="pc-header">
-      <text class="pc-title">方案确认</text>
-      <text class="pc-sub">查看报告方案,确认后生成执行任务</text>
-    </view>
-
-    <view class="pc-loading" v-if="loading">加载中...</view>
-
-    <view class="pc-waiting" v-else-if="!reportReady">
-      <text class="pc-waiting-icon">⏳</text>
-      <text class="pc-waiting-text">报告尚未生成</text>
-      <text class="pc-waiting-sub">请耐心等待报告完成,系统会自动通知你</text>
-      <view class="pc-waiting-btn" @click="refresh">
-        <text class="pc-waiting-btn-text">刷新</text>
-      </view>
-    </view>
-
-    <view class="pc-content" v-else>
-      <view class="pc-report-section">
-        <text class="pc-section-title">报告摘要</text>
-        <text class="pc-section-text">{{ reportSummary || '暂无摘要信息' }}</text>
-      </view>
-
-      <view class="pc-report-section" v-if="planItems && planItems.length > 0">
-        <text class="pc-section-title">建议方案</text>
-        <view class="pc-plan-item" v-for="(item, i) in planItems" :key="getPlanKey(i)">
-          <text class="pc-plan-num">{{ i + 1 }}</text>
-          <view class="pc-plan-body">
-            <text class="pc-plan-title">{{ item.title }}</text>
-            <text class="pc-plan-desc" v-if="item.desc">{{ item.desc }}</text>
-          </view>
-        </view>
-      </view>
-
-      <view class="pc-confirm-btn" @click="confirmPlan">
-        <text class="pc-confirm-btn-text">确认方案,生成任务</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getUserIntent } from '../../utils/api.js'
-import { confirmProblemPlan } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      code: '',
-      loading: false,
-      reportReady: false,
-      reportSummary: '',
-      planItems: []
-    }
-  },
-  onLoad(options) {
-    this.code = (options && options.code) || ''
-    this.loadPlan()
-  },
-  methods: {
-    getPlanKey: function(i) { return 'p' + i },
-    loadPlan() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      getUserIntent().then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var intent = res && res.data
-        if (intent) {
-          var stage = intent.journeyStage || 0
-          self.reportReady = stage >= 5
-          if (self.reportReady) {
-            // 从 survey_result_json 获取方案快照
-            var resultJson = intent.survey_result_json
-            if (resultJson) {
-              var parsed = typeof resultJson === 'string' ? JSON.parse(resultJson) : resultJson
-              self.reportSummary = parsed.summary || parsed.reportSummary || '报告已生成,请查看详情。'
-              var items = parsed.items || parsed.planItems || parsed.recommendations || []
-              self.planItems = Array.isArray(items) ? items : []
-            } else {
-              self.reportSummary = '报告已生成,建议方案已就绪。'
-              self.planItems = [
-                { title: '个性化饮食调整', desc: '根据报告结果调整日常饮食结构' },
-                { title: '运动计划', desc: '定制运动方案,每周执行' },
-                { title: '作息优化', desc: '调整作息习惯,改善睡眠质量' }
-              ]
-            }
-          }
-        } else {
-          self.reportReady = false
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.reportReady = false
-      })
-    },
-    confirmPlan() {
-      var self = this
-      uni.showLoading({ title: '处理中...', mask: true })
-      // 方案确认:后端生成方案任务(memberOnly=1)并推进旅程 stage=6
-      confirmProblemPlan('').then(function(res) {
-        uni.hideLoading()
-        if (res && (res.code === 0 || res.code === 200)) {
-          uni.showToast({ title: '方案已确认,任务已生成', icon: 'success' })
-          setTimeout(function() {
-            uni.navigateTo({ url: '/pages/tasks/tasks' })
-          }, 500)
-        } else {
-          uni.showToast({ title: res.message || '确认失败', icon: 'none' })
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '确认失败,请重试', icon: 'none' })
-      })
-    },
-    refresh() {
-      this.loadPlan()
-    }
-  }
-}
-</script>
-
-<style scoped>
-.pc-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx;
-  box-sizing: border-box;
-}
-
-.pc-header {
-  margin-bottom: 32rpx;
-}
-
-.pc-title {
-  display: block;
-  font-size: 38rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.pc-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 10rpx;
-}
-
-.pc-loading {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.pc-waiting {
-  text-align: center;
-  padding: 80rpx 0;
-}
-
-.pc-waiting-icon {
-  font-size: 80rpx;
-  display: block;
-}
-
-.pc-waiting-text {
-  display: block;
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #333;
-  margin-top: 20rpx;
-}
-
-.pc-waiting-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  margin-top: 10rpx;
-}
-
-.pc-waiting-btn {
-  display: inline-block;
-  margin-top: 32rpx;
-  background: #F97316;
-  border-radius: 40rpx;
-  padding: 18rpx 60rpx;
-}
-
-.pc-waiting-btn:active {
-  opacity: 0.9;
-}
-
-.pc-waiting-btn-text {
-  color: #fff;
-  font-size: 28rpx;
-  font-weight: 600;
-}
-
-.pc-report-section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 28rpx 30rpx;
-  margin-bottom: 24rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
-}
-
-.pc-section-title {
-  display: block;
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-  margin-bottom: 16rpx;
-}
-
-.pc-section-text {
-  display: block;
-  font-size: 26rpx;
-  color: #666;
-  line-height: 1.6;
-}
-
-.pc-plan-item {
-  display: flex;
-  align-items: flex-start;
-  margin-bottom: 20rpx;
-}
-
-.pc-plan-item:last-child {
-  margin-bottom: 0;
-}
-
-.pc-plan-num {
-  width: 40rpx;
-  height: 40rpx;
-  border-radius: 50%;
-  background: #F97316;
-  color: #fff;
-  font-size: 22rpx;
-  font-weight: bold;
-  text-align: center;
-  line-height: 40rpx;
-  margin-right: 16rpx;
-  flex-shrink: 0;
-}
-
-.pc-plan-body {
-  flex: 1;
-}
-
-.pc-plan-title {
-  display: block;
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333;
-}
-
-.pc-plan-desc {
-  display: block;
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 4rpx;
-}
-
-.pc-confirm-btn {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  border-radius: 48rpx;
-  padding: 26rpx 0;
-  text-align: center;
-  margin-top: 16rpx;
-}
-
-.pc-confirm-btn:active {
-  opacity: 0.9;
-}
-
-.pc-confirm-btn-text {
-  color: #fff;
-  font-size: 32rpx;
-  font-weight: 600;
-}
-</style>

+ 0 - 311
cfc-frontend/pages/problem/quick-survey.vue

@@ -1,311 +0,0 @@
-<template>
-  <view class="qs-wrap">
-    <view class="qs-header">
-      <text class="qs-title">快速了解你</text>
-      <text class="qs-sub">答几道小题,为你推荐最合适的内容和方案</text>
-    </view>
-
-    <view class="qs-loading" v-if="loading">加载中...</view>
-    <view class="qs-empty" v-else-if="!questions || questions.length === 0">暂无可用问卷</view>
-
-    <view class="qs-body" v-else>
-      <!-- 模板选择(多模板时可切换) -->
-      <view class="qs-template-row" v-if="templates.length > 1">
-        <text class="qs-template-label">选择话题:</text>
-        <view class="qs-template-chip" v-for="(t, i) in templates" :key="getTemplateKey(t, i)"
-          :class="{ 'qs-template-chip-active': selectedTemplateId === t.id }"
-          @click="switchTemplate(t)">
-          <text>{{ t.title }}</text>
-        </view>
-      </view>
-
-      <view class="qs-q" v-for="(q, qi) in questions" :key="getQuestionKey(q, qi)">
-        <text class="qs-q-label">{{ qi + 1 }}. {{ q.text }}</text>
-        <view class="qs-options">
-          <view class="qs-opt" v-for="(o, oi) in q.options" :key="getOptionKey(o, qi, oi)"
-            :class="{ 'qs-opt-active': selectedIdx(qi) === oi }"
-            @click="select(qi, oi)">
-            <text class="qs-opt-text">{{ o.text }}</text>
-            <text class="qs-opt-check" v-if="selectedIdx(qi) === oi">✓</text>
-          </view>
-        </view>
-      </view>
-
-      <view class="qs-btn" @click="submit" v-if="!submitting">
-        <text class="qs-btn-text">提交,查看推荐</text>
-      </view>
-      <view class="qs-btn qs-btn-disabled" v-else>
-        <text class="qs-btn-text">提交中...</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getActiveTemplates } from '../../utils/api.js'
-import { submitProblemSurvey } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      templates: [],
-      selectedTemplateId: null,
-      templateTitle: '',
-      questions: [],
-      answers: [],
-      loading: false,
-      submitting: false
-    }
-  },
-  onLoad() {
-    this.loadTemplates()
-  },
-  methods: {
-    getTemplateKey(t, i) {
-      return t && t.id ? 't' + t.id : 't' + i
-    },
-    getQuestionKey(q, qi) {
-      return q && q.id ? 'q' + q.id : 'q' + qi
-    },
-    getOptionKey(o, qi, oi) {
-      return o && o.id ? 'o' + qi + '_' + o.id : 'o' + qi + '_' + oi
-    },
-    selectedIdx(qi) {
-      var arr = this.answers[qi]
-      return (arr && arr.length > 0) ? arr[0] : -1
-    },
-    select(qi, oi) {
-      this.answers[qi] = [oi]
-      this.$forceUpdate()
-    },
-    loadTemplates() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      getActiveTemplates().then(function(res) {
-        uni.hideLoading()
-        self.loading = false
-        var list = res && res.data
-        var arr = Array.isArray(list) ? list : []
-        self.templates = arr
-        if (arr.length > 0) {
-          self.applyTemplate(arr[0])
-        } else {
-          self.questions = []
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.loading = false
-        self.templates = []
-        self.questions = []
-      })
-    },
-    applyTemplate(t) {
-      this.selectedTemplateId = t.id
-      this.templateTitle = t.title || ''
-      var parsed = []
-      if (t.questionsJson) {
-        try {
-          var p = typeof t.questionsJson === 'string' ? JSON.parse(t.questionsJson) : t.questionsJson
-          parsed = Array.isArray(p) ? p : []
-        } catch (e) {
-          parsed = []
-        }
-      }
-      this.questions = parsed
-      this.answers = []
-    },
-    switchTemplate(t) {
-      this.applyTemplate(t)
-    },
-    submit() {
-      var self = this
-      // 至少答一题
-      var hasAnswer = false
-      for (var i = 0; i < this.answers.length; i++) {
-        if (this.answers[i] && this.answers[i].length > 0) {
-          hasAnswer = true
-          break
-        }
-      }
-      if (!hasAnswer) {
-        uni.showToast({ title: '请至少回答一题', icon: 'none' })
-        return
-      }
-      self.submitting = true
-      uni.showLoading({ title: '提交中...', mask: true })
-
-      var answers = {}
-      for (var qi = 0; qi < this.questions.length; qi++) {
-        var selected = this.answers[qi] || []
-        if (selected.length === 0) continue
-        var q = this.questions[qi]
-        var opt = q.options && q.options[selected[0]]
-        if (opt && opt.id) {
-          answers[String(opt.id)] = opt.id
-        } else if (opt && opt.value) {
-          answers[String(opt.value)] = opt.value
-        } else {
-          answers['q' + qi] = opt ? opt.text : ''
-        }
-      }
-
-      submitProblemSurvey({
-        problemDomainCode: '',
-        surveyTemplateId: this.selectedTemplateId,
-        answers: answers
-      }).then(function(res) {
-        uni.hideLoading()
-        self.submitting = false
-        if (res && (res.code === 0 || res.code === 200)) {
-          uni.showToast({ title: '提交成功', icon: 'success' })
-          setTimeout(function() {
-            // 跳回首页展示定向内容
-            uni.switchTab({ url: '/pages/index-home/index' })
-          }, 800)
-        } else {
-          uni.showToast({ title: res.message || '提交失败', icon: 'none' })
-        }
-      }).catch(function() {
-        uni.hideLoading()
-        self.submitting = false
-        uni.showToast({ title: '提交失败,请重试', icon: 'none' })
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.qs-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx 120rpx;
-  box-sizing: border-box;
-}
-
-.qs-header {
-  margin-bottom: 32rpx;
-}
-
-.qs-title {
-  display: block;
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.qs-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 10rpx;
-}
-
-.qs-loading, .qs-empty {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.qs-template-row {
-  display: flex;
-  align-items: center;
-  flex-wrap: wrap;
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 16rpx 20rpx;
-  margin-bottom: 24rpx;
-}
-
-.qs-template-label {
-  font-size: 24rpx;
-  color: #999;
-  margin-right: 8rpx;
-}
-
-.qs-template-chip {
-  font-size: 24rpx;
-  color: #666;
-  background: #F3F4F6;
-  border-radius: 24rpx;
-  padding: 6rpx 20rpx;
-  margin: 4rpx;
-}
-
-.qs-template-chip-active {
-  background: #F97316;
-  color: #fff;
-}
-
-.qs-q {
-  margin-bottom: 32rpx;
-}
-
-.qs-q-label {
-  display: block;
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-  margin-bottom: 16rpx;
-}
-
-.qs-options {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-
-.qs-opt {
-  display: flex;
-  align-items: center;
-  background: #fff;
-  border: 2rpx solid #E5E7EB;
-  border-radius: 16rpx;
-  padding: 18rpx 24rpx;
-  margin-right: 16rpx;
-  margin-bottom: 12rpx;
-}
-
-.qs-opt-active {
-  border-color: #F97316;
-  background: #FFF7ED;
-}
-
-.qs-opt-text {
-  font-size: 28rpx;
-  color: #333;
-}
-
-.qs-opt-check {
-  color: #F97316;
-  font-size: 28rpx;
-  font-weight: bold;
-  margin-left: 8rpx;
-}
-
-.qs-btn {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  border-radius: 48rpx;
-  padding: 26rpx 0;
-  text-align: center;
-  margin-top: 32rpx;
-}
-
-.qs-btn:active {
-  opacity: 0.9;
-}
-
-.qs-btn-disabled {
-  opacity: 0.7;
-}
-
-.qs-btn-text {
-  color: #fff;
-  font-size: 32rpx;
-  font-weight: 600;
-}
-</style>

+ 0 - 270
cfc-frontend/pages/problem/survey.vue

@@ -1,270 +0,0 @@
-<template>
-  <view class="sv-wrap">
-    <view class="sv-header">
-      <text class="sv-title">专项调研</text>
-      <text class="sv-sub">回答以下问题,我们为你推荐最适合的方案</text>
-    </view>
-
-    <view class="sv-loading" v-if="loading">加载中...</view>
-    <view class="sv-empty" v-else-if="!questions || questions.length === 0">暂无问卷题目</view>
-
-    <view class="sv-form" v-else>
-      <view class="sv-q" v-for="(q, qi) in questions" :key="getQKey(qi)">
-        <text class="sv-q-label">{{ qi + 1 }}. {{ q.text }}</text>
-        <view class="sv-options">
-          <view
-            class="sv-opt"
-            v-for="(o, oi) in q.options"
-            :key="getOptKey(qi, oi)"
-            :class="{ 'sv-opt-active': isSelected(qi, oi) }"
-            @click="toggleOpt(qi, oi, q)">
-            <text class="sv-opt-text">{{ o.text }}</text>
-            <text class="sv-opt-check" v-if="isSelected(qi, oi)">✓</text>
-          </view>
-        </view>
-      </view>
-
-      <view class="sv-btn" @click="submitSurvey">
-        <text class="sv-btn-text">提交调研</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { submitProblemSurvey, getProblemSurveyQuestions } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      code: '',
-      templateId: '',
-      questions: [],
-      answers: [],
-      loading: false,
-      submitting: false
-    }
-  },
-  onLoad(options) {
-    this.code = (options && options.code) || ''
-    this.templateId = (options && options.templateId) || ''
-    this.loadQuestions()
-  },
-  methods: {
-    getQKey: function(qi) { return 'q' + qi },
-    getOptKey: function(qi, oi) { return 'o' + qi + '_' + oi },
-    getOptId: function(q, oi) {
-      var opt = q.options && q.options[oi]
-      return opt ? (opt.id != null ? opt.id : oi) : oi
-    },
-    isSelected(qi, oi) {
-      var ans = this.answers[qi]
-      if (!ans) return false
-      var q = this.questions[qi]
-      return ans.indexOf(this.getOptId(q, oi)) !== -1
-    },
-    toggleOpt(qi, oi, q) {
-      var optId = this.getOptId(q, oi)
-      var isMulti = q.multi !== false
-      if (!this.answers[qi]) {
-        this.answers[qi] = []
-      }
-      var arr = this.answers[qi]
-      var idx = arr.indexOf(optId)
-      if (idx !== -1) {
-        arr.splice(idx, 1)
-      } else {
-        if (isMulti) {
-          arr.push(optId)
-        } else {
-          this.answers[qi] = [optId]
-        }
-      }
-      this.$forceUpdate()
-    },
-    loadQuestions() {
-      var self = this
-      self.loading = true
-      uni.showLoading({ title: '加载中...', mask: true })
-      // 优先按问题域 code 拉取问卷(后端未绑定模板时返回内置默认问卷)
-      if (self.code) {
-        getProblemSurveyQuestions(self.code).then(function(res) {
-          uni.hideLoading()
-          self.loading = false
-          var data = res && res.data
-          if (data && Array.isArray(data.questions) && data.questions.length > 0) {
-            self.questions = data.questions
-            if (data.templateId) self.templateId = data.templateId
-          } else {
-            self.questions = self.getFallbackQuestions()
-          }
-        }).catch(function() {
-          uni.hideLoading()
-          self.loading = false
-          self.questions = self.getFallbackQuestions()
-        })
-      } else {
-        uni.hideLoading()
-        self.loading = false
-        self.questions = self.getFallbackQuestions()
-      }
-    },
-    getFallbackQuestions() {
-      return [
-        { id: 'q1', text: '请简单描述你的情况', multi: false, options: [{ id: 'o1', text: '了解中' }, { id: 'o2', text: '已有初步了解' }] },
-        { id: 'q2', text: '你希望获得什么帮助?', multi: true, options: [{ id: 'o1', text: '了解方案' }, { id: 'o2', text: '购买产品' }, { id: 'o3', text: '咨询专家' }] }
-      ]
-    },
-    submitSurvey() {
-      var self = this
-      // 检查是否至少答了一题
-      var hasAnswer = false
-      for (var i = 0; i < this.answers.length; i++) {
-        if (this.answers[i] && this.answers[i].length > 0) {
-          hasAnswer = true
-          break
-        }
-      }
-      if (!hasAnswer) {
-        uni.showToast({ title: '请至少回答一题', icon: 'none' })
-        return
-      }
-      if (self.submitting) return
-      self.submitting = true
-      uni.showLoading({ title: '提交中...', mask: true })
-
-      // 统一契约: [{questionId, optionIds}]
-      var answerList = []
-      for (var qi = 0; qi < this.questions.length; qi++) {
-        var q = this.questions[qi]
-        var selected = this.answers[qi] || []
-        if (selected.length > 0) {
-          answerList.push({
-            questionId: q.id != null ? q.id : ('q' + (qi + 1)),
-            optionIds: selected
-          })
-        }
-      }
-
-      submitProblemSurvey({
-        problemDomainCode: self.code,
-        surveyTemplateId: self.templateId || undefined,
-        answers: answerList
-      }).then(function(res) {
-        uni.hideLoading()
-        self.submitting = false
-        uni.showToast({ title: '提交成功', icon: 'success' })
-        setTimeout(function() {
-          uni.navigateTo({
-            url: '/pages/problem/package-match?code=' + self.code
-          })
-        }, 500)
-      }).catch(function() {
-        uni.hideLoading()
-        self.submitting = false
-        uni.showToast({ title: '提交失败,请重试', icon: 'none' })
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.sv-wrap {
-  min-height: 100vh;
-  background: #FFF7ED;
-  padding: 40rpx 32rpx 120rpx;
-  box-sizing: border-box;
-}
-
-.sv-header {
-  margin-bottom: 32rpx;
-}
-
-.sv-title {
-  display: block;
-  font-size: 38rpx;
-  font-weight: bold;
-  color: #333;
-  text-align: center;
-}
-
-.sv-sub {
-  display: block;
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  margin-top: 10rpx;
-}
-
-.sv-loading, .sv-empty {
-  text-align: center;
-  font-size: 28rpx;
-  color: #999;
-  padding: 80rpx 0;
-}
-
-.sv-q {
-  margin-bottom: 36rpx;
-}
-
-.sv-q-label {
-  display: block;
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-  margin-bottom: 16rpx;
-}
-
-.sv-options {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-
-.sv-opt {
-  display: flex;
-  align-items: center;
-  background: #fff;
-  border: 2rpx solid #E5E7EB;
-  border-radius: 16rpx;
-  padding: 18rpx 24rpx;
-  margin-right: 16rpx;
-  margin-bottom: 12rpx;
-}
-
-.sv-opt-active {
-  border-color: #F97316;
-  background: #FFF7ED;
-}
-
-.sv-opt-text {
-  font-size: 28rpx;
-  color: #333;
-}
-
-.sv-opt-check {
-  color: #F97316;
-  font-size: 28rpx;
-  font-weight: bold;
-  margin-left: 8rpx;
-}
-
-.sv-btn {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  border-radius: 48rpx;
-  padding: 26rpx 0;
-  text-align: center;
-  margin-top: 32rpx;
-}
-
-.sv-btn:active {
-  opacity: 0.9;
-}
-
-.sv-btn-text {
-  color: #fff;
-  font-size: 32rpx;
-  font-weight: 600;
-}
-</style>

+ 0 - 375
cfc-frontend/pages/profile/components/ProfileMenu.vue

@@ -1,375 +0,0 @@
-<template>
-  <view>
-    <view class="menu-list">
-      <!-- ===== 个人 ===== -->
-      <view class="menu-group">
-        <view class="menu-group-title">── 个人 ──</view>
-        <view class="menu-item" v-if="role === 'parent'" @click="goToFamilyMembers">
-          <text>👨‍👩‍👧‍👦 家庭成员</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" v-if="role === 'parent'" @click="goToAppointments">
-          <text>📅 测评预约</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" @click="goToDailyTasks">
-          <text>📋 每日任务</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" v-if="role === 'parent'" @click="goToOnboarding">
-          <text>🎯 新手任务</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" @click="goToReportManagement">
-          <text>📋 报告管理</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" v-if="role === 'parent'" @click="goToServiceRoleApply">
-          <text>📋 服务角色申请</text>
-          <text class="arrow">›</text>
-        </view>
-      </view>
-
-      <!-- ===== 服务 ===== -->
-      <view class="menu-group" v-if="showServices">
-        <view class="menu-group-title">── 服务 ──</view>
-        <view class="menu-item" @click="goToMembership">
-          <text>👑 会员中心</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" v-if="isVendor" @click="goToVendorCenter">
-          <text>🏪 服务商中心</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" @click="goToOrders">
-          <text>🛒 订单管理</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" @click="goToConsigneeList">
-          <text>📍 收货人管理</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" @click="goToAfterSales">
-          <text>🔄 售后记录</text>
-          <text class="arrow">›</text>
-        </view>
-      </view>
-
-      <!-- ===== 设置 ===== -->
-      <view class="menu-group">
-        <view class="menu-group-title">── 设置 ──</view>
-        <view class="menu-item" v-if="role === 'parent'" @click="showPasswordModal = true">
-          <text>🔐 {{ hasPassword ? '重置密码' : '设置密码' }}</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item menu-item-switch" v-if="role === 'parent'">
-          <text>👁 对家庭成员可见</text>
-          <switch :checked="showToFamily === 1" @change="onShowToFamilyChange" color="#5B9BD5" />
-        </view>
-        <view class="menu-item" @click="showInviteActionSheet">
-          <text>📨 邀请加入</text>
-          <text class="arrow">›</text>
-        </view>
-        <view class="menu-item" @click="logout">
-          <text>🚪 退出登录</text>
-          <text class="arrow">›</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 设置/重置密码弹窗 -->
-    <view class="modal-mask" v-if="showPasswordModal" @click="showPasswordModal = false">
-      <view class="modal" @click.stop>
-        <view class="modal-title">{{ hasPassword ? '重置密码' : '设置密码' }}</view>
-        <view class="form-item" v-if="hasPassword">
-          <input v-model="oldPassword" type="number" password placeholder="输入旧密码" maxlength="6" />
-        </view>
-        <view class="form-item">
-          <input v-model="newPassword" type="number" password placeholder="输入新密码" maxlength="6" />
-        </view>
-        <view class="form-item">
-          <input v-model="confirmPassword" type="number" password placeholder="确认新密码" maxlength="6" />
-        </view>
-        <view class="modal-btns">
-          <button @click="showPasswordModal = false">取消</button>
-          <button class="btn-primary" @click="savePassword">确定</button>
-        </view>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { setPassword, verifyPassword, updateMemberVisibility, getFamilyMemberList, vendorStatus, ensureFamily } from '../../../utils/api.js'
-
-export default {
-  name: 'ProfileMenu',
-  props: {
-    role: {
-      type: String,
-      default: 'parent'
-    },
-    showServices: {
-      type: Boolean,
-      default: true
-    }
-  },
-  data() {
-    return {
-      hasPassword: false,
-      showPasswordModal: false,
-      oldPassword: '',
-      newPassword: '',
-      confirmPassword: '',
-      showToFamily: 1,
-      isVendor: false
-    }
-  },
-  onShow() {
-    if (!uni.getStorageSync('token')) return
-    this.hasPassword = !!uni.getStorageSync('passwordSet')
-    this.loadShowToFamily()
-    this.checkVendorStatus()
-  },
-  methods: {
-    async loadShowToFamily() {
-      try {
-        const uid = this.$store.state.userId || uni.getStorageSync('userId')
-        const res = await getFamilyMemberList({ visibleOnly: true })
-        const members = res.data || []
-        const self = members.find(m => m.id == uid)
-        if (self) {
-          this.showToFamily = self.showToFamily !== 0 ? 1 : 0
-        }
-      } catch (e) {}
-    },
-    goToOnboarding() {
-      uni.navigateTo({ url: '/pages/profile-extra/onboarding' })
-    },
-    goToReportManagement() {
-      uni.navigateTo({ url: '/pages/profile/report-management' })
-    },
-    goToServiceRoleApply() {
-      uni.navigateTo({ url: '/pages/profile/service-role-apply' })
-    },
-    goToFamilyMembers() {
-      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
-    },
-    goToAppointments() {
-      uni.navigateTo({ url: '/pages/parent/appointments' })
-    },
-    goToDailyTasks() {
-      uni.navigateTo({ url: '/pages/tasks/daily-tasks' })
-    },
-    async onShowToFamilyChange(e) {
-      const val = e.detail.value ? 1 : 0
-      const uid = this.$store.state.userId || uni.getStorageSync('userId')
-      try {
-        await updateMemberVisibility(uid, 'parent', val)
-        this.showToFamily = val
-        uni.showToast({ title: val ? '已对家庭成员可见' : '已对家庭成员隐藏', icon: 'none' })
-      } catch (e) {
-        uni.showToast({ title: '操作失败', icon: 'none' })
-      }
-    },
-    goToMembership() {
-      uni.navigateTo({ url: '/pages/membership/index' })
-    },
-    goToVendorCenter() {
-      uni.navigateTo({ url: '/pages/vendor/center' })
-    },
-    goToOrders() {
-      uni.navigateTo({ url: '/pages/shop/order-list/order-list' })
-    },
-    goToConsigneeList() {
-      uni.navigateTo({ url: '/pages/shop/consignee-list/consignee-list' })
-    },
-    goToAfterSales() {
-      uni.navigateTo({ url: '/pages/shop/after-sales/after-sales' })
-    },
-    
-    showInviteActionSheet() {
-      var self = this
-      var ui = uni.getStorageSync('userInfo') || {}
-      if (!ui.familyId) {
-        ensureFamily().then(function(result) {
-          if (result && result.created) {
-            self.showInviteActionSheet()
-          }
-        })
-        return
-      }
-      const userInfo = ui
-      const items = ['👨‍👩‍👧‍👦 邀请家人', '👨‍🏫 邀请成长规划师']
-      const types = ['family', 'guide']
-      uni.showActionSheet({
-        itemList: items,
-        success: (res) => {
-          self.inviteGenerate(types[res.tapIndex], userInfo)
-        }
-      })
-    },
-    inviteGenerate(type, userInfo) {
-      const inviteTypes = {
-        family: {
-          title: (userInfo && userInfo.nickname ? userInfo.nickname : '家人') + ' 邀请你加入家庭',
-          path: '/pages/login/login?invite_type=family'
-        },
-        guide: {
-          title: (userInfo && userInfo.nickname ? userInfo.nickname : '家长') + ' 邀请你成为家庭成长规划师',
-          path: '/pages/login/login?invite_type=guide'
-        }
-      }
-      const config = inviteTypes[type] || inviteTypes.family
-      this.$emit('invite-generate', {
-        title: config.title,
-        path: config.path + '&invite_code=',
-        imageUrl: '/static/invite-card.png'
-      })
-      uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
-    },
-    async savePassword() {
-      if (this.newPassword.length !== 6) {
-        uni.showToast({ title: '请输入6位密码', icon: 'none' })
-        return
-      }
-      if (this.newPassword !== this.confirmPassword) {
-        uni.showToast({ title: '两次密码不一致', icon: 'none' })
-        return
-      }
-      if (this.hasPassword && this.oldPassword.length !== 6) {
-        uni.showToast({ title: '请输入旧密码', icon: 'none' })
-        return
-      }
-      try {
-        if (this.hasPassword) {
-          const verifyRes = await verifyPassword(this.oldPassword)
-          if (verifyRes.code !== 200) {
-            uni.showToast({ title: '旧密码错误', icon: 'none' })
-            return
-          }
-        }
-        await setPassword(this.newPassword)
-        uni.setStorageSync('passwordSet', true)
-        this.hasPassword = true
-        uni.showToast({ title: '密码设置成功', icon: 'success' })
-        this.showPasswordModal = false
-        this.oldPassword = ''
-        this.newPassword = ''
-        this.confirmPassword = ''
-      } catch (e) {
-        console.error('设置密码失败', e)
-      }
-    },
-    logout() {
-      uni.showModal({
-        title: '确认退出',
-        content: '确定要退出登录吗?',
-        success: (res) => {
-          if (res.confirm) {
-            this.$store.commit('logout')
-            uni.reLaunch({ url: '/pages/index-home/index' })
-          }
-        }
-      })
-    },
-    async checkVendorStatus() {
-      try {
-        const res = await vendorStatus()
-        this.isVendor = res.data && res.data.vendorStatus === 'approved'
-      } catch (e) {
-        this.isVendor = false
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.menu-list {
-  margin: 30rpx;
-}
-.menu-group {
-  background: #fff;
-  border-radius: 20rpx;
-  margin-bottom: 20rpx;
-  overflow: hidden;
-}
-.menu-group-title {
-  padding: 20rpx 30rpx 10rpx;
-  font-size: 22rpx;
-  color: #999;
-  text-align: center;
-}
-.menu-item {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 28rpx 30rpx;
-  border-bottom: 1rpx solid #f5f5f5;
-  font-size: 28rpx;
-  color: #333;
-}
-.menu-item:last-child {
-  border-bottom: none;
-}
-.arrow {
-  color: #ccc;
-  font-size: 28rpx;
-}
-.menu-item-switch {
-  justify-content: space-between;
-}
-.menu-item-switch text {
-  flex: 1;
-}
-
-.modal-mask {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0,0,0,0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 999;
-}
-.modal {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 40rpx;
-  width: 80%;
-}
-.modal-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-.form-item {
-  margin-bottom: 20rpx;
-}
-.form-item input {
-  border: 1rpx solid #ddd;
-  border-radius: 12rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-}
-.modal-btns {
-  display: flex;
-  gap: 20rpx;
-  margin-top: 30rpx;
-}
-.modal-btns button {
-  flex: 1;
-  font-size: 28rpx;
-  padding: 20rpx;
-  border-radius: 12rpx;
-}
-.modal-btns .btn-primary {
-  background: #667eea;
-  color: #fff;
-}
-</style>