Browse Source

fix: edit page scrolling, action tab blank screen, SKU specs save

- ProductEdit/ActivityEdit/ArticleEdit: scroll fix with display:block on card/body
- action/index.vue: first-load fallback to first child when no saved currentChildId
- action/index.vue: reduce tab-transition fallback timeout 5s -> 2s
- ProductSkuEditor.vue: formatSpecsForInput helper to prevent {} save bug
- DatabaseInitializer + schema.sql: sync migration
Xiaogang Liao 3 months ago
parent
commit
b5c05932b2

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -6116,6 +6116,9 @@ try {
     } catch (Exception e) {
         log.warn("创建commission_dist_log表失败: {}", e.getMessage());
     }
+
+    // 迁移136: payment_orders表添加period列(会员订阅周期)
+    ensureColumn("payment_orders", "period", "VARCHAR(20) DEFAULT 'yearly' COMMENT '订阅周期: monthly/quarterly/yearly'");
     }
 
     private void runMigration82() {

+ 21 - 0
cfc-backend/src/main/resources/schema.sql

@@ -3547,3 +3547,24 @@ CREATE TABLE IF NOT EXISTS commission_dist_log (
     INDEX idx_level1 (level1_user_id),
     INDEX idx_level2 (level2_user_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='P点分润记录';
+
+-- 会员订单表
+CREATE TABLE IF NOT EXISTS payment_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    level_code VARCHAR(50) DEFAULT 'FREE' COMMENT '会员等级编码',
+    payment_type VARCHAR(20) DEFAULT 'yearly' COMMENT '支付类型: yearly/monthly/trial',
+    period VARCHAR(20) DEFAULT 'yearly' COMMENT '订阅周期: monthly/quarterly/yearly',
+    amount INT COMMENT '支付金额(分)',
+    user_coupon_id BIGINT COMMENT '使用的用户优惠券ID',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/expired',
+    pay_method VARCHAR(20) COMMENT '支付方式',
+    transaction_id VARCHAR(200) COMMENT '交易流水号',
+    paid_at DATETIME COMMENT '支付时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_order_no (order_no),
+    INDEX idx_family_id (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员支付订单';

+ 8 - 3
cfc-frontend/pages/action/index.vue

@@ -299,8 +299,13 @@ export default {
               break
             }
           }
-          // FIX: 不再自动选择第一个孩子
-          if (found) {
+          // 首次进入或未选孩子时,默认选第一个孩子以完成本页首屏渲染
+          if (!found && self.children.length > 0) {
+            self.currentChildId = self.children[0].childId
+            self.activeChildId = self.children[0].childId
+            self.currentChildName = self.children[0].name || self.children[0].nickname || '孩子'
+          }
+          if (self.activeChildId) {
             self.loadDimensionData()
           }
         }
@@ -562,7 +567,7 @@ export default {
       setTimeout(function() {
         if (unwatch) unwatch()
         self.showTabTransition = false
-      }, 5000)
+      }, 2000)
     }
   }
 }

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-37d2d39a6acdf2007e2f91e19a95bd1e05c4e2c3
+37d2d39a6acdf2007e2f91e19a95bd1e05c4e2c3

+ 28 - 6
cfc-web/public/CHANGELOG-v1.0.md

@@ -6,21 +6,19 @@
 
 ## v1.0.618 (2026-07-29)
 
+### Bug 修复
+- 推荐活动和推荐商品显示实际价格而非'登录查看价格'
+
 ### 其他
+- - index-home/index.vue: 游客视图商品卡片显示价格而非固定'登录查看'
 - EnergyWeekCard, FunTaskItem, HealthCheckinCard, HealthDimensionsSection,
 - HealthLikeButton, HealthTips, MediaRecorder, OctopusGraph, PearlDiagram,
 - PointsProgressRing, RepurchaseReminder, SkeletonBlock, TaskItem/TaskReminder,
 - UserQuickEntry, WisdomTips; assessment/body/membership/parent/register-child/
 - wisdom_temp 等老旧页面; teacherGuard router
-- 
-
-
-## v1.0.618 (2026-07-29)
 
 ### 文档
 - 新增财富能量兑换优惠券流程图
-
-
 ## v1.0.617 (2026-07-29)
 
 ### Bug 修复
@@ -43,6 +41,30 @@
 - 
 
 
+## v1.0.615 (2026-07-29)
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- 改为 default: '' 避免警告
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+### Bug 修复
+- ProductEdit card layout, sales page add leaky-gut/pancreatitis articles
+- remove required:true from dimensionCode prop in DimensionActivities/Products
+
+### 新功能
+- add pancreatitis and leaky-gut articles, update screenshot plan
+
+### 文档
+- 新增佣金结算流程图 — 两级推荐佣金+提现+结算全链路
+
+
 ## v1.0.614 (2026-07-29)
 
 ### Bug 修复

+ 28 - 6
cfc-web/public/CHANGELOG.md

@@ -10,21 +10,19 @@
 
 ## v1.0.618 (2026-07-29)
 
+### Bug 修复
+- 推荐活动和推荐商品显示实际价格而非'登录查看价格'
+
 ### 其他
+- - index-home/index.vue: 游客视图商品卡片显示价格而非固定'登录查看'
 - EnergyWeekCard, FunTaskItem, HealthCheckinCard, HealthDimensionsSection,
 - HealthLikeButton, HealthTips, MediaRecorder, OctopusGraph, PearlDiagram,
 - PointsProgressRing, RepurchaseReminder, SkeletonBlock, TaskItem/TaskReminder,
 - UserQuickEntry, WisdomTips; assessment/body/membership/parent/register-child/
 - wisdom_temp 等老旧页面; teacherGuard router
-- 
-
-
-## v1.0.618 (2026-07-29)
 
 ### 文档
 - 新增财富能量兑换优惠券流程图
-
-
 ## v1.0.617 (2026-07-29)
 
 ### Bug 修复
@@ -47,6 +45,30 @@
 - 
 
 
+## v1.0.615 (2026-07-29)
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- 改为 default: '' 避免警告
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+### Bug 修复
+- ProductEdit card layout, sales page add leaky-gut/pancreatitis articles
+- remove required:true from dimensionCode prop in DimensionActivities/Products
+
+### 新功能
+- add pancreatitis and leaky-gut articles, update screenshot plan
+
+### 文档
+- 新增佣金结算流程图 — 两级推荐佣金+提现+结算全链路
+
+
 ## v1.0.614 (2026-07-29)
 
 ### Bug 修复

+ 34 - 1
cfc-web/src/views/admin/ActivityEdit.vue

@@ -518,7 +518,40 @@ activityId
 </script>
 
 <style scoped>
-.activity-edit { padding: 20px; }
+.activity-edit {
+  padding: 20px;
+  min-height: 0;
+  overflow-y: auto;
+}
+.activity-edit > .el-card {
+  display: block;
+  min-height: auto;
+}
+.activity-edit > .el-card /deep/ .el-card__body {
+  display: block;
+  min-height: auto;
+  overflow: visible;
+}
+.activity-edit /deep/ .admin-page-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 12px;
+  flex-wrap: wrap;
+}
+.activity-edit /deep/ .admin-page-title {
+  font-size: 18px;
+  font-weight: 600;
+  line-height: 32px;
+}
+.activity-edit /deep/ .admin-page-actions {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 8px;
+  flex-wrap: wrap;
+  margin-left: auto;
+}
 .cover-preview {
   width: 200px;
   height: 120px;

+ 33 - 3
cfc-web/src/views/admin/ArticleEdit.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="article-edit admin-page">
     <el-card>
-      <div slot="header">
-        <span>{{ readonly && articleStatus !== 'rejected' ? '文章详情' : (isEdit ? '编辑文章' : '新建文章') }}</span>
-        <div style="float: right;">
+      <div slot="header" class="admin-page-header">
+        <span class="admin-page-title">{{ readonly && articleStatus !== 'rejected' ? '文章详情' : (isEdit ? '编辑文章' : '新建文章') }}</span>
+        <div class="admin-page-actions">
           <el-button @click="$router.push('/article-manage')">返回列表</el-button>
           <template v-if="!readonly">
             <el-button type="primary" @click="handleSave(false)" :loading="saving">保存草稿</el-button>
@@ -518,8 +518,38 @@ export default {
 <style scoped>
 .article-edit {
   padding: 20px;
+  min-height: 0;
   overflow-y: auto;
 }
+.article-edit > .el-card {
+  display: block;
+  min-height: auto;
+}
+.article-edit > .el-card /deep/ .el-card__body {
+  display: block;
+  min-height: auto;
+  overflow: visible;
+}
+.article-edit /deep/ .admin-page-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 12px;
+  flex-wrap: wrap;
+}
+.article-edit /deep/ .admin-page-title {
+  font-size: 18px;
+  font-weight: 600;
+  line-height: 32px;
+}
+.article-edit /deep/ .admin-page-actions {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 8px;
+  flex-wrap: wrap;
+  margin-left: auto;
+}
 .cover-preview {
   width: 240px;
   max-height: 160px;

+ 21 - 10
cfc-web/src/views/admin/components/ProductSkuEditor.vue

@@ -162,14 +162,8 @@ const baseURL = process.env.VUE_APP_BASE_API || ''
     showDialog(sku) {
       if (sku) {
         this.editingId = sku.id
-        let specsVal = sku.specs || ''
-        if (typeof specsVal === 'object') {
-          specsVal = JSON.stringify(specsVal)
-        } else if (typeof specsVal === 'string' && specsVal.trim().startsWith('[')) {
-          try { specsVal = JSON.stringify(JSON.parse(specsVal)) } catch (e) {}
-        }
         this.skuForm = {
-          specs: specsVal,
+          specs: this.formatSpecsForInput(sku.specs),
           price: sku.price ? parseFloat(sku.price) : 0,
           originalPrice: sku.originalPrice ? parseFloat(sku.originalPrice) : 0,
           stock: sku.stock || 0,
@@ -211,11 +205,9 @@ const baseURL = process.env.VUE_APP_BASE_API || ''
     parseSpecs(specs) {
       if (!specs) return '-'
       try {
-        // Handle object (Map from DTO) directly
         if (typeof specs === 'object') {
           return Object.entries(specs).map(([k, v]) => k + ':' + v).join('; ')
         }
-        // Handle string (JSON string)
         const obj = JSON.parse(specs)
         if (typeof obj === 'object' && obj !== null) {
           return Object.entries(obj).map(([k, v]) => k + ':' + v).join('; ')
@@ -225,6 +217,25 @@ const baseURL = process.env.VUE_APP_BASE_API || ''
         return specs
       }
     },
+    formatSpecsForInput(specs) {
+      if (!specs) return ''
+      if (typeof specs === 'object') {
+        return Object.entries(specs).map(([k, v]) => k + ':' + v).join('; ')
+      }
+      const text = String(specs).trim()
+      if (!text) return ''
+      try {
+        const parsed = JSON.parse(text)
+        if (Array.isArray(parsed)) {
+          return parsed.map((item, index) => '规格' + (index + 1) + ':' + item).join('; ')
+        }
+        if (parsed && typeof parsed === 'object') {
+          return Object.entries(parsed).map(([k, v]) => k + ':' + v).join('; ')
+        }
+      } catch (e) {
+      }
+      return text
+    },
     normalizeSpecsToJson(specs) {
       if (!specs || !specs.trim()) return '{}'
       const trimmed = specs.trim()
@@ -269,9 +280,9 @@ const baseURL = process.env.VUE_APP_BASE_API || ''
             res = await createProductSku(payload)
           }
           if (res.code === 200) {
+            await this.loadSkus()
             this.$message.success('保存成功')
             this.dialogVisible = false
-            this.loadSkus()
           } else {
             this.$message.error(res.message || '保存失败')
           }