Quellcode durchsuchen

chore: auto bump version and changelog [skip ci]

iwt vor 3 Wochen
Ursprung
Commit
58718c5d23

+ 41 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/OrgMemberVO.java

@@ -0,0 +1,41 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 组织成员视图对象(org_members LEFT JOIN users)。
+ * familyCount/totalOrders/totalConsumption 由 OrgMemberService 批量聚合后填充,
+ * Mapper 查询仅返回成员基础字段。
+ */
+@Data
+public class OrgMemberVO implements Serializable {
+
+    private Long userId;
+
+    private String nickname;
+
+    /** 脱敏后的手机号(如 138****1234),由 Service 层填充 */
+    private String phone;
+
+    private String avatar;
+
+    /** planner/user */
+    private String memberType;
+
+    /** 组织内角色(teacher/student/member等) */
+    private String roleInOrg;
+
+    private Date joinedAt;
+
+    /** 成员关联的家庭数(仅计 org_members 直属家庭,不含 teacherFamilyIds 绑定) */
+    private Integer familyCount;
+
+    /** 成员关联的有效订单总数(product_orders PAID/COMPLETED) */
+    private Integer totalOrders;
+
+    /** 成员关联的消费总额(单位分) */
+    private Long totalConsumption;
+}

+ 32 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/OrgMember.java

@@ -0,0 +1,32 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("org_members")
+public class OrgMember implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long orgId;
+
+    /** 用户ID(users.id) */
+    private Long userId;
+
+    /** planner/user */
+    private String memberType;
+
+    /** 组织内角色(teacher/student/member等) */
+    private String roleInOrg;
+
+    /** active/inactive */
+    private String status;
+
+    private Date joinedAt;
+}

+ 40 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Organization.java

@@ -0,0 +1,40 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("organizations")
+public class Organization implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String name;
+
+    /** enterprise/institution/other */
+    private String type;
+
+    /** 组织编号(唯一标识,为空时由服务端生成) */
+    private String code;
+
+    /** 组织管理员 userId(关联 users.id) */
+    private Long adminUserId;
+
+    private String contactName;
+
+    private String contactPhone;
+
+    private String description;
+
+    /** active/disabled */
+    private String status;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 19 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/OrgMemberMapper.java

@@ -0,0 +1,19 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.OrgMember;
+import com.etotem.cfc.dto.OrgMemberVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface OrgMemberMapper extends BaseMapper<OrgMember> {
+
+    /** 按 orgId 查活跃成员(status='active'),LEFT JOIN users 带出基础信息 */
+    List<OrgMemberVO> selectActiveMembersWithUser(@Param("orgId") Long orgId);
+
+    /** 按 orgId + userId 列表查记录(含 inactive,用于复活/查重) */
+    List<OrgMember> selectByOrgIdAndUserIds(@Param("orgId") Long orgId, @Param("userIds") List<Long> userIds);
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/OrganizationMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.Organization;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface OrganizationMapper extends BaseMapper<Organization> {
+}

+ 211 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/OrgMemberService.java

@@ -0,0 +1,211 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.etotem.cfc.common.DataMaskUtil;
+import com.etotem.cfc.dto.OrgMemberVO;
+import com.etotem.cfc.entity.OrgMember;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.OrgMemberMapper;
+import com.etotem.cfc.mapper.UserMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+public class OrgMemberService extends ServiceImpl<OrgMemberMapper, OrgMember> {
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private OrgMemberMapper orgMemberMapper;
+
+    @Resource
+    private JdbcTemplate jdbcTemplate;
+
+    @Resource
+    private UserSearchService userSearchService;
+
+    /**
+     * 成员列表(含家庭数/订单数/消费总额摘要,默认只返回 status=active)。
+     * 聚合基于 userId 集合批量 IN 查询,禁止 per-member 循环查询。
+     */
+    public List<OrgMemberVO> memberList(Long orgId) {
+        // 1) 查活跃成员基础信息(Mapper 联查 users 表带出 nickname/phone/avatar)
+        List<OrgMemberVO> baseList = orgMemberMapper.selectActiveMembersWithUser(orgId);
+        if (baseList.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<Long> userIds = baseList.stream()
+                .map(OrgMemberVO::getUserId)
+                .distinct()
+                .collect(Collectors.toList());
+
+        // 2) 批量聚合:家庭数(family_members.user_id IN ... 去重计数)
+        Map<Long, Integer> familyCountMap = batchFamilyCount(userIds);
+        // 3) 批量聚合:有效订单数
+        Map<Long, Integer> totalOrdersMap = batchTotalOrders(userIds);
+        // 4) 批量聚合:消费总额(分)
+        Map<Long, Long> totalConsumptionMap = batchTotalConsumption(userIds);
+
+        // 5) 组装 VO(手机号脱敏)
+        for (OrgMemberVO vo : baseList) {
+            Long uid = vo.getUserId();
+            vo.setFamilyCount(familyCountMap.getOrDefault(uid, 0));
+            vo.setTotalOrders(totalOrdersMap.getOrDefault(uid, 0));
+            vo.setTotalConsumption(totalConsumptionMap.getOrDefault(uid, 0L));
+            if (vo.getPhone() != null) {
+                vo.setPhone(DataMaskUtil.maskPhone(vo.getPhone()));
+            }
+        }
+        return baseList;
+    }
+
+    /** 添加成员:已存在记录(含 inactive)则复活并更新类型;不存在则插入 */
+    @Transactional
+    public boolean addMember(Long orgId, Long userId, String memberType) {
+        if (orgId == null || userId == null) {
+            throw new IllegalArgumentException("orgId/userId 不能为空");
+        }
+        OrgMember existing = orgMemberMapper.selectOne(new LambdaQueryWrapper<OrgMember>()
+                .eq(OrgMember::getOrgId, orgId)
+                .eq(OrgMember::getUserId, userId));
+        if (existing != null) {
+            boolean wasInactive = "inactive".equals(existing.getStatus());
+            existing.setMemberType(memberType);
+            existing.setStatus("active");
+            if (wasInactive) {
+                existing.setJoinedAt(new Date());
+            }
+            return orgMemberMapper.updateById(existing) > 0;
+        }
+        OrgMember member = new OrgMember();
+        member.setOrgId(orgId);
+        member.setUserId(userId);
+        member.setMemberType(memberType);
+        member.setStatus("active");
+        member.setJoinedAt(new Date());
+        return orgMemberMapper.insert(member) > 0;
+    }
+
+    /** 移除成员(软删,status=inactive) */
+    @Transactional
+    public boolean removeMember(Long orgId, Long userId) {
+        OrgMember existing = orgMemberMapper.selectOne(new LambdaQueryWrapper<OrgMember>()
+                .eq(OrgMember::getOrgId, orgId)
+                .eq(OrgMember::getUserId, userId));
+        if (existing == null) {
+            return false;
+        }
+        existing.setStatus("inactive");
+        return orgMemberMapper.updateById(existing) > 0;
+    }
+
+    /** 变更成员类型(planner ↔ user),同时会复活 inactive 成员为 active */
+    @Transactional
+    public boolean changeType(Long orgId, Long userId, String memberType) {
+        if (memberType == null
+                || (!"planner".equals(memberType) && !"user".equals(memberType))) {
+            throw new IllegalArgumentException("memberType 仅支持 planner/user");
+        }
+        OrgMember existing = orgMemberMapper.selectOne(new LambdaQueryWrapper<OrgMember>()
+                .eq(OrgMember::getOrgId, orgId)
+                .eq(OrgMember::getUserId, userId));
+        if (existing == null) {
+            return false;
+        }
+        boolean wasInactive = "inactive".equals(existing.getStatus());
+        existing.setMemberType(memberType);
+        existing.setStatus("active");
+        if (wasInactive) {
+            existing.setJoinedAt(new Date());
+        }
+        return orgMemberMapper.updateById(existing) > 0;
+    }
+
+    /** 按手机号/昵称搜索平台用户(添加成员时下拉选人用),委托 UserSearchService */
+    public List<Map<String, Object>> userSearch(String keyword, String memberType) {
+        return userSearchService.search(keyword, memberType);
+    }
+
+    // ========== 批量聚合辅助 ==========
+
+    private Map<Long, Integer> batchFamilyCount(List<Long> userIds) {
+        if (userIds == null || userIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        String in = buildInPlaceholder(userIds.size());
+        return jdbcTemplate.query(
+                "SELECT user_id, COUNT(DISTINCT id) AS cnt FROM family_members " +
+                        "WHERE user_id IN (" + in + ") GROUP BY user_id",
+                rs -> {
+                    Map<Long, Integer> map = new HashMap<>();
+                    while (rs.next()) {
+                        map.put(rs.getLong("user_id"), rs.getInt("cnt"));
+                    }
+                    return map;
+                }, userIds.toArray());
+    }
+
+    private Map<Long, Integer> batchTotalOrders(List<Long> userIds) {
+        if (userIds == null || userIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        String in = buildInPlaceholder(userIds.size());
+        return jdbcTemplate.query(
+                "SELECT buyer_id, COUNT(*) AS cnt FROM product_orders " +
+                        "WHERE buyer_id IN (" + in + ") AND status IN ('PAID','COMPLETED') " +
+                        "GROUP BY buyer_id",
+                rs -> {
+                    Map<Long, Integer> map = new HashMap<>();
+                    while (rs.next()) {
+                        map.put(rs.getLong("buyer_id"), rs.getInt("cnt"));
+                    }
+                    return map;
+                }, userIds.toArray());
+    }
+
+    private Map<Long, Long> batchTotalConsumption(List<Long> userIds) {
+        if (userIds == null || userIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        String in = buildInPlaceholder(userIds.size());
+        return jdbcTemplate.query(
+                "SELECT buyer_id, SUM(total_amount) AS total FROM product_orders " +
+                        "WHERE buyer_id IN (" + in + ") AND status IN ('PAID','COMPLETED') " +
+                        "GROUP BY buyer_id",
+                rs -> {
+                    Map<Long, Long> map = new HashMap<>();
+                    while (rs.next()) {
+                        long sum = rs.getLong("total");
+                        // SUM 可能为 NULL,用 wasNull 兜底
+                        if (rs.wasNull()) {
+                            sum = 0L;
+                        }
+                        map.put(rs.getLong("buyer_id"), sum);
+                    }
+                    return map;
+                }, userIds.toArray());
+    }
+
+    private String buildInPlaceholder(int cnt) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < cnt; i++) {
+            if (i > 0) {
+                sb.append(",");
+            }
+            sb.append("?");
+        }
+        return sb.toString();
+    }
+}

+ 202 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/OrganizationService.java

@@ -0,0 +1,202 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.etotem.cfc.entity.Organization;
+import com.etotem.cfc.entity.OrgMember;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.OrganizationMapper;
+import com.etotem.cfc.mapper.OrgMemberMapper;
+import com.etotem.cfc.mapper.UserMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+@Service
+public class OrganizationService extends ServiceImpl<OrganizationMapper, Organization> {
+
+    private static final String CODE_PREFIX = "ORG";
+    private static final int CODE_LEN = 6;
+
+    @Resource
+    private OrgMemberMapper orgMemberMapper;
+
+    @Resource
+    private UserMapper userMapper;
+
+    public Page<Organization> list(String keyword, String type, String status, int page, int size) {
+        Page<Organization> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<Organization> wrapper = new LambdaQueryWrapper<>();
+        if (keyword != null && !keyword.isEmpty()) {
+            wrapper.like(Organization::getName, keyword);
+        }
+        if (type != null && !type.isEmpty()) {
+            wrapper.eq(Organization::getType, type);
+        }
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(Organization::getStatus, status);
+        }
+        wrapper.orderByDesc(Organization::getId);
+        return this.page(pageParam, wrapper);
+    }
+
+    public Organization detail(Long id) {
+        return this.getById(id);
+    }
+
+    @Transactional
+    public Map<String, Object> create(String name, String type, String code,
+                                      String adminPhone, String adminRealName) {
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("组织名称不能为空");
+        }
+        if (adminPhone == null || adminPhone.isEmpty()) {
+            throw new IllegalArgumentException("管理员手机号不能为空");
+        }
+        if (code == null || code.isEmpty()) {
+            code = generateCode();
+        }
+
+        User admin = findOrCreateAdminUser(adminPhone, adminRealName);
+
+        Organization org = new Organization();
+        org.setName(name);
+        org.setType(type);
+        org.setCode(code);
+        org.setAdminUserId(admin.getId());
+        org.setStatus("active");
+        org.setCreatedAt(new Date());
+        org.setUpdatedAt(new Date());
+        this.save(org);
+
+        // 组织管理员自动作为该组织规划师类型成员(便于统计口径与自身组织可见性)
+        OrgMember member = new OrgMember();
+        member.setOrgId(org.getId());
+        member.setUserId(admin.getId());
+        member.setMemberType("planner");
+        member.setStatus("active");
+        member.setJoinedAt(new Date());
+        orgMemberMapper.insert(member);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("organization", org);
+        result.put("adminUser", admin);
+        return result;
+    }
+
+    public boolean update(Long id, String name, String type, String contactName,
+                          String contactPhone, String description) {
+        Organization org = this.getById(id);
+        if (org == null) {
+            return false;
+        }
+        if (name != null && !name.isEmpty()) {
+            org.setName(name);
+        }
+        if (type != null) {
+            org.setType(type);
+        }
+        if (contactName != null) {
+            org.setContactName(contactName);
+        }
+        if (contactPhone != null) {
+            org.setContactPhone(contactPhone);
+        }
+        if (description != null) {
+            org.setDescription(description);
+        }
+        org.setUpdatedAt(new Date());
+        return this.updateById(org);
+    }
+
+    public boolean disable(Long id, String status) {
+        Organization org = this.getById(id);
+        if (org == null) {
+            return false;
+        }
+        org.setStatus(status);
+        org.setUpdatedAt(new Date());
+        return this.updateById(org);
+    }
+
+    /**
+     * 查用户管理的组织(限活跃,按 id 倒序取首条,避免重复数据报错)
+     */
+    public Organization getByAdminId(Long userId) {
+        LambdaQueryWrapper<Organization> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Organization::getAdminUserId, userId);
+        wrapper.eq(Organization::getStatus, "active");
+        wrapper.orderByDesc(Organization::getId);
+        wrapper.last("LIMIT 1");
+        List<Organization> orgs = this.list(wrapper);
+        return orgs == null || orgs.isEmpty() ? null : orgs.get(0);
+    }
+
+    /** 组织管理员获取自己所属的组织(my-org 专用入口) */
+    public Organization getMyOrg(Long userId) {
+        return getByAdminId(userId);
+    }
+
+    /** 按手机号查已有用户(复用),不存在则创建组织管理员账号 */
+    private User findOrCreateAdminUser(String phone, String realName) {
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(User::getPhone, phone);
+        User user = userMapper.selectOne(wrapper);
+        if (user != null) {
+            // 复用时若不含 org_admin 角色,补上;主角色保持不动(不影响 C 端身份)
+            List<String> roles = user.getRoles();
+            if (roles == null) {
+                roles = new ArrayList<>();
+            }
+            if (!roles.contains("org_admin")) {
+                roles.add("org_admin");
+                user.setRoles(roles);
+                user.setUpdatedAt(new Date());
+                userMapper.updateById(user);
+            }
+            return user;
+        }
+        User u = new User();
+        u.setPhone(phone);
+        u.setRealName(realName);
+        u.setNickname(realName != null && !realName.isEmpty()
+                ? realName : "组织管理员" + phone.substring(Math.max(0, phone.length() - 4)));
+        u.setRole("org_admin");
+        u.setRoles(new ArrayList<>(Collections.singletonList("org_admin")));
+        u.setPassword("");
+        u.setOpenid("admin_" + System.currentTimeMillis());
+        u.setFamilyId(0L);
+        u.setCreatedAt(new Date());
+        u.setUpdatedAt(new Date());
+        userMapper.insert(u);
+        return u;
+    }
+
+    private String generateCode() {
+        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        Random random = new Random();
+        for (int attempt = 0; attempt < 10; attempt++) {
+            StringBuilder sb = new StringBuilder(CODE_PREFIX);
+            for (int i = 0; i < CODE_LEN; i++) {
+                sb.append(chars.charAt(random.nextInt(chars.length())));
+            }
+            String code = sb.toString();
+            LambdaQueryWrapper<Organization> check = new LambdaQueryWrapper<>();
+            check.eq(Organization::getCode, code);
+            Long count = this.count(check);
+            if (count == null || count == 0) {
+                return code;
+            }
+        }
+        throw new IllegalStateException("无法生成唯一的组织编号");
+    }
+}

+ 48 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/UserSearchService.java

@@ -0,0 +1,48 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.common.DataMaskUtil;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.UserMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 平台用户搜索(添加组织成员时下拉选人用)。
+ * 仅按昵称/手机号模糊匹配 users 表,返回脱敏结果,最多 20 条。
+ */
+@Service
+public class UserSearchService {
+
+    @Resource
+    private UserMapper userMapper;
+
+    public List<Map<String, Object>> search(String keyword, String memberType) {
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
+        if (keyword != null && !keyword.isEmpty()) {
+            wrapper.and(w -> w.like(User::getNickname, keyword)
+                    .or()
+                    .like(User::getPhone, keyword));
+        }
+        wrapper.orderByDesc(User::getId);
+        wrapper.last("LIMIT 20");
+        List<User> users = userMapper.selectList(wrapper);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (User user : users) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", user.getId());
+            item.put("nickname", user.getNickname());
+            item.put("phone", user.getPhone() != null ? DataMaskUtil.maskPhone(user.getPhone()) : "");
+            item.put("avatar", user.getAvatar());
+            item.put("memberType", memberType != null ? memberType : "user");
+            item.put("status", "active");
+            result.add(item);
+        }
+        return result;
+    }
+}

+ 36 - 0
cfc-backend/src/main/resources/mapper/OrgMemberMapper.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.etotem.cfc.mapper.OrgMemberMapper">
+
+    <resultMap id="orgMemberVOMap" type="com.etotem.cfc.dto.OrgMemberVO">
+        <result column="user_id" property="userId"/>
+        <result column="nickname" property="nickname"/>
+        <result column="phone" property="phone"/>
+        <result column="avatar" property="avatar"/>
+        <result column="member_type" property="memberType"/>
+        <result column="role_in_org" property="roleInOrg"/>
+        <result column="joined_at" property="joinedAt"/>
+    </resultMap>
+
+    <select id="selectActiveMembersWithUser" resultMap="orgMemberVOMap">
+        SELECT u.id AS user_id, u.nickname, u.phone, u.avatar,
+               m.member_type, m.role_in_org, m.joined_at
+        FROM org_members m
+        LEFT JOIN users u ON u.id = m.user_id
+        WHERE m.org_id = #{orgId} AND m.status = 'active'
+        ORDER BY m.joined_at ASC
+    </select>
+
+    <select id="selectByOrgIdAndUserIds" resultType="com.etotem.cfc.entity.OrgMember">
+        SELECT id, org_id, user_id, member_type, role_in_org, status, joined_at
+        FROM org_members
+        WHERE org_id = #{orgId}
+        <if test="userIds != null and userIds.size() > 0">
+            AND user_id IN
+            <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+                #{userId}
+            </foreach>
+        </if>
+    </select>
+
+</mapper>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-287870ca38462912947fe6010eb73a15ac2c135d
+537e488ae4eb19e71ba5cc076ab8f83bafa3b444

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

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1756",
+  "version": "1.0.1757",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1756",
+      "version": "1.0.1757",
       "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.1757",
+  "version": "1.0.1758",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

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

@@ -4,6 +4,12 @@
 
 ---
 
+## v1.0.1758 (2026-10-22)
+
+### Bug 修复
+- 恢复成长百科人体图,移除临时 SVG 占位
+
+
 ## v1.0.1757 (2026-10-22)
 
 ### 样式

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1757
+> 当前版本: v1.0.1758
 
 ## 历史版本
 
@@ -8,6 +8,12 @@
 
 ---
 
+## v1.0.1758 (2026-10-22)
+
+### Bug 修复
+- 恢复成长百科人体图,移除临时 SVG 占位
+
+
 ## v1.0.1757 (2026-10-22)
 
 ### 样式