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

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

liaoxg 20 часов назад
Родитель
Сommit
89adf93c55

+ 24 - 1
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -8747,6 +8747,29 @@ sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min,
 
         // 迁移225: articles 添加 growth_category 列(成长分类筛选,Article 实体已含字段,生产库缺列导致 Unknown column 'growth_category')
         ensureColumn("articles", "growth_category", "VARCHAR(50) DEFAULT NULL COMMENT '成长分类'");
+
+        // 迁移209: 将 ecom_supplier 合并至 supply_system(商品供应商管理 → 供应商体系管理统一)
+        // 1. 给 supply_system 加联系人字段
+        ensureColumn("supply_system", "contact_name", "VARCHAR(50) DEFAULT NULL COMMENT '联系人'");
+        ensureColumn("supply_system", "contact_phone", "VARCHAR(20) DEFAULT NULL COMMENT '联系电话'");
+        ensureColumn("supply_system", "remark", "VARCHAR(500) DEFAULT NULL COMMENT '备注'");
+        log.info("迁移209: supply_system 已添加 contact_name/contact_phone/remark 列");
+
+        // 2. 将 ecom_supplier 存量数据迁移到 supply_system(以平台管理员 id=1 为默认 admin)
+        try {
+            jdbcTemplate.update(
+                    "INSERT INTO supply_system (name, admin_id, settlement_period_days, platform_profit_rate, status, " +
+                            "contact_name, contact_phone, remark, created_at, updated_at) " +
+                            "SELECT name, 1, 30, 0, " +
+                            "CASE WHEN status = 1 THEN 'active' ELSE 'disabled' END, " +
+                            "contact_name, contact_phone, remark, created_at, COALESCE(updated_at, created_at) " +
+                            "FROM ecom_supplier WHERE NOT EXISTS (" +
+                            "  SELECT 1 FROM supply_system ss WHERE ss.name = ecom_supplier.name LIMIT 1)"
+            );
+            log.info("迁移209: ecom_supplier 存量数据已迁移至 supply_system");
+        } catch (Exception e) {
+            log.warn("迁移209: ecom_supplier 迁移失败(可能已存在): {}", e.getMessage());
+        }
     }
 
     private void runFullRegionDataMigrations() {
@@ -9769,7 +9792,7 @@ sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min,
                 {"commerce", "productManage", "", "商品管理", "/product-manage", "el-icon-s-goods", "commerce:products,commerce:self", "0"},
                 {"commerce", "inventory", "", "库存管理", "/inventory", "el-icon-document", "inventory:self", "1"},
                 {"commerce", "orderManage", "", "订单管理", "/order-manage", "el-icon-s-order", "commerce:orders,order:self", "2"},
-                {"commerce", "ecomSupplier", "", "供应商管理", "/ecom-supplier", "el-icon-s-shop", "commerce:supplier", "3"},
+                {"commerce", "ecomSupplier", "", "供应商管理", "/supply-system", "el-icon-s-shop", "system:supply", "3"},
                 {"commerce", "productProfitRate", "", "产品利润率", "/product-profit-rate", "el-icon-data-line", "finance:profit-rate", "4"},
                 {"commerce", "coupon", "", "优惠券管理", "/coupon", "el-icon-ticket", "commerce:coupon", "5"},
                 {"commerce", "couponGrantLog", "", "发券记录", "/coupon-grant-log", "el-icon-document", "commerce:coupon", "6"},

+ 16 - 97
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminSupplierController.java

@@ -1,123 +1,42 @@
 package com.etotem.cfc.controller.admin;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.EcomSupplier;
-import com.etotem.cfc.mapper.EcomSupplierMapper;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
-import java.util.Date;
-import java.util.HashMap;
 import java.util.Map;
-import com.etotem.cfc.util.SortUtil;
-import com.etotem.cfc.util.ParamUtils;
 
-@Tag(name = "管理端-电商供应商管理")
+/**
+ * @deprecated 已合并至 /api/admin/supply-system(供应商体系管理)。此接口返回 410。
+ */
+@Deprecated
+@Tag(name = "管理端-电商供应商管理(已废弃)")
 @RestController
 @RequestMapping("/api/admin/supplier")
 public class AdminSupplierController {
 
-    @Resource
-    private EcomSupplierMapper ecomSupplierMapper;
-
-    @Operation(summary = "供应商列表")
+    @Operation(summary = "【已废弃】供应商列表,请使用 /api/admin/supply-system/list")
     @PostMapping("/list")
     public Result<Map<String, Object>> list(@RequestBody Map<String, Object> params) {
-        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
-        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
-        String keyword = (String) params.get("keyword");
-
-        Page<EcomSupplier> pageParam = new Page<>(page, size);
-        LambdaQueryWrapper<EcomSupplier> wrapper = new LambdaQueryWrapper<>();
-        if (keyword != null && !keyword.isEmpty()) {
-            wrapper.like(EcomSupplier::getName, keyword);
-        }
-        wrapper.orderByDesc(EcomSupplier::getCreatedAt);
-        SortUtil.applySort(wrapper);
-        Page<EcomSupplier> result = ecomSupplierMapper.selectPage(pageParam, wrapper);
-
-        Map<String, Object> data = new HashMap<>();
-        data.put("records", result.getRecords());
-        data.put("total", result.getTotal());
-        data.put("page", page);
-        data.put("size", size);
-        return Result.success(data);
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/list");
     }
 
-    @Operation(summary = "新增供应商(兼容新旧字段契约)")
+    @Operation(summary = "【已废弃】新增供应商")
     @PostMapping("/save")
-    public Result<EcomSupplier> save(@RequestBody Map<String, Object> body) {
-        EcomSupplier supplier = new EcomSupplier();
-        applyFields(supplier, body);
-        if (supplier.getName() == null || supplier.getName().trim().isEmpty()) {
-            return Result.error("供应商名称不能为空");
-        }
-        supplier.setCreatedAt(new Date());
-        supplier.setUpdatedAt(new Date());
-        ecomSupplierMapper.insert(supplier);
-        return Result.success(supplier);
+    public Result<Map<String, Object>> save(@RequestBody Map<String, Object> body) {
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/create");
     }
 
-    @Operation(summary = "更新供应商(兼容新旧字段契约)")
+    @Operation(summary = "【已废弃】更新供应商")
     @PostMapping("/update")
-    public Result<String> update(@RequestBody Map<String, Object> body) {
-        Long id = ParamUtils.getLong(body.get("id"));
-        if (id == null) return Result.error("id不能为空");
-        EcomSupplier supplier = ecomSupplierMapper.selectById(id);
-        if (supplier == null) return Result.error("供应商不存在");
-        applyFields(supplier, body);
-        supplier.setUpdatedAt(new Date());
-        ecomSupplierMapper.updateById(supplier);
-        return Result.success("ok");
-    }
-
-    /**
-     * 字段契约兼容:前端表单提交 {phone,nickname,realName,vendorType,vendorStatus},
-     * 库表字段为 {name,contactName,contactPhone,status,remark}。新旧字段都接受,新字段优先。
-     */
-    private void applyFields(EcomSupplier supplier, Map<String, Object> body) {
-        String name = str(body, "nickname", body.get("name"));
-        if (name != null) supplier.setName(name);
-        String contactName = str(body, "realName", body.get("contactName"));
-        if (contactName != null) supplier.setContactName(contactName);
-        String contactPhone = str(body, "phone", body.get("contactPhone"));
-        if (contactPhone != null) supplier.setContactPhone(contactPhone);
-        String remark = str(body, "remark", null);
-        if (remark != null) supplier.setRemark(remark);
-        Object vendorStatus = body.get("vendorStatus") != null ? body.get("vendorStatus") : body.get("status");
-        if (vendorStatus != null) {
-            supplier.setStatus(parseStatus(vendorStatus));
-        }
-    }
-
-    private static String str(Map<String, Object> body, String key, Object fallback) {
-        Object v = body.get(key);
-        if (v == null) v = fallback;
-        return v != null ? v.toString() : null;
-    }
-
-    private static Integer parseStatus(Object v) {
-        if (v instanceof Number) return ((Number) v).intValue();
-        String s = v.toString().trim();
-        if ("active".equalsIgnoreCase(s) || "enabled".equalsIgnoreCase(s) || "1".equals(s)) return 1;
-        if ("pending".equalsIgnoreCase(s) || "disabled".equalsIgnoreCase(s) || "0".equals(s)) return 0;
-        try {
-            return Integer.valueOf(s);
-        } catch (NumberFormatException e) {
-            return 1;
-        }
+    public Result<Map<String, Object>> update(@RequestBody Map<String, Object> body) {
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/update");
     }
 
-    @Operation(summary = "删除供应商")
+    @Operation(summary = "【已废弃】删除供应商")
     @PostMapping("/delete")
-    public Result<String> delete(@RequestBody Map<String, Object> params) {
-        Long id = ParamUtils.getLong(params.get("id"));
-        if (id == null) return Result.error("id不能为空");
-        ecomSupplierMapper.deleteById(id);
-        return Result.success("ok");
+    public Result<Map<String, Object>> delete(@RequestBody Map<String, Object> params) {
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/toggle-status");
     }
 }

+ 18 - 25
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/EcomSupplierController.java

@@ -1,56 +1,49 @@
 package com.etotem.cfc.controller.admin;
 
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.EcomSupplier;
-import com.etotem.cfc.service.EcomSupplierService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
 import java.util.List;
 import java.util.Map;
-import com.etotem.cfc.util.ParamUtils;
 
-@Tag(name = "管理端-电商供应商")
+/**
+ * @deprecated 已合并至 /api/admin/supply-system(供应商体系管理)。此接口返回 410。
+ */
+@Deprecated
+@Tag(name = "管理端-电商供应商(已废弃)")
 @RestController
 @RequestMapping("/api/admin/ecom-supplier")
 public class EcomSupplierController {
 
-    @Resource
-    private EcomSupplierService supplierService;
-
-    @Operation(summary = "供应商列表")
+    @Operation(summary = "【已废弃】供应商列表,请使用 /api/admin/supply-system/list")
     @PostMapping("/list")
     public Result<Map<String, Object>> list(@RequestBody Map<String, Object> params) {
-        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
-        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
-        String keyword = (String) params.get("keyword");
-        return supplierService.list(page, size, keyword);
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/list");
     }
 
-    @Operation(summary = "所有供应商(下拉选择用)")
+    @Operation(summary = "【已废弃】所有供应商(下拉选择用)")
     @PostMapping("/all")
-    public Result<List<EcomSupplier>> all() {
-        return supplierService.all();
+    public Result<List<Object>> all() {
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/list-all");
     }
 
-    @Operation(summary = "创建供应商")
+    @Operation(summary = "【已废弃】创建供应商")
     @PostMapping("/save")
-    public Result<String> save(@RequestBody EcomSupplier supplier) {
-        return supplierService.save(supplier);
+    public Result<String> save(@RequestBody Object supplier) {
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/create");
     }
 
-    @Operation(summary = "更新供应商")
+    @Operation(summary = "【已废弃】更新供应商")
     @PostMapping("/update")
-    public Result<String> update(@RequestBody EcomSupplier supplier) {
-        return supplierService.update(supplier);
+    public Result<String> update(@RequestBody Object supplier) {
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/update");
     }
 
-    @Operation(summary = "删除供应商")
+    @Operation(summary = "【已废弃】删除供应商")
     @PostMapping("/delete")
     public Result<String> delete(@RequestBody Map<String, Object> params) {
-        Long id = ParamUtils.getLong(params.get("id"));
-        return supplierService.delete(id);
+        return Result.error(410, "该接口已废弃,请使用 /api/admin/supply-system/toggle-status");
     }
 }

+ 12 - 3
cfc-backend/src/main/java/com/etotem/cfc/service/SysMenuService.java

@@ -137,17 +137,26 @@ public class SysMenuService extends ServiceImpl<SysMenuMapper, SysMenu> {
         Set<Long> included = new HashSet<>();
         for (SysMenu m : all) {
             if (authed.contains(m.getId()) && Integer.valueOf(1).equals(m.getVisible())) {
-                // 向上补全父级链
+                // 向上补全父级链,要求路径上所有祖先节点均 visible=1
+                boolean chainVisible = true;
                 Long cur = m.getId();
-                while (cur != null && !included.contains(cur) && idSet.contains(cur)) {
-                    included.add(cur);
+                List<Long> chainNodes = new ArrayList<>();
+                while (cur != null && idSet.contains(cur)) {
                     SysMenu node = byId.get(cur);
+                    if (!Integer.valueOf(1).equals(node.getVisible())) {
+                        chainVisible = false;
+                        break;
+                    }
+                    chainNodes.add(cur);
                     Long pid = node.getParentId() == null ? 0L : node.getParentId();
                     if (pid == 0L || !idSet.contains(pid)) {
                         break;
                     }
                     cur = pid;
                 }
+                if (chainVisible) {
+                    included.addAll(chainNodes);
+                }
             }
         }
 

+ 5 - 2
cfc-backend/src/main/resources/schema.sql

@@ -2660,16 +2660,19 @@ CREATE TABLE IF NOT EXISTS cart (
 
 CREATE TABLE IF NOT EXISTS supply_system (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    name VARCHAR(100) NOT NULL COMMENT '体系名称',
+    name VARCHAR(100) NOT NULL COMMENT '体系名称/供应商名称',
     admin_id BIGINT NOT NULL COMMENT '管理员用户ID',
     settlement_period_days INT NOT NULL DEFAULT 30 COMMENT '账期(天)',
     platform_profit_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '平台留利比例(%)',
     description TEXT COMMENT '描述',
     status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/disabled',
+    contact_name VARCHAR(50) COMMENT '联系人',
+    contact_phone VARCHAR(20) COMMENT '联系电话',
+    remark VARCHAR(500) COMMENT '备注',
     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     KEY idx_admin_id (admin_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商体系';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商体系(含电商供应商)';
 
 CREATE TABLE IF NOT EXISTS supply_system_member (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-d2cb89ebef7a38fed3584ea9c1891400cc7e16d0
+6163c4cf76c3482f30a9b2e670e7147243667a32

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1579",
+  "version": "1.0.1583",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1579",
+      "version": "1.0.1583",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

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

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

@@ -4,6 +4,95 @@
 
 ---
 
+## v1.0.1584 (2026-10-12)
+
+### Bug 修复
+- ArticleEdit/ActivityEdit 加内滚动容器,修复表单无法下滑
+
+
+## v1.0.1583 (2026-10-12)
+
+### Bug 修复
+- 修复菜单"显示"功能不生效 — Layout改用userMenus、el-switch绑定Integer、父链补全检查visible
+
+
+## v1.0.1582 (2026-10-12)
+
+### 重构
+- 合并电商供应商管理至供应商体系管理
+
+### 其他
+- - DatabaseInitializer 迁移209: 补齐 supply_system 列并将 ecom_supplier 存量数据迁入
+- - EcomSupplierController/AdminSupplierController: 标记 @Deprecated,接口统一返回 410
+- - 菜单 ecomSupplier 指向 /supply-system,权限改为 system:supply
+- - 前端 api/admin.js 指向新接口;router 中 /ecom-supplier 重定向至 /admin/supply-system
+- 
+
+
+## v1.0.1581 (2026-10-12)
+
+### Bug 修复
+- 修复辈分关系和出生日期保存回显问题
+- 透明无边框/右键菜单/任务栏图标/默认接口/点击完成待办
+- 修复 isAdmin 恒为 false 的 bug,并清理新增的修改关系入口
+- 首页合并冲突采用 feed 集成方案,移除浮动通知条
+- 对齐真实后台接口契约(任务/能量/吉祥物/喝水)
+
+### 其他
+- - 修复 UserService.updateFamilyMember:在管理员权限下保存 generationLevel/peerType
+- - 新增 FamilyMemberVO.birthday 字段,并回显至前端
+- - 修复 FamilyMemberService.toFamilyMemberVO:回写 birthday 为 yyyy-MM-dd 字符串
+- - 前端 family-members.vue onEditRelation:传递 birthday/generationLevel 在 URL
+- - 前端 add-member.vue onLoad:从 options 回显 birthday/generationLevel
+- - 前端 family-members.vue:修复 userId 取值(.id→.userId),移除修改关系入口
+- 
+- - api/mod.rs: upload_media(multipart); api/models.rs: MediaRecord
+- - settings.rs: AppSettings.custom_mascot_url 持久化
+- - commands.rs: upload_mascot/clear_custom_mascot; get_settings 返回 customMascotUrl
+- - settings.html: 形象选择+预览+上传/恢复默认 UI; 窗口高度 340->470
+- - pet-engine.js: setCustomImage 优先渲染自定义形象; bridge.js 监听 pet://custom-image 并启动加载
+- 
+- - index.html/interactions.js: 背景透明 + 右键菜单(设置/鼠标穿透/退出)
+- - commands.rs/main.rs: open_settings/quit_app 命令; 点击宠物优先完成待办任务
+- - poller.rs: 到期任务提醒并记录可一键完成的 pending_task
+- - settings.rs/settings.html: 默认接口地址改为 https://cfc.cf-club.com
+- - api/mod.rs: complete_task 封装
+- 
+- - 移除修改关系弹窗及入口:用户确认改用编辑资料,清理相关代码
+- - 恢复 GenerationPicker 注册(编辑资料流程必需)
+- - add-member.vue: modal-link 颜色改为品牌橙 #F97316
+- 
+- - Backend: IndicatorController source-types endpoint
+- - Backend: schema.sql delete duplicate report_format L1829
+- - Backend: DanReportUploadService compareUploads(new) + DanReportController /compare
+- - Frontend: indicator-curve.vue reportTypeOptions dynamic (source-types) + fallback
+- - Frontend: indicator-curve.vue skipGaps >0 display + rank-empty tip
+- - Frontend: dan-assessment/comparison.vue + pages.json registration
+- - Frontend: utils/api.js getIndicatorSourceTypes wrapper
+- 
+
+### 新功能
+- 设置内自定义宠物形象上传(复用 /api/media/upload)
+- DAN compare + indicator curve dynamic types + skipGaps + schema cleanup
+- JS<->Rust 桥接、设置窗口与命令实现
+- 托盘菜单、单实例与主进程组装
+- 轮询器(任务/能量/喝水)与 AppState
+- ApiClient 端点封装与响应解析
+- 设置文件读写与默认值
+- keyring 令牌存储(双平台)
+- 宠物形象配置解析(真实 JSON 测试)
+- 宠物状态映射与优先级测试
+- API serde 模型层与解析测试
+- Tauri v2 脚手架与双平台窗口配置
+
+### 重构
+- TrendChart 移入 pages/health 子包并更新引用
+- 移除已退役的 WPF 版
+
+### 文档
+- 双平台 CI、分发说明与 README
+
+
 ## v1.0.1580 (2026-10-12)
 
 ### Bug 修复

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1580
+> 当前版本: v1.0.1584
 
 ## 历史版本
 
@@ -8,6 +8,95 @@
 
 ---
 
+## v1.0.1584 (2026-10-12)
+
+### Bug 修复
+- ArticleEdit/ActivityEdit 加内滚动容器,修复表单无法下滑
+
+
+## v1.0.1583 (2026-10-12)
+
+### Bug 修复
+- 修复菜单"显示"功能不生效 — Layout改用userMenus、el-switch绑定Integer、父链补全检查visible
+
+
+## v1.0.1582 (2026-10-12)
+
+### 重构
+- 合并电商供应商管理至供应商体系管理
+
+### 其他
+- - DatabaseInitializer 迁移209: 补齐 supply_system 列并将 ecom_supplier 存量数据迁入
+- - EcomSupplierController/AdminSupplierController: 标记 @Deprecated,接口统一返回 410
+- - 菜单 ecomSupplier 指向 /supply-system,权限改为 system:supply
+- - 前端 api/admin.js 指向新接口;router 中 /ecom-supplier 重定向至 /admin/supply-system
+- 
+
+
+## v1.0.1581 (2026-10-12)
+
+### Bug 修复
+- 修复辈分关系和出生日期保存回显问题
+- 透明无边框/右键菜单/任务栏图标/默认接口/点击完成待办
+- 修复 isAdmin 恒为 false 的 bug,并清理新增的修改关系入口
+- 首页合并冲突采用 feed 集成方案,移除浮动通知条
+- 对齐真实后台接口契约(任务/能量/吉祥物/喝水)
+
+### 其他
+- - 修复 UserService.updateFamilyMember:在管理员权限下保存 generationLevel/peerType
+- - 新增 FamilyMemberVO.birthday 字段,并回显至前端
+- - 修复 FamilyMemberService.toFamilyMemberVO:回写 birthday 为 yyyy-MM-dd 字符串
+- - 前端 family-members.vue onEditRelation:传递 birthday/generationLevel 在 URL
+- - 前端 add-member.vue onLoad:从 options 回显 birthday/generationLevel
+- - 前端 family-members.vue:修复 userId 取值(.id→.userId),移除修改关系入口
+- 
+- - api/mod.rs: upload_media(multipart); api/models.rs: MediaRecord
+- - settings.rs: AppSettings.custom_mascot_url 持久化
+- - commands.rs: upload_mascot/clear_custom_mascot; get_settings 返回 customMascotUrl
+- - settings.html: 形象选择+预览+上传/恢复默认 UI; 窗口高度 340->470
+- - pet-engine.js: setCustomImage 优先渲染自定义形象; bridge.js 监听 pet://custom-image 并启动加载
+- 
+- - index.html/interactions.js: 背景透明 + 右键菜单(设置/鼠标穿透/退出)
+- - commands.rs/main.rs: open_settings/quit_app 命令; 点击宠物优先完成待办任务
+- - poller.rs: 到期任务提醒并记录可一键完成的 pending_task
+- - settings.rs/settings.html: 默认接口地址改为 https://cfc.cf-club.com
+- - api/mod.rs: complete_task 封装
+- 
+- - 移除修改关系弹窗及入口:用户确认改用编辑资料,清理相关代码
+- - 恢复 GenerationPicker 注册(编辑资料流程必需)
+- - add-member.vue: modal-link 颜色改为品牌橙 #F97316
+- 
+- - Backend: IndicatorController source-types endpoint
+- - Backend: schema.sql delete duplicate report_format L1829
+- - Backend: DanReportUploadService compareUploads(new) + DanReportController /compare
+- - Frontend: indicator-curve.vue reportTypeOptions dynamic (source-types) + fallback
+- - Frontend: indicator-curve.vue skipGaps >0 display + rank-empty tip
+- - Frontend: dan-assessment/comparison.vue + pages.json registration
+- - Frontend: utils/api.js getIndicatorSourceTypes wrapper
+- 
+
+### 新功能
+- 设置内自定义宠物形象上传(复用 /api/media/upload)
+- DAN compare + indicator curve dynamic types + skipGaps + schema cleanup
+- JS<->Rust 桥接、设置窗口与命令实现
+- 托盘菜单、单实例与主进程组装
+- 轮询器(任务/能量/喝水)与 AppState
+- ApiClient 端点封装与响应解析
+- 设置文件读写与默认值
+- keyring 令牌存储(双平台)
+- 宠物形象配置解析(真实 JSON 测试)
+- 宠物状态映射与优先级测试
+- API serde 模型层与解析测试
+- Tauri v2 脚手架与双平台窗口配置
+
+### 重构
+- TrendChart 移入 pages/health 子包并更新引用
+- 移除已退役的 WPF 版
+
+### 文档
+- 双平台 CI、分发说明与 README
+
+
 ## v1.0.1580 (2026-10-12)
 
 ### Bug 修复

+ 6 - 5
cfc-web/src/api/admin.js

@@ -1793,10 +1793,11 @@ export function updateCommissionConfig(data) {
   })
 }
 
-// 电商供应商管理
+// 电商供应商管理(已合并至供应商体系,接口指向 /api/admin/supply-system)
+// 保留旧函数名但指向新接口,避免前端报错
 export function getEcomSupplierList(params) {
   return request({
-    url: '/api/admin/supplier/list',
+    url: '/api/admin/supply-system/list',
     method: 'post',
     data: params
   })
@@ -1804,7 +1805,7 @@ export function getEcomSupplierList(params) {
 
 export function saveEcomSupplier(data) {
   return request({
-    url: '/api/admin/supplier/save',
+    url: '/api/admin/supply-system/create',
     method: 'post',
     data
   })
@@ -1812,7 +1813,7 @@ export function saveEcomSupplier(data) {
 
 export function updateEcomSupplier(data) {
   return request({
-    url: '/api/admin/supplier/update',
+    url: '/api/admin/supply-system/update',
     method: 'post',
     data
   })
@@ -1820,7 +1821,7 @@ export function updateEcomSupplier(data) {
 
 export function deleteEcomSupplier(id) {
   return request({
-    url: '/api/admin/supplier/delete',
+    url: '/api/admin/supply-system/toggle-status',
     method: 'post',
     data: { id }
   })

+ 3 - 3
cfc-web/src/router/index.js

@@ -826,12 +826,12 @@ const routes = [
         meta: { title: '团队成员管理', perm: 'system:base' },
         props: true
       },
-      // ========== 电商供应商管理 ==========
+      // ========== 电商供应商管理(已合并至供应商体系,路由指向 /supply-system)==========
       {
         path: 'ecom-supplier',
+        redirect: '/admin/supply-system',
         name: 'EcomSupplierManage',
-        component: () => import('@/views/admin/EcomSupplierManage'),
-        meta: { title: '电商供应商管理', perm: 'ecom:supplier' }
+        meta: { title: '电商供应商管理', perm: 'system:supply', hidden: true }
       },
       {
         path: 'badge-manage',

+ 2 - 2
cfc-web/src/views/Layout.vue

@@ -158,7 +158,7 @@ import { updateAdminInfo } from '@/api/admin'
 import { getMyInviteCode } from '@/api/guide'
 import { getUnreadMessageCount } from '@/api/teacherMessage'
 import { getEffectivePermissions, hasPermission, getRoleLabel } from '@/utils/permissions'
-import axios from 'axios'
+import { getUserMenus } from '@/api/menu'
 
 export default {
   data() {
@@ -508,7 +508,7 @@ export default {
     },
     async fetchMenu() {
       try {
-        var res = await axios.post('/api/admin/menu/tree')
+        var res = await getUserMenus()
         if (res.data && res.data.code === 200 && res.data.data && res.data.data.length > 0) {
           this.menuItems = this.buildMenuItems(res.data.data)
         }

+ 20 - 0
cfc-web/src/views/admin/ActivityEdit.vue

@@ -29,6 +29,7 @@
         style="margin-bottom: 16px;"
       />
 
+      <div class="activity-edit-form-wrap">
       <el-form label-width="120px" v-loading="loading" :model="form" :rules="formRules" ref="form">
         <el-form-item label="活动标题" prop="title">
           <el-input v-model="form.title" placeholder="请输入活动标题" :disabled="readonly" />
@@ -143,6 +144,7 @@
           <span style="margin-left:8px;color:#999;font-size:12px;">0表示使用系统默认积分</span>
         </el-form-item>
       </el-form>
+      </div>
     </el-card>
 
     <!-- 创建成功弹窗 -->
@@ -605,4 +607,22 @@ activityId
   max-height: 500px;
   overflow-y: auto;
 }
+
+/* 内滚动容器:利用 el-card__body flex column 让表单区域独立滚动 */
+.activity-edit-form-wrap {
+  flex: 1;
+  min-height: 0;
+  overflow-y: auto;
+  padding-right: 8px;
+  margin-right: -8px;
+}
+
+/* 内滚动容器:利用 el-card__body flex column 让表单区域独立滚动 */
+.activity-edit-form-wrap {
+  flex: 1;
+  min-height: 0;
+  overflow-y: auto;
+  padding-right: 8px;
+  margin-right: -8px;
+}
 </style>

+ 11 - 0
cfc-web/src/views/admin/ArticleEdit.vue

@@ -30,6 +30,7 @@
         style="margin-bottom: 16px;"
       />
 
+      <div class="article-edit-form-wrap">
       <el-form :model="form" label-width="120px" v-loading="loading">
         <el-form-item label="标题">
           <el-input v-model="form.title" placeholder="请输入文章标题" maxlength="200" show-word-limit :disabled="readonly" />
@@ -133,6 +134,7 @@
           </el-radio-group>
         </el-form-item>
       </el-form>
+      </div>
     </el-card>
   </div>
 </template>
@@ -1017,4 +1019,13 @@ onError: (file, err, res) => {
     flex-wrap: wrap;
   }
 }
+
+/* 内滚动容器:利用 el-card__body flex column 让表单区域独立滚动 */
+.article-edit-form-wrap {
+  flex: 1;
+  min-height: 0;
+  overflow-y: auto;
+  padding-right: 8px;
+  margin-right: -8px;
+}
 </style>

+ 2 - 2
cfc-web/src/views/admin/MenuManage.vue

@@ -49,7 +49,7 @@
           <el-table-column prop="sort" label="排序" width="80" align="center"></el-table-column>
           <el-table-column prop="visible" label="显示" width="80" align="center">
             <template slot-scope="scope">
-              <el-switch v-model="scope.row.visible" size="mini" @change="handleToggleVisible(scope.row)"></el-switch>
+              <el-switch v-model="scope.row.visible" :active-value="1" :inactive-value="0" size="mini" @change="handleToggleVisible(scope.row)"></el-switch>
             </template>
           </el-table-column>
           <el-table-column label="操作" width="320" fixed="right">
@@ -172,7 +172,7 @@
           <el-input-number v-model="menuForm.sort" :min="0" style="width:100%"></el-input-number>
         </el-form-item>
         <el-form-item label="是否显示">
-          <el-switch v-model="menuForm.visible"></el-switch>
+          <el-switch v-model="menuForm.visible" :active-value="1" :inactive-value="0"></el-switch>
         </el-form-item>
       </el-form>
       <div slot="footer">