Просмотр исходного кода

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

李宇 3 месяцев назад
Родитель
Сommit
7b2a02ce7f

+ 162 - 0
cfc-backend/src/test/java/com/etotem/cfc/config/FamilyAccessInterceptorTest.java

@@ -0,0 +1,162 @@
+package com.etotem.cfc.config;
+
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.service.UserService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.MockitoAnnotations;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.Collections;
+import java.util.Set;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class FamilyAccessInterceptorTest {
+
+    @Mock
+    private UserService userService;
+
+    @InjectMocks
+    private FamilyAccessInterceptor familyAccessInterceptor;
+
+    private HttpServletResponse response;
+
+    @BeforeEach
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+        response = mock(HttpServletResponse.class);
+    }
+
+    @Test
+    public void testChildAccessValid() {
+        Long userId = 1L;
+        Long familyId = 1L;
+        User user = createMockUser(userId, familyId, "child");
+        when(userService.getEffectiveFamilyIds(userId)).thenReturn(Collections.singleton(familyId));
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/dan-assessment/materials");
+        request.setParameter("familyId", familyId.toString());
+        request.setAttribute("userId", userId);
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertTrue(result);
+        // 验证没有调用参数抛出 403
+    }
+
+    @Test
+    public void testChildAccessInvalid() {
+        Long userId = 1L;
+        Long userFamilyId = 1L;
+        Long requestedFamilyId = 2L;
+        User user = createMockUser(userId, userFamilyId, "child");
+        when(userService.getEffectiveFamilyIds(userId)).thenReturn(Collections.singleton(userFamilyId));
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/dan-assessment/materials");
+        request.setParameter("familyId", requestedFamilyId.toString());
+        request.setAttribute("userId", userId);
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertFalse(result);
+        // 验证发送了 403 响应
+        verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN);
+    }
+
+    @Test
+    public void testTeacherAccessValid() {
+        Long userId = 2L;
+        Long teacherFamilyId = 5L;
+        Long assignedFamilyId = 5L;
+        User user = createMockUser(userId, teacherFamilyId, "teacher");
+        when(userService.getEffectiveFamilyIds(userId)).thenReturn(Collections.singleton(assignedFamilyId));
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/dan-assessment/materials");
+        request.setParameter("familyId", assignedFamilyId.toString());
+        request.setAttribute("userId", userId);
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertTrue(result);
+    }
+
+    @Test
+    public void testMissingFamilyId() {
+        Long userId = 1L;
+        Long familyId = 1L;
+        User user = createMockUser(userId, familyId, "child");
+        when(userService.getEffectiveFamilyIds(userId)).thenReturn(Collections.singleton(familyId));
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/dan-assessment/materials");
+        // 不设置 familyId 则返回 true
+        request.setAttribute("userId", userId);
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertTrue(result);
+    }
+
+    @Test
+    public void testAdminPath() {
+        Long userId = 1L;
+        User user = createMockUser(userId, 1L, "admin");
+        when(userService.getEffectiveFamilyIds(userId)).thenReturn(Collections.singleton(1L));
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/admin/users");
+        // 任何 familyId 都不应该被拒绝,因为 admin path 会被跳过
+        request.setParameter("familyId", "999"); // 不存在的 familyId
+        request.setAttribute("userId", userId);
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertTrue(result);
+    }
+
+    @Test
+    public void testNoUserId() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/dan-assessment/materials");
+        request.setParameter("familyId", "1");
+        request.setAttribute("userId", null); // 没有 userId
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertTrue(result);
+    }
+
+    @Test
+    public void testEmptyEffectiveFamilyIds() {
+        Long userId = 1L;
+        User user = createMockUser(userId, null, "parent");
+        when(userService.getEffectiveFamilyIds(userId)).thenReturn(Collections.emptySet());
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setMethod("POST");
+        request.setRequestURI("/api/dan-assessment/materials");
+        request.setParameter("familyId", "1");
+        request.setAttribute("userId", userId);
+
+        boolean result = familyAccessInterceptor.preHandle(request, response, null);
+        assertTrue(result);
+    }
+
+    private User createMockUser(Long userId, Long familyId, String role) {
+        User user = new User();
+        user.setId(userId);
+        user.setFamilyId(familyId);
+        user.setTeacherFamilyIds(null);
+        return user;
+    }
+}

+ 9 - 0
cfc-frontend/.postcssrc.js

@@ -0,0 +1,9 @@
+// PostCSS config for uni-app
+const path = require('path');
+module.exports = {
+  plugins: {
+    'autoprefixer': {
+      overrideBrowserslist: ['> 1%', 'last 2 versions', 'not dead']
+    }
+  }
+}

+ 47 - 2
cfc-frontend/AGENTS.md

@@ -64,12 +64,56 @@ pages.json 中定义 5 个 TabBar 页面(TabBar 文案,非五维维度名)
 - 四级地址选择: `components/address-picker.vue` - 省市区街道联动
 - 双角色界面: 根据用户角色(parent/child)显示不同UI
 
+## BUILD SYSTEM
+
+### 依赖约束(2026-07-23 修复后锁定)
+
+| 包 | 版本 | 原因 |
+|----|------|------|
+| `@vue/cli-service` | `~4.5.0` | v5 使用 webpack 5,`@dcloudio/uni-mp-weixin` 依赖 `webpack/lib/GraphHelpers`(webpack 4 特有)|
+| `@vue/cli-plugin-babel` | `~4.5.0` | 与 cli-service v4 匹配 |
+| `postcss` (override + dependency) | `7.0.39` | `remove-scoped.js` 在 PostCSS 8 下会走 v8 插件路径,但 `spaces` 属性访问有兼容问题 |
+| `node-sass` | (devDependency) | `@dcloudio/vue-cli-plugin-uni` 内置的 sass-loader 需要 |
+| `copy-webpack-plugin` | `^5.1.2` | 与 cli-service v4 的 webpack 4 兼容 |
+
+### postinstall 自动补丁
+
+`npm install` 后会自动运行 `postinstall-fix.js`,修补 `remove-scoped.js` 中的 `spaces` 崩溃:
+
+```js
+// 问题: selector.first 可能为 undefined
+// 修改前: selector.first.spaces.before = '';
+// 修改后: if (selector.first) { selector.first.spaces.before = ''; }
+```
+
+### 已知编译错误处理
+
+| 错误 | 原因 | 处理 |
+|------|------|------|
+| `Cannot read properties of undefined (reading 'spaces')` in `remove-scoped.js` | PostCSS 节点没有 `spaces` 属性 | postinstall-fix.js 自动补丁 |
+| `Cannot find module 'webpack/lib/GraphHelpers'` | `@vue/cli-service v5` 的 webpack 5 不支持 | 保持 `@vue/cli-service ~4.5.0` |
+| `Cannot find module 'node-sass'` | uni-app sass-loader 需要 | `npm install node-sass --save-dev` |
+| `getPreCompileOptions` DevTools 内部错误 | `dist/dev/mp-weixin/` 缺少 `project.config.json` | 先 `npm run build:mp-weixin` 生成 dist |
+| `Unexpected token` in `.vue` | JS 语法错误,常见于方法被截断/拼接错误 | 检查 v-on:click/方法调用是否正确闭合 |
+
+### 修改 `remove-scoped.js` 的风险
+
+该文件在 `node_modules/@dcloudio/vue-cli-plugin-uni/` 内,每次 `npm install` 会通过 `postinstall-fix.js` 自动恢复补丁。如需手动执行:
+
+```bash
+node postinstall-fix.js
+```
+
+**不要**将 `@vue/cli-service` 升级到 v5,除非:
+- `@dcloudio/uni-mp-weixin` 确认移除了 `webpack/lib/GraphHelpers` 依赖
+- 或者找到替代方法处理独立分包插件
+
 ## COMMANDS
 
 ```bash
-npm install             # 安装依赖
+npm install             # 安装依赖(自动运行 postinstall-fix.js)
 npm run dev:mp-weixin   # 开发模式 (微信小程序)
-npm run build:mp-weixin # 构建生产版本
+npm run build:mp-weixin # 构建生产版本(仅生成 dist,不在 DevTools 里编译)
 ```
 
 ## NOTES
@@ -78,3 +122,4 @@ npm run build:mp-weixin # 构建生产版本
 - 后端API地址: 配置在 `config/index.js` 的 `baseUrl`
 - 地址数据: 使用后端街道API获取四级地址数据
 - 调试工具: 微信开发者工具需配置合法域名
+- **不要在 WeChat DevTools 里直接编译源码**,先 `npm run build:mp-weixin` 生成 `dist/dev/mp-weixin/`,然后在 DevTools 中导入该目录

+ 18 - 20
cfc-frontend/components/ContactCard.vue

@@ -59,8 +59,15 @@ export default {
   data: function() {
     return {
       showInviteModal: false,
-      relationshipTypeList: [],
+      relationshipTypeList: [
+        { typeName: '家人', typeKey: 'family' },
+        { typeName: '朋友', typeKey: 'friend' },
+        { typeName: '伴侣', typeKey: 'partner' },
+        { typeName: '同事', typeKey: 'colleague' },
+        { typeName: '其他', typeKey: 'other' }
+      ],
       selectedRelationshipName: '',
+      selectedRelationshipKey: '',
       generationLevelList: ['父母(+1)', '子女(-1)', '配偶(0)', '兄弟姐妹(0)', '祖父母(+2)', '孙子女(-2)'],
       selectedGeneration: ''
     }
@@ -92,28 +99,22 @@ export default {
   methods: {
     handleInvite: function() {
       this.showInviteModal = true
-      this.loadRelationshipTypes()
-    },
-    loadRelationshipTypes: function() {
-      var that = this
-      var app = getApp()
-      if (!app || !app.globalData || !app.globalData.baseUrl) return
-      uni.request({
-        url: app.globalData.baseUrl + '/api/family/member/relationship-types',
-        method: 'POST',
-        header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') },
-        success: function(res) {
-          if (res.data && res.data.data) {
-            that.relationshipTypeList = res.data.data
-          }
+      // 预填联系人已有的关系类型
+      var self = this
+      this.selectedRelationshipKey = this.contact.relationshipType || 'other'
+      for (var i = 0; i < this.relationshipTypeList.length; i++) {
+        if (this.relationshipTypeList[i].typeKey === this.selectedRelationshipKey) {
+          this.selectedRelationshipName = this.relationshipTypeList[i].typeName
+          break
         }
-      })
+      }
     },
     onRelationshipChange: function(e) {
       var idx = e.detail.value
       var item = this.relationshipTypeList[idx]
       if (item) {
         this.selectedRelationshipName = item.typeName
+        this.selectedRelationshipKey = item.typeKey
       }
     },
     onGenerationChange: function(e) {
@@ -134,9 +135,6 @@ export default {
         '孙子女(-2)': 'grandchild'
       }
       var generationLevel = generationMap[this.selectedGeneration] || 'child'
-      var selectedItem = this.relationshipTypeList.filter(function(item) {
-        return item.typeName === that.selectedRelationshipName
-      })[0]
       var app = getApp()
       if (!app || !app.globalData || !app.globalData.baseUrl) return
       uni.request({
@@ -145,7 +143,7 @@ export default {
         header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') },
         data: {
           contactId: this.contact.id,
-          relationshipType: selectedItem ? selectedItem.typeKey : 'child',
+          relationshipType: this.selectedRelationshipKey || 'other',
           generationLevel: generationLevel
         },
         success: function(res) {

+ 15 - 21
cfc-frontend/components/ContactImport.vue

@@ -49,7 +49,15 @@
 </template>
 
 <script>
-import { importPhoneContact, getRelationshipTypes } from '../utils/api.js'
+import { importPhoneContact } from '../utils/api.js'
+
+var DEFAULT_RELATIONSHIP_TYPES = [
+  { value: 'family', label: '家人' },
+  { value: 'friend', label: '朋友' },
+  { value: 'partner', label: '伴侣' },
+  { value: 'colleague', label: '同事' },
+  { value: 'other', label: '其他' }
+]
 
 export default {
   props: {
@@ -58,6 +66,10 @@ export default {
     phone: { type: String, default: '' }
   },
   data: function() {
+    var defaultLabels = []
+    for (var i = 0; i < DEFAULT_RELATIONSHIP_TYPES.length; i++) {
+      defaultLabels.push(DEFAULT_RELATIONSHIP_TYPES[i].label)
+    }
     return {
       form: {
         name: '',
@@ -68,14 +80,13 @@ export default {
         birthday: '',
         bio: ''
       },
-      relationshipTypes: [],
-      typeLabels: []
+      relationshipTypes: DEFAULT_RELATIONSHIP_TYPES,
+      typeLabels: defaultLabels
     }
   },
   watch: {
     visible: function(val) {
       if (val) {
-        this.loadTypes()
         if (!this.form.name && this.name) {
           this.form.name = this.name
         }
@@ -96,23 +107,6 @@ export default {
     }
   },
   methods: {
-    loadTypes: function() {
-      var self = this
-      getRelationshipTypes().then(function(res) {
-        var labels
-        if (res.code === 200 && Array.isArray(res.data)) {
-          labels = res.data.map(function(item) {
-            return typeof item === 'string' ? item : (item.typeName || item.name || item.label || item.value || '')
-          })
-          self.relationshipTypes = res.data
-          self.typeLabels = labels
-        }
-      }).catch(function() {
-        // fallback to default type options
-        self.typeLabels = ['家人', '朋友', '伴侣', '同事', '其他']
-        self.relationshipTypes = ['family', 'friend', 'partner', 'colleague', 'other']
-      })
-    },
     chooseFromWeChat: function() {
       var self = this
       if (typeof wx !== 'undefined' && wx.chooseContact) {

Разница между файлами не показана из-за своего большого размера
+ 307 - 329
cfc-frontend/package-lock.json


+ 10 - 5
cfc-frontend/package.json

@@ -10,11 +10,12 @@
     "dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-serve",
     "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-build",
     "build:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin UNI_INPUT_DIR=. UNI_CLI_CONTEXT=. vue-cli-service uni-build",
+    "postinstall": "node postinstall-fix.js",
     "postbuild": "node -e \"try{require('fs').unlinkSync('dist/dev/mp-weixin/custom-tab-bar/index.vue')}catch(e){};try{const c=require('./dist/dev/mp-weixin/project.config.json');c.setting=c.setting||{};c.setting.lazyCodeLoading='requiredComponents';require('fs').writeFileSync('./dist/dev/mp-weixin/project.config.json',JSON.stringify(c,null,2))}catch(e){}\""
   },
   "devDependencies": {
-    "@babel/core": "^7.29.7",
-    "@babel/preset-env": "^7.29.7",
+    "@babel/core": "^7.16.0",
+    "@babel/preset-env": "^7.16.0",
     "@dcloudio/uni-app": "^2.0.2-5010420260703001",
     "@dcloudio/uni-cli-i18n": "^2.0.2-5010420260703001",
     "@dcloudio/uni-cli-shared": "^2.0.2-5010420260703001",
@@ -24,12 +25,13 @@
     "@dcloudio/uni-mp-weixin": "^2.0.2-5010420260703001",
     "@dcloudio/uni-template-compiler": "^2.0.2-5010420260703001",
     "@dcloudio/webpack-uni-pages-loader": "^2.0.2-5010420260703001",
-    "@vue/cli-plugin-babel": "^5.0.9",
-    "@vue/cli-service": "^5.0.9",
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
     "@vue/compiler-sfc": "2.7.16",
     "babel-jest": "^26.6.3",
-    "copy-webpack-plugin": "^14.0.0",
+    "copy-webpack-plugin": "^5.1.2",
     "jest": "^26.6.3",
+    "node-sass": "^9.0.0",
     "vue": "2.7.16",
     "vuex": "^3.6.2",
     "yalc": "^1.0.0-pre.53"
@@ -37,10 +39,13 @@
   "dependencies": {
     "@dcloudio/vue-cli-plugin-uni": "^2.0.2-5000720260410001",
     "@dcloudio/webpack-uni-mp-loader": "^2.0.2-5010520260709001",
+    "postcss": "7.0.39",
     "regenerator-runtime": "^0.14.1",
     "yargs-parser": "^22.0.0"
   },
   "overrides": {
+    "postcss": "7.0.39",
+    "postcss-selector-parser": "5.0.0",
     "@vue/compiler-sfc": "2.7.16",
     "@vue/babel-plugin-jsx": "npm:@vue/babel-plugin-jsx@^1.1.1"
   }

+ 9 - 0
cfc-frontend/postcss.config.js

@@ -0,0 +1,9 @@
+// PostCSS config for uni-app mp-weixin build
+// Fixed: ensure compat with @vue/component-compiler-utils (PostCSS 7.x)
+module.exports = {
+  plugins: [
+    require('postcss-import'),
+    require('postcss-url'),
+    require('autoprefixer')
+  ]
+}

+ 82 - 0
cfc-frontend/postinstall-fix.js

@@ -0,0 +1,82 @@
+#!/usr/bin/env node
+/**
+ * Postinstall fix: patch remove-scoped.js to guard unsafe `.spaces` access.
+ *
+ * Root cause: remove-scoped.js in @dcloudio/vue-cli-plugin-uni accesses
+ * `selector.first.spaces.before` without checking if `selector.first` exists.
+ * When postcss-selector-parser produces an empty selector (e.g. from certain
+ * CSS edge cases), this crashes with:
+ *   "TypeError: Cannot read properties of undefined (reading 'spaces')"
+ *
+ * The fix adds a guard before accessing .spaces on selector.first.
+ *
+ * Patches BOTH:
+ *   1. Project local node_modules (for vue-cli-service / npm build)
+ *   2. HBuilderX bundled uniapp-cli (for HBuilderX build)
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+/** Find all remove-scoped.js files to patch */
+function findRemoveScopedFiles() {
+  const files = [];
+
+  // 1. Project local node_modules
+  const localPath = path.join(__dirname, 'node_modules', '@dcloudio',
+    'vue-cli-plugin-uni', 'packages', '@vue', 'component-compiler-utils',
+    'dist', 'stylePlugins', 'remove-scoped.js');
+  if (fs.existsSync(localPath)) files.push(localPath);
+
+  // 2. HBuilderX bundled uniapp-cli (common install paths)
+  const hbxPaths = [
+    'E:\\develop\\HBuilderX\\plugins\\uniapp-cli',
+    'C:\\Program Files\\HBuilderX\\plugins\\uniapp-cli',
+    'C:\\Program Files (x86)\\HBuilderX\\plugins\\uniapp-cli',
+    path.join(process.env.LOCALAPPDATA || '', 'Programs', 'HBuilderX', 'plugins', 'uniapp-cli'),
+  ];
+  for (const base of hbxPaths) {
+    const hbxPath = path.join(base, 'node_modules', '@dcloudio',
+      'vue-cli-plugin-uni', 'packages', '@vue', 'component-compiler-utils',
+      'dist', 'stylePlugins', 'remove-scoped.js');
+    if (fs.existsSync(hbxPath)) files.push(hbxPath);
+  }
+
+  return files;
+}
+
+/** Patch a single remove-scoped.js file */
+function patchFile(filePath) {
+  let content = fs.readFileSync(filePath, 'utf8');
+
+  // Fix: selector.first.spaces.before = ''; → if (selector.first) { ... }
+  if (content.includes("selector.first.spaces.before = '';")) {
+    content = content.replace(
+      /selector\.first\.spaces\.before = '';/,
+      "if (selector.first) { selector.first.spaces.before = ''; }"
+    );
+    fs.writeFileSync(filePath, content, 'utf8');
+    console.log(`  ✅ ${filePath}`);
+    return true;
+  } else if (content.includes("if (selector.first) { selector.first.spaces.before = ''; }")) {
+    console.log(`  ⏭️  ${filePath} (already patched)`);
+    return true;
+  } else {
+    console.log(`  ❌ ${filePath} (unexpected format)`);
+    return false;
+  }
+}
+
+// Main
+const files = findRemoveScopedFiles();
+if (files.length === 0) {
+  console.log('remove-scoped.js not found in any location, skipping patch');
+  process.exit(0);
+}
+
+console.log(`Found ${files.length} remove-scoped.js to patch:`);
+let patched = 0;
+for (const f of files) {
+  if (patchFile(f)) patched++;
+}
+console.log(`Done: ${patched}/${files.length} patched`);

+ 0 - 4
cfc-frontend/utils/api.js

@@ -341,10 +341,6 @@ export const updateFamilyMember = (data) => {
   return request('/api/family/member/update', 'POST', data)
 }
 
-export const getRelationshipTypes = () => {
-  return request('/api/family/member/relationship-types', 'POST')
-}
-
 // 任务模块
 export const createTask = (data) => {
   return request('/api/tasks', 'POST', data)

+ 11 - 0
cfc-frontend/vue.config.js

@@ -0,0 +1,11 @@
+// vue.config.js - disable thread-loader for compatibility
+module.exports = {
+  parallel: false, // Ensure we use the correct source directory
+  pages: undefined,
+  configureWebpack: {
+    resolve: {
+      extensions: ['.vue', '.js', '.json'], // Ensure .vue files resolve correctly
+    },
+    node: false,
+  },
+}

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-bdafd52675a7e9f04346850d7bdd90ce69fb46b9
+671338de8db38cffffb9d71ebe015a98105bdb2f

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.494",
+  "version": "1.0.495",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 62 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,68 @@
 
 ---
 
+## v1.0.495 (2026-07-23)
+
+### 文档
+- update TASK-P0-001 status to 已实施
+- PROJECT-OVERVIEW v1.3 - 辈分重构全栈完成 + 状态同步
+- update PROJECT-OVERVIEW.md v1.3 - 辈分重构全栈完成状态同步
+
+### Bug 修复
+- remove dead getRelationshipTypes API after P0-001 backend removal
+- extend postinstall-fix.js to also patch HBuilderX bundled remove-scoped.js
+- revert to @vue/cli-service v4 + patch remove-scoped.js via postinstall
+- Fix:
+- fix syntax error in parent-index.vue - loadAlerts + handleDismissAlert
+- restore @vue/cli-service v5, remove PostCSS config files
+- Fix:
+- restore @vue/cli-service to v5 to fix WeChat DevTools compile error
+- use ES module default-export for generationLevel constants
+
+### 其他
+- type list (family/friend/partner/colleague/other), matching backend removal of
+- /relationship-types endpoint and relationship_types table.
+- 
+- v4 alone had remove-scoped.js crash on empty selector (spaces access).
+- - Keep @vue/cli-service ~4.5.0, postcss 7.0.39 pinned
+- - Add postinstall-fix.js to patch remove-scoped.js selector.first guard
+- - Add node-sass as devDependency (needed by uni-app sass-loader)
+- - Restore .postcssrc.js, postcss.config.js, vue.config.js
+- - Document build constraints in AGENTS.md
+- 
+- 导致 webpack-uni-mp-loader/script.js 编译失败。
+- - 正确闭合 loadAlerts 的 .then() 链
+- - 将 dismissAlert 调用抽出为独立的 handleDismissAlert(alertId) 方法
+- 
+- added postcss:^8.4.49 as a direct dependency, causing remove-scoped.js
+- (Cannot read properties of undefined (reading 'spaces')) crash.
+- - @vue/cli-service ^4.5.19 -> ^5.0.9
+- - copy-webpack-plugin ^5.1.2 -> ^14.0.0
+- - Remove .postcssrc.js, postcss.config.js, vue.config.js
+- - Remove postcss from dependencies (v5 brings its own)
+- - Remove postcss/postcss-selector-parser from overrides
+- 
+- - 删除 RelationshipPicker.vue: 已被 GenerationPicker.vue 替代
+- 
+- - @vue/cli-plugin-babel: ~4.5.0 → ^5.0.9
+- - copy-webpack-plugin: ^5.1.2 → ^14.0.0
+- - @babel/core/preset-env: ^7.16.0 → ^7.29.7
+- - Remove override @vue/babel-plugin-resolve-type@1.0.0 (does not exist on npm)
+- Root cause: downgrade to @vue/cli-service v4 in 6a4d0496 triggered
+- remove-scoped.js crash in WeChat DevTools build pipeline.
+- 
+- - 创建 WealthSubScores.vue 组件展示富维度三子维度详情(家长/孩子角色感礁)
+- - 更新 wealth/index.vue: 导入两个新组件 + data添加bodyWealthStatus/Message字段 + API加载逻辑
+- - 修复 DatabaseInitializer.java: runMigration100方法缺少闭合括号导致编译失败
+- TASK-P0-002 前端实施完成,后端EnergyService子维度计算已由先前提交完成
+- 
+
+### 新功能
+- 推广佣金体系后端实体 + 删除废弃 RelationshipPicker 组件
+- TASK-P0-001 辈分重构 — 订阅式 generation + is_spouse,删除关系类型表与字段
+- 富维度子维度组件 + 身克富杠杆展示 + 修复DatabaseInitializer编译错误
+
+
 ## v1.0.494 (2026-07-22)
 
 ### 其他

+ 63 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.494
+> 当前版本: v1.0.495
 
 ## 历史版本
 
@@ -8,6 +8,68 @@
 
 ---
 
+## v1.0.495 (2026-07-23)
+
+### 文档
+- update TASK-P0-001 status to 已实施
+- PROJECT-OVERVIEW v1.3 - 辈分重构全栈完成 + 状态同步
+- update PROJECT-OVERVIEW.md v1.3 - 辈分重构全栈完成状态同步
+
+### Bug 修复
+- remove dead getRelationshipTypes API after P0-001 backend removal
+- extend postinstall-fix.js to also patch HBuilderX bundled remove-scoped.js
+- revert to @vue/cli-service v4 + patch remove-scoped.js via postinstall
+- Fix:
+- fix syntax error in parent-index.vue - loadAlerts + handleDismissAlert
+- restore @vue/cli-service v5, remove PostCSS config files
+- Fix:
+- restore @vue/cli-service to v5 to fix WeChat DevTools compile error
+- use ES module default-export for generationLevel constants
+
+### 其他
+- type list (family/friend/partner/colleague/other), matching backend removal of
+- /relationship-types endpoint and relationship_types table.
+- 
+- v4 alone had remove-scoped.js crash on empty selector (spaces access).
+- - Keep @vue/cli-service ~4.5.0, postcss 7.0.39 pinned
+- - Add postinstall-fix.js to patch remove-scoped.js selector.first guard
+- - Add node-sass as devDependency (needed by uni-app sass-loader)
+- - Restore .postcssrc.js, postcss.config.js, vue.config.js
+- - Document build constraints in AGENTS.md
+- 
+- 导致 webpack-uni-mp-loader/script.js 编译失败。
+- - 正确闭合 loadAlerts 的 .then() 链
+- - 将 dismissAlert 调用抽出为独立的 handleDismissAlert(alertId) 方法
+- 
+- added postcss:^8.4.49 as a direct dependency, causing remove-scoped.js
+- (Cannot read properties of undefined (reading 'spaces')) crash.
+- - @vue/cli-service ^4.5.19 -> ^5.0.9
+- - copy-webpack-plugin ^5.1.2 -> ^14.0.0
+- - Remove .postcssrc.js, postcss.config.js, vue.config.js
+- - Remove postcss from dependencies (v5 brings its own)
+- - Remove postcss/postcss-selector-parser from overrides
+- 
+- - 删除 RelationshipPicker.vue: 已被 GenerationPicker.vue 替代
+- 
+- - @vue/cli-plugin-babel: ~4.5.0 → ^5.0.9
+- - copy-webpack-plugin: ^5.1.2 → ^14.0.0
+- - @babel/core/preset-env: ^7.16.0 → ^7.29.7
+- - Remove override @vue/babel-plugin-resolve-type@1.0.0 (does not exist on npm)
+- Root cause: downgrade to @vue/cli-service v4 in 6a4d0496 triggered
+- remove-scoped.js crash in WeChat DevTools build pipeline.
+- 
+- - 创建 WealthSubScores.vue 组件展示富维度三子维度详情(家长/孩子角色感礁)
+- - 更新 wealth/index.vue: 导入两个新组件 + data添加bodyWealthStatus/Message字段 + API加载逻辑
+- - 修复 DatabaseInitializer.java: runMigration100方法缺少闭合括号导致编译失败
+- TASK-P0-002 前端实施完成,后端EnergyService子维度计算已由先前提交完成
+- 
+
+### 新功能
+- 推广佣金体系后端实体 + 删除废弃 RelationshipPicker 组件
+- TASK-P0-001 辈分重构 — 订阅式 generation + is_spouse,删除关系类型表与字段
+- 富维度子维度组件 + 身克富杠杆展示 + 修复DatabaseInitializer编译错误
+
+
 ## v1.0.494 (2026-07-22)
 
 ### 其他

+ 19 - 21
docs/superpowers/PROJECT-OVERVIEW.md

@@ -1,8 +1,8 @@
 # 浠艾福 项目全景 — 阶段性需求与设计汇总
 
-**文档版本:** v1.3
+**文档版本:** v1.4
 **日期:** 2026-07-23
-**状态:** 已确认(v1.3 辈分重构全栈完成 + 审核校正)
+**状态:** 已确认(v1.4 全量状态审计 + 取消项归档)
 **维护:** 所有需求变更需更新本文档
 
 ---
@@ -34,7 +34,7 @@
 | **Phase 2-4** | 代码已写(Chat/Analysis/Recommend+RAG+Memory+Monitoring),前端集成待实施 | `plans/2026-07-22-comprehensive-implementation.md` |
 
 **核心设计:** `specs/2026-07-20-langgraph-migration-design.md`
-**需求汇总:** `specs/2026-07-20-langgraph-requirements-summary.md` ⚠️ **文件缺失**
+**需求汇总:** `specs/2026-07-20-langgraph-requirements-summary.md` 🔄 已合并至 langgraph-migration-design.md
 
 ---
 
@@ -58,7 +58,7 @@
 ### 2.3 家庭成员体系 ⭐ P1
 
 **阶段:** Phase 1
-**状态:** 🚧 进行中
+**状态:** ✅ 已完成(辈分重构全栈 + 关系图谱 + 成员卡片 + 维度区全部交付)
 
 | 功能 | 状态 | 关联文档 |
 |------|:----:|---------|
@@ -92,8 +92,8 @@
 | 菌群报告页面重构(200+菌属全覆盖) | Phase 1 | ✅ 已完成(4页面 1214行 + HealthKnowledgeBase后端) | `specs/2026-07-17-flora-microbiome-report-page-design.md` + `plans/2026-07-17-flora-microbiome-report-implementation.md` |
 | 身体健康7维子体系模型 | Phase 4 | 🟡 计划中 | `specs/2026-06-26-health-dimensions-design-spec.md` |
 | 成长档案重建 | Phase 4 | 🟡 计划中 | `specs/2026-06-22-growth-record-rebuild-design.md` |
-| 营养健康部分缺陷修复 | Phase 1 | 🟡 计划中 | `plans/2026-06-22-营养健康部分缺陷修复计划.md` |
-| 主动健康六阶模型改进(P0-P3) | Phase 5 | 🚧 设计稿 | `specs/2026-07-22-active-health-six-stage-design.md` (设计总纲) |
+| 营养健康部分缺陷修复 | Phase 1 | ✅ 已实施(5项缺陷已修复) | `plans/2026-06-22-营养健康部分缺陷修复计划.md` |
+| 主动健康六阶模型改进(P0-P3) | Phase 5 | ✅ P0/P1-1/P1-2/P2-1已完成, P2-2 ⏳ P3 ⏳ | `specs/2026-07-22-active-health-six-stage-design.md` (设计总纲) |
 
 ---
 
@@ -104,7 +104,7 @@
 | 功能 | 阶段 | 状态 | 关联文档 |
 |------|:----:|:----:|---------|
 | 五维 TabBar 页面统一改造 | Phase 1 | ✅ 核心完成(body 843行/mind 1367行/wisdom 783行/wealth 780行/action 755行,均有真实UI) | `specs/2026-06-13-five-dimension-pages-redesign.md` |
-| DAN测评结果展示+六维雷达图 | Phase 1 | 🟡 已上线(双Tab) | — |
+| DAN测评结果展示+六维雷达图 | Phase 1 | ✅ 已上线(双Tab) | — |
 | DAN测评执行记录双角色模型(assessor/planner分离) | Phase 1 | ✅ 已实施(4 commits:实体+Service+Controller+前端) | — |
 | 认知测评题目录入优化 | Phase 1 | 🟡 计划中 | `plans/2026-07-13-dan-assessment-phase2-5.md` |
 
@@ -157,7 +157,7 @@
 | 文章中心主页(发现页重建) | ✅ 已完成(4页面 1429行 + ArticleController + DB表) | `specs/2026-07-03-article-center-design.md` + `plans/2026-07-03-article-center-implementation.md` |
 | 文章发布系统(运营审核流程) | 🟡 计划中(Phase 3 Wave 3) | `plans/2026-07-22-comprehensive-implementation.md` |
 | 文章与活动发布系统(合并版) | 🟡 设计稿 | `specs/2026-06-09-article-activity-publishing-design.md` |
-| 公众号内容发布(Dify工作流) | 🟡 草稿 | — |
+| 公众号内容发布(Dify工作流) | ❌ 已取消(LangGraph 已取代 Dify) | — |
 
 ---
 
@@ -197,7 +197,7 @@
 | 权限系统升级(多角色合并模式) | Phase 1 | 🟡 设计稿 | `specs/2026-06-20-web-permission-system-design.md` + `plans/2026-06-20-web-permission-system.md` |
 | Web端E2E测试修复 | Phase 1 | ✅ 已结束 | `plans/2026-06-20-cfc-web-e2e-test-fix.md` |
 | Web端重构(代码质量) | Phase 1 | ✅ 已结束 | `plans/2026-06-20-cfc-web-refactor.md` |
-| 维度知识库动态管理平台 | Phase 1 | 🟡 设计完成,待实施 | `specs/2026-06-20-dimension-knowledge-base-design.md` + `plans/2026-06-20-dimension-knowledge-base-plan.md` |
+| 维度知识库动态管理平台 | Phase 1 | ✅ 已实施(KnowledgeBaseController+KnowledgeTagController+admin/knowledge/index.vue+dimension/index.vue) | `specs/2026-06-20-dimension-knowledge-base-design.md` + `plans/2026-06-20-dimension-knowledge-base-plan.md` |
 | 登录页左侧品牌区重设计 | Phase 1 | ✅ 已确认 | `specs/2026-07-02-login-left-panel-redesign.md` |
 
 ---
@@ -246,10 +246,10 @@
 | 类别 | 项 | 优先级 | 关联 |
 |------|-------|:------:|------|
 | **安全** | 确定性缺口修复 | P1 | `plans/2026-07-10-determinism-gap-fix-plan.md` |
-| **Bug** | 维度页面差距修复(Phase 2) | P1 | `plans/2026-07-12-dimension-pages-gap-fix.md` |
+| **Bug** | 维度页面差距修复(Phase 1+2 ✅已实施, Phase 3 富重建待实施) | P1 | `plans/2026-07-12-dimension-pages-gap-fix.md` |
 | **Bug** | 供应商体系缺口修复 | P2 | `plans/2026-07-13-supplier-system-gap-fix.md` |
 | **Bug** | 家庭关系图修复 | P2 | `plans/2026-06-22-family-relation-graph-redesign.md` |
-| **Bug** | 营养健康部分缺陷修复 | P2 | `plans/2026-06-22-营养健康部分缺陷修复计划.md` |
+| **Bug** | 营养健康部分缺陷修复 | P2 ✅ 已实施 | `plans/2026-06-22-营养健康部分缺陷修复计划.md` |
 
 ---
 
@@ -275,7 +275,7 @@
 |------|:----:|------|
 | `2026-07-22-comprehensive-implementation.md` | ✅ 当前 | 全面实施计划(Phase 2-4) |
 | `2026-07-20-langgraph-migration-design.md` | ✅ 当前 | LangGraph 架构设计 |
-| `2026-07-20-langgraph-requirements-summary.md` | ⚠️ 文件缺失 | 需求与决策汇总 |
+| `2026-07-20-langgraph-requirements-summary.md` | 🔄 已合并至 langgraph-migration-design.md | 需求与决策汇总 |
 | `2026-06-05-five-dimension-energy-design.md` | ✅ 已实施 | 五维能量系统(核心) |
 | `2026-06-08-five-dimension-wuxing-philosophy.md` | ✅ 已实施 | 五行哲学体系 |
 | `2026-07-12-wuxing-ke-full-design.md` | ✅ 已实施 | 五维相克完整设计 |
@@ -284,13 +284,13 @@
 | `2026-06-19-family-relation-graph-design.md` | ✅ 已实施(FamilyRelationGraph.vue 797行) | 家庭关系图谱 |
 | `2026-06-22-family-relation-graph-redesign.md` | ✅ 已实施(10+页面集成) | FamilyRelationGraph重构 |
 | `2026-07-07-家庭成员关系视角转换设计.md` | ✅ 全栈完成 | 辈分重构设计(GenerationLevel.java + FamilyMemberService + GenerationPicker.vue + generationLevel.js) |
-| `2026-07-03-article-center-design.md` | 🚧 进行中 | 文章中心设计 |
+| `2026-07-03-article-center-design.md` | ✅ 已完成 | 文章中心设计 |
 | `2026-07-09-family-membership-design.md` | ✅ 已实施(Service+Controller已完成,前端待实现) | 家庭会员体系 |
 | `2026-07-14-family-promotion-redesign.md` | 🚧 后端完成,前端基础就绪 | 家庭推广体系 |
-| `2026-07-17-flora-microbiome-report-page-design.md` | 🚧 实施中 | 菌群报告页面(gut-flora-detail.vue已修改) |
+| `2026-07-17-flora-microbiome-report-page-design.md` | ✅ 已完成 | 菌群报告页面(4页面 1214行 + HealthKnowledgeBase后端) |
 | `2026-06-24-cfc-ecommerce-extension-design.md` | ✅ 已结束(Product体系已完整可用) | 电商原生扩展 |
 | `2026-07-05-supply-distribution-system-design.md` | 🟡 待实施 | 供应商销售体系 |
-| `2026-06-20-dimension-knowledge-base-design.md` | 🟡 待实施 | 维度知识库平台 |
+| `2026-06-20-dimension-knowledge-base-design.md` | ✅ 已实施 | 维度知识库平台 |
 | `2026-06-20-web-permission-system-design.md` | 🟡 设计稿 | Web权限系统 |
 | `2026-06-26-health-dimensions-design-spec.md` | 🟡 设计稿 | 身体健康7维模型 |
 | `2026-06-22-growth-record-rebuild-design.md` | 🟡 待评审 | 成长档案重建 |
@@ -306,7 +306,7 @@
 | `2026-06-04-pre-login-discovery-design.md` | ✅ 已完成(多页面游客态+wuxing-sandbox preview) | 登录前浏览 |
 | `2026-06-02-care-family-club-design.md` | 🟡 设计稿 | 平台升级总纲 |
 | `2026-07-12-dimension-features-energy-map.md` | 🟡 设计稿 | 维度功能能量映射 |
-| `2026-07-22-active-health-six-stage-design.md` | 🚧 设计中 | 主动健康六阶模型改进总设计 |
+| `2026-07-22-active-health-six-stage-design.md` | 🟡 当前(P0/P1-1/P1-2/P2-1 ✅已完成, P2-2/P3 ⏳) | 主动健康六阶模型改进总设计 |
 | `2026-07-10-certainty-ppt-design.md` | 🟡 设计稿 | 确定性叙事PPT |
 
 ### 实施计划(plans/)
@@ -323,7 +323,7 @@
 | `2026-07-14-family-promotion.md` | 🚧 P1后端完成,P2-P4待实施(前端基础页面已存在) | 家庭推广体系 |
 | `2026-07-13-dan-assessment-phase2-5.md` | 🟡 计划中 | DAN测评优化 |
 | `2026-07-12-wealth-dimension-redesign.md` | 🔄 合并至 comprehensive plan P3-WEALTH | 富维度重构 |
-| `2026-07-12-dimension-pages-gap-fix.md` | 🟡 实施中 | 维度页面缺口修复 |
+| `2026-07-12-dimension-pages-gap-fix.md` | ✅ Phase1+2已实施, Phase3富重建待实施 | 维度页面缺口修复 |
 | `2026-07-09-family-membership-core.md` | ✅ 已实施(MemberSubscriptionService + MembershipController已完成) | 家庭会员P0 |
 | `2026-07-09-family-membership-ai-butler.md` | ⏳ 待开发(Phase 2 Wave 3) | AI管家P1 |
 | `2026-07-07-家庭成员辈分重构实施计划.md` | ✅ 全栈完成(GenerationLevel.java + FamilyMemberService + GenerationPicker.vue + generationLevel.js) | 辈分重构 |
@@ -334,8 +334,8 @@
 | `2026-06-27-health-dimensions-incremental-plan.md` | 🟡 计划中(Phase 4) | 健康维度增量 |
 | `2026-06-26-health-dimensions-refactor.md` | 🟡 计划中(Phase 4) | 健康维度重构 |
 | `2026-06-22-family-relation-graph-redesign.md` | ✅ 已完成(FamilyRelationGraph.vue 797行,10+页面使用) | 关系图谱重构 |
-| `2026-06-22-营养健康部分缺陷修复计划.md` | 🟡 计划中(Phase 2) | 营养健康修复 |
-| `2026-06-20-dimension-knowledge-base-plan.md` | 🟡 计划中(Phase 3) | 知识库平台 |
+| `2026-06-22-营养健康部分缺陷修复计划.md` | ✅ 已实施(5项缺陷已修复) | 营养健康修复 |
+| `2026-06-20-dimension-knowledge-base-plan.md` | ✅ 已实施 | 知识库平台 |
 | `2026-06-20-web-permission-system.md` | ✅ 已结束 | 权限系统 |
 | `2026-06-20-cfc-web-e2e-test-fix.md` | ✅ 已结束 | Web端E2E测试 |
 | `2026-06-20-cfc-web-refactor.md` | ✅ 已结束 | Web端重构 |
@@ -383,8 +383,6 @@
 | **P2-1** | 身份重塑系统 | ④ 身份重塑 | 5级称号+健康积分+时间线叙事,从执行者到健康达人 | ✅ |
 | **P2-2** | 家庭健康互动 | ⑤ 意见领袖化 | 排行榜+点赞+挑战,家庭内部互促 | ⏳ |
 | **P3** | 健康社区扩散 | ⑥ 社会扩散 | 健康圈+分享页,家庭→社区扩散 | ⏳ |
-| **P2-2** | 家庭健康互动 | ⑤ 意见领袖化 | 排行榜+点赞+挑战,家庭内部互促 |
-| **P3** | 健康社区扩散 | ⑥ 社会扩散 | 健康圈+分享页,家庭→社区扩散 |
 
 ### 新增文件总览
 

+ 1 - 1
docs/superpowers/tickets/TASK-P0-001-generation-refactor.md

@@ -3,7 +3,7 @@
 **任务 ID:** TASK-P0-001  
 **优先级:** P0(阻塞性)  
 **预计工时:** 3-5 天(后端 3d + 前端 2d)  
-**状态:** 待开始  
+**状态:** 已实施  
 **负责人:** 待分配  
 **关联文档:** `plans/2026-07-07-家庭成员辈分重构实施计划.md`
 

Разница между файлами не показана из-за своего большого размера
+ 277 - 220
docs/系统测试/test-cases.js


Некоторые файлы не были показаны из-за большого количества измененных файлов