Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/cfclub' into cfclub

Sisyphus 1 settimana fa
parent
commit
a3d4d83ec7

+ 16 - 0
cfc-frontend/mixins/login-sheet.js

@@ -93,6 +93,22 @@ export default {
           console.warn('loadData 调用失败', e)
         }
       }
+      // 登录成功后,尝试刷新当前页面,重置登录状态
+      try {
+        var pages = getCurrentPages()
+        if (pages.length) {
+          var curPage = pages[pages.length - 1]
+          if (curPage && typeof curPage.onLoad === 'function' && typeof curPage.onShow === 'function') {
+            var options = curPage.options || {}
+            curPage.onLoad(options)
+            curPage.onShow()
+          } else if (curPage && typeof curPage.onShow === 'function') {
+            curPage.onShow()
+          }
+        }
+      } catch (e) {
+        console.warn('刷新当前页失败', e)
+      }
     }
   }
 }

+ 6 - 0
cfc-frontend/pages.json

@@ -1102,6 +1102,12 @@
             "navigationBarTitleText": "方案制定"
           }
         },
+        {
+          "path": "plan-list",
+          "style": {
+            "navigationBarTitleText": "我的方案"
+          }
+        },
         {
           "path": "teacher-service",
           "style": {

+ 1 - 9
cfc-frontend/pages/health-main/index.vue

@@ -543,15 +543,7 @@ export default {
       uni.navigateTo({ url: url })
     },
     goPlanSummary: function() {
-      var url = '/pages/health/health-plan-summary'
-      if (this.lastPlanId) {
-        url += '?planId=' + this.lastPlanId
-      }
-      var mid = this.selectedMemberId
-      if (mid) {
-        url += (url.indexOf('?') >= 0 ? '&' : '?') + 'memberId=' + mid
-      }
-      uni.navigateTo({ url: url })
+      uni.navigateTo({ url: '/pages/health/plan-list' })
     },
     loadHealthData: function() {
       var self = this

+ 1 - 1
cfc-frontend/pages/health/gut-flora-detail.vue

@@ -975,7 +975,7 @@ applyDraftPayload: function(payload) {
               cancelText: '看报告',
               success: function(modalRes) {
                 if (modalRes.confirm) {
-                  uni.redirectTo({ url: '/pages/health/health-plan-summary?planId=' + planId })
+                  uni.redirectTo({ url: '/pages/health/plan-list' })
                 } else {
                   goDetail()
                 }

+ 239 - 0
cfc-frontend/pages/health/plan-list.vue

@@ -0,0 +1,239 @@
+<template>
+  <view class="page">
+    <view class="header">
+      <text class="title">我的健康方案</text>
+      <text class="subtitle">查看和管理健康计划</text>
+    </view>
+
+    <!-- 新建方案按钮 -->
+    <view class="action-bar">
+      <button class="btn-create" @click="goCreatePlan">+ 新建方案</button>
+    </view>
+
+    <!-- 加载状态 -->
+    <view class="loading" v-if="loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty" v-else-if="plans.length === 0">
+      <text class="empty-icon">📋</text>
+      <text class="empty-title">暂无健康方案</text>
+      <text class="empty-desc">点击下方按钮创建第一个健康方案</text>
+    </view>
+
+    <!-- 方案列表 -->
+    <view class="plan-list" v-else>
+      <view
+        class="plan-card"
+        v-for="plan in plans"
+        :key="plan.id"
+        @click="goDetail(plan.id)"
+      >
+        <view class="card-top">
+          <text class="plan-title">{{ plan.goal || plan.memberName || '健康方案' }}</text>
+          <view class="plan-status" :class="'status-' + plan.status">
+            <text>{{ statusText(plan.status) }}</text>
+          </view>
+        </view>
+        <view class="card-meta">
+          <text class="meta-item" v-if="plan.memberName">成员:{{ plan.memberName }}</text>
+          <text class="meta-item">创建时间:{{ formatTime(plan.createdAt) }}</text>
+        </view>
+        <view class="plan-arrow">›</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getHealthPlanList } from '@/utils/api.js'
+
+export default {
+  data() {
+    return {
+      loading: false,
+      plans: []
+    }
+  },
+  onLoad() {
+    this.loadPlans()
+  },
+  onShow() {
+    this.loadPlans()
+  },
+  methods: {
+    loadPlans() {
+      this.loading = true
+      var self = this
+      getHealthPlanList({}).then(function(res) {
+        self.loading = false
+        if (res && res.code === 200 && res.data) {
+          var list = res.data.list || res.data
+          self.plans = Array.isArray(list) ? list : []
+        } else {
+          self.plans = []
+        }
+      }).catch(function(err) {
+        self.loading = false
+        console.error('加载方案列表失败', err)
+        self.plans = []
+      })
+    },
+    goDetail(planId) {
+      uni.navigateTo({ url: '/pages/health/health-plan-summary?planId=' + planId })
+    },
+    goCreatePlan() {
+      uni.navigateTo({ url: '/pages/health/health-plan-summary' })
+    },
+    statusText(status) {
+      var map = {
+        'draft': '待确认',
+        'pending_review': '待审核',
+        'confirmed': '已确认',
+        'published': '已发布',
+        'rejected': '已驳回'
+      }
+      return map[status] || '未知'
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      return String(t).substring(0, 19).replace('T', ' ')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page {
+  min-height: 100vh;
+  background: #F5FAFE;
+  padding-bottom: 30rpx;
+}
+
+.header {
+  padding: 40rpx 30rpx 20rpx;
+}
+
+.title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #1F2D3D;
+  display: block;
+}
+
+.subtitle {
+  font-size: 26rpx;
+  color: #8A94A6;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.action-bar {
+  padding: 0 30rpx 20rpx;
+}
+
+.btn-create {
+  width: 100%;
+  background: #4A9BD7;
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: 600;
+  padding: 24rpx 0;
+  border-radius: 12rpx;
+  border: none;
+}
+
+.loading, .empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 100rpx 0;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #8A94A6;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+}
+
+.empty-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-top: 20rpx;
+}
+
+.empty-desc {
+  font-size: 26rpx;
+  color: #999;
+  margin-top: 10rpx;
+  text-align: center;
+}
+
+.plan-list {
+  padding: 0 30rpx;
+}
+
+.plan-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  position: relative;
+}
+
+.plan-card:active {
+  opacity: 0.8;
+}
+
+.card-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.plan-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  flex: 1;
+}
+
+.plan-status {
+  font-size: 22rpx;
+  padding: 4rpx 16rpx;
+  border-radius: 20rpx;
+}
+
+.status-draft { background: #FFF3E0; color: #F97316; }
+.status-pending_review { background: #E3F2FD; color: #2196F3; }
+.status-confirmed { background: #E8F5E9; color: #4CAF50; }
+.status-published { background: #E8F5E9; color: #4CAF50; }
+.status-rejected { background: #FDE8E8; color: #C62828; }
+
+.card-meta {
+  display: flex;
+  flex-direction: column;
+  margin-top: 16rpx;
+  gap: 6rpx;
+}
+
+.meta-item {
+  font-size: 24rpx;
+  color: #bbb;
+}
+
+.plan-arrow {
+  position: absolute;
+  right: 24rpx;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 36rpx;
+  color: #CCC;
+}
+</style>

+ 1 - 1
cfc-frontend/pages/health/report-confirm.vue

@@ -875,7 +875,7 @@ export default {
               cancelText: '看报告',
               success: function(modalRes) {
                 if (modalRes.confirm) {
-                  uni.redirectTo({ url: '/pages/health/health-plan-summary?planId=' + planId })
+                  uni.redirectTo({ url: '/pages/health/plan-list' })
                 } else {
                   goReport()
                 }

+ 11 - 11
cfc-frontend/pages/invite/join-form.vue

@@ -177,8 +177,8 @@ export default {
 	margin-bottom: 30rpx;
 }
 .form-item {
-				margin-bottom: 40rpx;
-			}
+	margin-bottom: 40rpx;
+}
 .form-item:last-child {
 	margin-bottom: 0;
 }
@@ -192,15 +192,15 @@ export default {
 	color: #f44336;
 }
 .input, .picker-input {
-				padding: 20rpx;
-				background: #f5f5f5;
-				border-radius: 12rpx;
-				font-size: 28rpx;
-				box-sizing: border-box;
-				width: 100%;
-				min-height: 72rpx;
-				line-height: 1.5;
-			}
+	padding: 28rpx 24rpx;
+	background: #f5f5f5;
+	border-radius: 12rpx;
+	font-size: 30rpx;
+	box-sizing: border-box;
+	width: 100%;
+	min-height: 96rpx;
+	line-height: 1.4;
+}
 .avatar-upload {
 	width: 150rpx;
 	height: 150rpx;

+ 4 - 2
cfc-frontend/pages/invite/join.vue

@@ -235,9 +235,11 @@ export default {
             setTimeout(() => this.goHome(), 1000)
             return
           }
-          // 不在目标家庭 -> 去完善个人信息页提交申请
-          this.setRedirectInfo(false)
+          // 不在目标家庭 -> 自动跳转到完善个人信息页
           this.loading = false
+          this.redirecting = true
+          this.goJoinForm()
+          return
         } else {
           this.setRedirectInfo(false)
           this.loading = false

+ 205 - 0
cfc-frontend/pages/reminder-config/index.vue

@@ -242,3 +242,208 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+.reminder-config-page {
+  box-sizing: border-box;
+  min-height: 100vh;
+  background: #F5F9FC;
+  overflow: hidden;
+}
+.content {
+  padding: 30rpx;
+  box-sizing: border-box;
+  width: 100%;
+  overflow: hidden;
+}
+.cfg-card {
+  background-color: #FFFFFF;
+  border-radius: 24rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 16rpx rgba(91, 155, 213, 0.08);
+}
+.cfg-card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.cfg-card-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #1E293B;
+}
+.cfg-card-desc {
+  display: block;
+  margin-top: 12rpx;
+  font-size: 24rpx;
+  color: #94A3B8;
+  line-height: 1.6;
+}
+.cfg-switch {
+  padding: 10rpx 28rpx;
+  border-radius: 999rpx;
+  font-size: 26rpx;
+  background: #E2E8F0;
+  color: #64748B;
+}
+.cfg-switch-on {
+  background: linear-gradient(135deg, #5B9BD5, #8FC5E8);
+  color: #FFFFFF;
+  font-weight: 600;
+}
+.channel-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+  margin-top: 24rpx;
+}
+.channel-item {
+  box-sizing: border-box;
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding: 16rpx 24rpx;
+  border: 2rpx solid #E2E8F0;
+  border-radius: 20rpx;
+  background: #FFFFFF;
+  min-width: 200rpx;
+}
+.channel-selected {
+  border-color: #5B9BD5;
+  background: #5B9BD5;
+}
+.channel-disabled {
+  opacity: 0.55;
+}
+.channel-check {
+  position: absolute;
+  top: -8rpx;
+  right: -8rpx;
+  width: 32rpx;
+  height: 32rpx;
+  border-radius: 50%;
+  background-color: #FFFFFF;
+  color: #5B9BD5;
+  font-size: 20rpx;
+  font-weight: 900;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 2;
+  box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.1);
+}
+.channel-info {
+  display: flex;
+  flex-direction: column;
+  gap: 6rpx;
+}
+.channel-name {
+  font-size: 26rpx;
+  color: #1E293B;
+  font-weight: 500;
+}
+.channel-selected .channel-name {
+  color: #FFFFFF;
+}
+.channel-reason {
+  font-size: 20rpx;
+  color: #94A3B8;
+}
+.channel-tip {
+  width: 100%;
+  margin-top: 16rpx;
+}
+.channel-tip-text {
+  font-size: 24rpx;
+  color: #5B9BD5;
+}
+.timing-list {
+  display: flex;
+  gap: 20rpx;
+  margin-top: 24rpx;
+}
+.timing-item {
+  flex: 1;
+  box-sizing: border-box;
+  padding: 24rpx;
+  border: 2rpx solid #E2E8F0;
+  border-radius: 20rpx;
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+.timing-active {
+  border-color: #5B9BD5;
+  background: #F0F7FF;
+}
+.timing-name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1E293B;
+}
+.timing-active .timing-name {
+  color: #3A7CC4;
+}
+.timing-desc {
+  font-size: 22rpx;
+  color: #94A3B8;
+}
+.offset-row {
+  display: flex;
+  align-items: center;
+  margin-top: 30rpx;
+  padding: 20rpx 24rpx;
+  background: #F8FAFC;
+  border-radius: 16rpx;
+}
+.offset-label {
+  font-size: 26rpx;
+  color: #1E293B;
+  margin-right: 20rpx;
+}
+.offset-input {
+  flex: 1;
+  font-size: 28rpx;
+  color: #1E293B;
+}
+.offset-unit {
+  font-size: 24rpx;
+  color: #94A3B8;
+  margin-left: 12rpx;
+}
+.offset-hint {
+  display: block;
+  margin-top: 12rpx;
+  font-size: 22rpx;
+  color: #94A3B8;
+}
+.tip-card {
+  background: #D6EAF8;
+  border-radius: 16rpx;
+  padding: 24rpx 30rpx;
+  margin-bottom: 30rpx;
+}
+.tip-text {
+  font-size: 24rpx;
+  color: #3A7CC4;
+  line-height: 1.6;
+}
+.save-area {
+  padding: 40rpx 30rpx;
+}
+.save-btn {
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  color: #FFFFFF;
+  text-align: center;
+  padding: 28rpx 0;
+  border-radius: 999rpx;
+  font-size: 30rpx;
+  font-weight: 600;
+  box-shadow: 0 6rpx 20rpx rgba(91, 155, 213, 0.3);
+}
+.save-btn:active {
+  opacity: 0.9;
+  transform: scale(0.98);
+}
+</style>

+ 1 - 1
cfc-frontend/pages/shop/cart/cart.vue

@@ -43,7 +43,7 @@
                 </text>
               </view>
               <text class="item-name">{{ item.productName }}</text>
-              <text v-if="item.giftLevelName" class="gift-badge">🎁购此送 {{ item.giftLevelName }}会员</text>
+              <text v-if="item.giftLevelName" class="gift-badge">🎁</text>
               <text v-if="item.specDesc" class="item-spec">{{ item.specDesc }}</text>
               <text v-if="item.supplierName" class="item-supplier">供应商: {{ item.supplierName }}</text>
               <text class="item-delete" @click="onRemove(item.productId)">🗑</text>

+ 1 - 1
cfc-frontend/pages/shop/index/index.vue

@@ -126,7 +126,7 @@
             <view class="price-row">
               <text class="price">{{ formatPriceWithSymbol(item.price) }}</text>
               <text v-if="item.memberPrice" class="member-price">会员{{ formatPriceWithSymbol(item.memberPrice) }}</text>
-              <text v-if="item.giftLevelName" class="gift-badge">🎁购此送 {{ item.giftLevelName }}会员</text>
+              <text v-if="item.giftLevelName" class="gift-badge">🎁</text>
             </view>
             <text class="vendor-name">{{ item.brandName || '平台官方' }}</text>
           </view>

+ 1 - 1
cfc-frontend/pages/shop/search/search.vue

@@ -58,7 +58,7 @@
             <view class="price-row">
               <text class="price">{{ formatPriceWithSymbol(item.price) }}</text>
               <text v-if="item.memberPrice" class="member-price">会员{{ formatPriceWithSymbol(item.memberPrice) }}</text>
-              <text v-if="item.giftLevelName" class="gift-badge">🎁购此送 {{ item.giftLevelName }}会员</text>
+              <text v-if="item.giftLevelName" class="gift-badge">🎁</text>
             </view>
           </view>
         </view>