From cc31d303d12f9c7cd2c4574351b67db04069ae0d Mon Sep 17 00:00:00 2001
From: hejianhao <15708179461@qq.com>
Date: 星期五, 28 八月 2026 15:46:30 +0800
Subject: [PATCH] 828优化功能新需求

---
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/vo/ClinicListItemVO.java                        |    1 
 traditionalApp/pages/home/home.vue                                                                                     |    5 
 tcm-training-admin/src/pages/member/clinic/index.tsx                                                                   |   12 +
 traditionalApp/pages/mine/mine.vue                                                                                     |   25 -
 tcm-training-admin/src/app.tsx                                                                                         |   13 +
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/mapper/ClinicQueryMapper.java                   |    2 
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/row/ClinicListRow.java                          |    1 
 tcm-training-admin-backend/src/main/resources/mapper/ClinicQueryMapper.xml                                             |    9 
 tcm-training-admin/config/routes.ts                                                                                    |  182 ++++++++--------
 traditionalApp/pages/login/login.vue                                                                                   |    6 
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/ClinicController.java                |    6 
 traditionalApp/App.vue                                                                                                 |    8 
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterRegionService.java     |   10 
 tcm-training-admin-backend/src/test/java/com/tcm/training/admin/app/register/service/AppRegisterRegionServiceTest.java |   24 ++
 traditionalApp/config/index.js                                                                                         |    1 
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterService.java           |   34 +-
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/bootstrap/DataInitializer.java                         |    2 
 /dev/null                                                                                                              |  199 ------------------
 tcm-training-admin/src/pages/live/broadcast/index.tsx                                                                  |    2 
 traditionalApp/pages/articleDetail/articleDetail.vue                                                                   |    3 
 traditionalApp/pages/good/projectDetail.vue                                                                            |    4 
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/ClinicAppService.java                   |   64 -----
 traditionalApp/manifest.json                                                                                           |    4 
 23 files changed, 216 insertions(+), 401 deletions(-)

diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterRegionService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterRegionService.java
index 2d587a6..67902d8 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterRegionService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterRegionService.java
@@ -101,6 +101,9 @@
                 }
                 List<AppRegisterRegionPickerOptionVO> currentDistrictOptions = new ArrayList<AppRegisterRegionPickerOptionVO>(districts.size());
                 for (SysRegion district : districts) {
+                    if (shouldHideInAppPicker(district)) {
+                        continue;
+                    }
                     currentDistrictOptions.add(toPickerOption(district, 3));
                 }
                 currentAreaOptions.add(currentDistrictOptions);
@@ -117,6 +120,13 @@
     }
 
     /**
+     * APP 地区选择器不展示行政区划占位节点。
+     */
+    private boolean shouldHideInAppPicker(SysRegion region) {
+        return region.getName() != null && region.getName().contains("市辖区");
+    }
+
+    /**
      * 将海诊通诊所地区编码或名称归一化为本地三级行政区划。
      */
     public RegionSelection resolveRegionSelection(String provinceCode,
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterService.java
index 0640522..edec2fc 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/register/service/AppRegisterService.java
@@ -184,6 +184,9 @@
         clinic.setBusinessLicenseUrl(trimToNull(request.getBusinessLicenseUrl()));
         clinic.setInviteCode(generateInviteCode());
         clinic.setStatus(1);
+
+        clinic.setAgentId(4L); // 绑定到默认的省代上面
+
         clinicMapper.insert(clinic);
 
         AppUser user = new AppUser();
@@ -194,19 +197,24 @@
         user.setClinicId(clinic.getId());
         userMapper.insert(user);
 
-//        Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码");
-//        Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码");
-//        Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码");
-//        log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}",
-//                clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
-//        try {
-//            JsonNode vzanResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
-//            log.info("微赞创建门店成功,出参={}", vzanResponse);
-//        } catch (RuntimeException ex) {
-//            log.error("微赞创建门店失败,本地事务将回滚,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}",
-//                    clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone(), ex);
-//            throw new IllegalStateException("微赞创建门店失败", ex);
-//        }
+        Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码");
+        Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码");
+        Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码");
+        log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}"
+                , clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
+        try {
+            JsonNode vzanResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
+            log.info("微赞创建门店成功,出参={}", vzanResponse);
+            // 注册阶段暂未绑定区域代理,parentMobile 传空,由微赞按一级代理处理。
+            log.info("调用微赞创建一级代理,入参 agentMobile={}, agentName={}, levelCode=1, storeName={}, parentMobile=18510939636",
+                    clinic.getContactPhone(), clinic.getName(), clinic.getName());
+            JsonNode vzanAgentResponse = vzanHttpUtil.createAgent(
+                    clinic.getContactPhone(), clinic.getName(), 1, "18510939636", clinic.getName());
+            log.info("微赞创建一级代理成功,出参={}", vzanAgentResponse);
+        } catch (RuntimeException ex) {
+            log.error("微赞创建门店或一级代理失败,本地事务将回滚,clinicId={}", clinic.getId(), ex);
+            throw new IllegalStateException("微赞门店或一级代理创建失败", ex);
+        }
 
         clearDraft(request.getRegisterTicket());
         return createLoginResponse(user);
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/bootstrap/DataInitializer.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/bootstrap/DataInitializer.java
index eb01d3a..2313f84 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/bootstrap/DataInitializer.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/bootstrap/DataInitializer.java
@@ -20,7 +20,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@Component
+//@Component
 public class DataInitializer implements ApplicationRunner {
 
     private final SysUserService sysUserService;
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/ClinicController.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/ClinicController.java
index 8aeec32..8f6baa8 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/ClinicController.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/ClinicController.java
@@ -44,10 +44,11 @@
             @RequestParam(required = false) String regionCode,
             @RequestParam(required = false) String name,
             @RequestParam(required = false) String phone,
+            @RequestParam(required = false) String agentName,
             @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdFrom,
             @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdTo,
             @RequestParam(required = false) Integer status) {
-        return ApiResponse.ok(clinicAppService.page(page, pageSize, regionCode, name, phone, createdFrom, createdTo, status));
+        return ApiResponse.ok(clinicAppService.page(page, pageSize, regionCode, name, phone, agentName, createdFrom, createdTo, status));
     }
 
     @GetMapping("/clinics/clinic-import-template")
@@ -65,10 +66,11 @@
                        @RequestParam(required = false) String regionCode,
                        @RequestParam(required = false) String name,
                        @RequestParam(required = false) String phone,
+                       @RequestParam(required = false) String agentName,
                        @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdFrom,
                        @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdTo,
                        @RequestParam(required = false) Integer status) throws IOException {
-        clinicAppService.export(regionCode, name, phone, createdFrom, createdTo, status, response);
+        clinicAppService.export(regionCode, name, phone, agentName, createdFrom, createdTo, status, response);
     }
 
     @GetMapping("/clinics/regions")
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/mapper/ClinicQueryMapper.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/mapper/ClinicQueryMapper.java
index 6d5e009..1880b5f 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/mapper/ClinicQueryMapper.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/mapper/ClinicQueryMapper.java
@@ -18,6 +18,7 @@
                                           @Param("regionCode") String regionCode,
                                           @Param("name") String name,
                                           @Param("phone") String phone,
+                                          @Param("agentName") String agentName,
                                           @Param("createdFrom") LocalDateTime createdFrom,
                                           @Param("createdTo") LocalDateTime createdTo,
                                           @Param("status") Integer status);
@@ -25,6 +26,7 @@
     List<ClinicListRow> selectClinicList(@Param("regionCode") String regionCode,
                                          @Param("name") String name,
                                          @Param("phone") String phone,
+                                         @Param("agentName") String agentName,
                                          @Param("createdFrom") LocalDateTime createdFrom,
                                          @Param("createdTo") LocalDateTime createdTo,
                                          @Param("status") Integer status);
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/row/ClinicListRow.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/row/ClinicListRow.java
index 64fa430..5d65cdd 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/row/ClinicListRow.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/row/ClinicListRow.java
@@ -16,6 +16,7 @@
     private String phone;
     private String regionName;
     private Integer status;
+    private String agentName;
     private Integer userCount;
     private LocalDateTime createdAt;
     private String businessLicenseUrl;
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/ClinicAppService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/ClinicAppService.java
index 5aecb91..f62d381 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/ClinicAppService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/ClinicAppService.java
@@ -4,7 +4,6 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.fasterxml.jackson.databind.JsonNode;
 import com.tcm.training.admin.agent.entity.AgentRegion;
 import com.tcm.training.admin.agent.entity.ProvincialAgent;
 import com.tcm.training.admin.agent.excel.ClinicAgentImportRow;
@@ -29,7 +28,6 @@
 import com.tcm.training.admin.member.vo.ClinicDetailVO;
 import com.tcm.training.admin.member.vo.ClinicListItemVO;
 import com.tcm.training.admin.member.vo.ClinicRegionOptionVO;
-import com.tcm.training.admin.utils.VzanHttpUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 import org.springframework.stereotype.Service;
@@ -54,7 +52,6 @@
     private final ProvincialAgentMapper provincialAgentMapper;
     private final UserMapper userMapper;
     private final SysRegionMapper sysRegionMapper;
-    private final VzanHttpUtil vzanHttpUtil;
     private final DataSourceTransactionManager transactionManager;
 
     public ClinicAppService(ClinicQueryMapper clinicQueryMapper,
@@ -62,14 +59,12 @@
                            ProvincialAgentMapper provincialAgentMapper,
                            UserMapper userMapper,
                            SysRegionMapper sysRegionMapper,
-                           VzanHttpUtil vzanHttpUtil,
                            DataSourceTransactionManager transactionManager) {
         this.clinicQueryMapper = clinicQueryMapper;
         this.clinicMapper = clinicMapper;
         this.provincialAgentMapper = provincialAgentMapper;
         this.userMapper = userMapper;
         this.sysRegionMapper = sysRegionMapper;
-        this.vzanHttpUtil = vzanHttpUtil;
         this.transactionManager = transactionManager;
     }
 
@@ -78,6 +73,7 @@
                                                String regionCode,
                                                String name,
                                                String phone,
+                                               String agentName,
                                                LocalDateTime createdFrom,
                                                LocalDateTime createdTo,
                                                Integer status) {
@@ -86,6 +82,7 @@
                 trimToNull(regionCode),
                 trimToNull(name),
                 trimToNull(phone),
+                trimToNull(agentName),
                 createdFrom,
                 createdTo,
                 status
@@ -177,6 +174,7 @@
     public void export(String regionCode,
                        String name,
                        String phone,
+                       String agentName,
                        LocalDateTime createdFrom,
                        LocalDateTime createdTo,
                        Integer status,
@@ -190,6 +188,7 @@
                 trimToNull(regionCode),
                 trimToNull(name),
                 trimToNull(phone),
+                trimToNull(agentName),
                 createdFrom,
                 createdTo,
                 status
@@ -307,61 +306,7 @@
             throw e;
         }
 
-        if (request.getAgentId() == null) {
-            return detail(id);
-        }
-
-        // 事务提交后,调用微赞接口(不回滚本地事务)
-        try {
-            Clinic clinic = clinicMapper.selectById(id);
-
-            // 调用微赞创建门店
-            Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码");
-            Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码");
-            Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码");
-            log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}",
-                    clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
-            JsonNode vzanStoreResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
-            log.info("微赞创建门店成功,出参={}", vzanStoreResponse);
-
-            // 查询代理信息获取parentMobile
-            ProvincialAgent agent = provincialAgentMapper.selectById(request.getAgentId());
-
-            String parentMobile = agent.getContactInfo();
-            if (parentMobile == null || parentMobile.trim().isEmpty()) {
-                throw new IllegalArgumentException("代理联系方式为空,agentId=" + request.getAgentId());
-            }
-
-            // 调用微赞创建代理
-            log.info("调用微赞创建代理,入参 agentMobile={}, agentName={}, levelCode=1, storeName={}, parentMobile={}",
-                    clinic.getContactPhone(), clinic.getName(), clinic.getName(), parentMobile);
-            JsonNode vzanAgentResponse = vzanHttpUtil.createAgent(
-                    clinic.getContactPhone(),  // agentMobile传门店的手机号
-                    clinic.getName(),          // agentName传门店的名称
-                    1,                        // levelCode传1
-                    parentMobile,              // parentMobile传代理的contact_info
-                    clinic.getName()          // storeName传门店的name
-            );
-            log.info("微赞创建代理成功,出参={}", vzanAgentResponse);
-
-        } catch (Exception e) {
-            // 捕获微赞接口异常,不回滚本地事务
-            log.error("调用微赞接口失败,但本地数据已保存成功,clinicId={}", id, e);
-            throw new IllegalStateException("调用微赞失败", e);
-        }
-
         return detail(id);
-    }
-
-    private Integer parseRegionCode(String code, String fieldName) {
-        if (code == null || code.trim().isEmpty()) {
-            throw new IllegalArgumentException(fieldName + "不能为空");
-        }
-        try {
-            return Integer.parseInt(code);
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException(fieldName + "格式错误: " + code);
-        }
     }
 
     private void addCode(Set<String> codes, String code) {
@@ -432,6 +377,7 @@
         item.setPhone(row.getPhone() == null ? "-" : PhoneMasker.mask(row.getPhone()));
         item.setRegionName(defaultText(row.getRegionName()));
         item.setStatus(row.getStatus());
+        item.setAgentName(defaultText(row.getAgentName()));
         item.setUserCount(row.getUserCount() == null ? 0 : row.getUserCount());
         item.setCreatedAt(row.getCreatedAt());
         item.setBusinessLicenseUrl(trimToNull(row.getBusinessLicenseUrl()));
diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/vo/ClinicListItemVO.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/vo/ClinicListItemVO.java
index db7b9de..708d38f 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/vo/ClinicListItemVO.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/vo/ClinicListItemVO.java
@@ -16,6 +16,7 @@
     private String phone;
     private String regionName;
     private Integer status;
+    private String agentName;
     private Integer userCount;
     private LocalDateTime createdAt;
     private String businessLicenseUrl;
diff --git a/tcm-training-admin-backend/src/main/resources/mapper/ClinicQueryMapper.xml b/tcm-training-admin-backend/src/main/resources/mapper/ClinicQueryMapper.xml
index 848abd3..0ccde7d 100644
--- a/tcm-training-admin-backend/src/main/resources/mapper/ClinicQueryMapper.xml
+++ b/tcm-training-admin-backend/src/main/resources/mapper/ClinicQueryMapper.xml
@@ -18,6 +18,9 @@
         <if test="phone != null and phone.trim() != ''">
             AND c.contact_phone LIKE CONCAT('%', #{phone}, '%')
         </if>
+        <if test="agentName != null and agentName.trim() != ''">
+            AND pa.agent_name LIKE CONCAT('%', #{agentName}, '%')
+        </if>
         <if test="createdFrom != null">
             AND c.created_at &gt;= #{createdFrom}
         </if>
@@ -44,10 +47,12 @@
                 ELSE NULL
             END AS regionName,
             c.status,
+            pa.agent_name AS agentName,
             COUNT(u.id) AS userCount,
             c.created_at AS createdAt,
             c.business_license_url AS businessLicenseUrl
         FROM tcm_clinic c
+        LEFT JOIN tcm_provincial_agent pa ON pa.id = c.agent_id AND pa.deleted = 0
         LEFT JOIN tcm_app_user u ON u.clinic_id = c.id AND u.deleted = 0
         <include refid="ClinicWhere"/>
         GROUP BY
@@ -58,6 +63,7 @@
             c.city_name,
             c.district_name,
             c.status,
+            pa.agent_name,
             c.created_at,
             c.business_license_url
         ORDER BY c.id DESC
@@ -78,10 +84,12 @@
                 ELSE NULL
             END AS regionName,
             c.status,
+            pa.agent_name AS agentName,
             COUNT(u.id) AS userCount,
             c.created_at AS createdAt,
             c.business_license_url AS businessLicenseUrl
         FROM tcm_clinic c
+        LEFT JOIN tcm_provincial_agent pa ON pa.id = c.agent_id AND pa.deleted = 0
         LEFT JOIN tcm_app_user u ON u.clinic_id = c.id AND u.deleted = 0
         <include refid="ClinicWhere"/>
         GROUP BY
@@ -92,6 +100,7 @@
             c.city_name,
             c.district_name,
             c.status,
+            pa.agent_name,
             c.created_at,
             c.business_license_url
         ORDER BY c.id DESC
diff --git a/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/app/register/service/AppRegisterRegionServiceTest.java b/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/app/register/service/AppRegisterRegionServiceTest.java
index c6449c6..ba020b2 100644
--- a/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/app/register/service/AppRegisterRegionServiceTest.java
+++ b/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/app/register/service/AppRegisterRegionServiceTest.java
@@ -2,6 +2,7 @@
 
 import com.tcm.training.admin.app.register.entity.SysRegion;
 import com.tcm.training.admin.app.register.mapper.SysRegionMapper;
+import com.tcm.training.admin.app.register.vo.AppRegisterRegionDatasetVO;
 import com.tcm.training.admin.app.register.vo.AppRegisterRegionOptionVO;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -9,6 +10,7 @@
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -52,6 +54,28 @@
     }
 
     /**
+     * APP 完整地区数据集应过滤区县层级的市辖区占位节点。
+     */
+    @Test
+    void buildRegionDatasetShouldHideMunicipalDistrictPlaceholder() {
+        SysRegion province = buildRegion(1L, "江西省", "360000", 0L);
+        SysRegion city = buildRegion(2L, "吉安市", "360800", 1L);
+        SysRegion placeholder = buildRegion(3L, "吉安市市辖区", "360801", 2L);
+        SysRegion district = buildRegion(4L, "吉州区", "360802", 2L);
+        when(sysRegionMapper.selectAllOrdered()).thenReturn(
+                Arrays.asList(province, city, placeholder, district)
+        );
+
+        AppRegisterRegionDatasetVO dataset = appRegisterRegionService.buildRegionDataset();
+
+        assertEquals(1, dataset.getAreas().size());
+        assertEquals(1, dataset.getAreas().get(0).size());
+        assertEquals(1, dataset.getAreas().get(0).get(0).size());
+        assertEquals("吉州区", dataset.getAreas().get(0).get(0).get(0).getLabel());
+        assertEquals("360802", dataset.getAreas().get(0).get(0).get(0).getValue());
+    }
+
+    /**
      * 海诊通仅返回地区名称时,应通过 sys_region 解析出完整编码。
      */
     @Test
diff --git a/tcm-training-admin/admin.zip b/tcm-training-admin/admin.zip
deleted file mode 100644
index 6ad07d0..0000000
--- a/tcm-training-admin/admin.zip
+++ /dev/null
Binary files differ
diff --git a/tcm-training-admin/config/routes.ts b/tcm-training-admin/config/routes.ts
index 484ca6e..8b51e35 100644
--- a/tcm-training-admin/config/routes.ts
+++ b/tcm-training-admin/config/routes.ts
@@ -192,22 +192,28 @@
             component: './member/user/redirect',
           },
           {
-            name: '用户管理',
-            path: '/member/user/manage',
-            access: 'member:user:view',
-            component: './member/user',
+            name: '省代用户',
+            path: '/member/user/agent',
+            access: 'member:agent:view',
+            component: './member/agent',
           },
           {
-            name: '诊所管理',
+            name: '诊所用户',
             path: '/member/user/clinic',
             access: 'member:clinic:view',
             component: './member/clinic',
           },
           {
-            name: '省代理管理',
-            path: '/member/user/agent',
-            access: 'member:agent:view',
-            component: './member/agent',
+            name: '普通用户',
+            path: '/member/user/manage',
+            access: 'member:user:view',
+            component: './member/user',
+          },
+          {
+            name: '会员用户',
+            path: '/member/user/member-users',
+            access: 'member:user:member:view',
+            component: './member/user/member-users',
           },
           {
             name: '编辑诊所',
@@ -222,12 +228,6 @@
             hideInMenu: true,
             access: 'member:clinic:view',
             component: './member/clinic/detail',
-          },
-          {
-            name: '会员用户',
-            path: '/member/user/member-users',
-            access: 'member:user:member:view',
-            component: './member/user/member-users',
           },
           {
             name: '会员用户详情',
@@ -273,26 +273,21 @@
   },
   {
     path: '/live',
-    name: '课程与学习',
+    name: '内容',
     icon: 'read',
     access: 'canViewLiveModule',
     routes: [
       {
         path: '/live',
-        redirect: '/live/course',
+        redirect: '/live/course/broadcast',
       },
       {
         path: '/live/expert',
         redirect: '/live/course/expert',
       },
       {
-        path: '/live/course',
-        name: '课程管理',
+        name: '类直播',
         routes: [
-          {
-            path: '/live/course',
-            redirect: '/live/course/broadcast',
-          },
           {
             name: '养生直播',
             path: '/live/course/broadcast',
@@ -314,36 +309,67 @@
             component: './live/broadcast/edit',
           },
           {
-            name: '微赞直播间设置',
-            path: '/live/course/live-room-setting',
-            access: 'live:room-setting:view',
-            component: './live/live-room-setting',
-          },          {
-            name: '直播预约记录',
+            name: '预约记录',
             path: '/live/course/reserve-records',
             access: 'live:reserve:view',
             component: './live/reserve-records',
           },
           {
-            name: '直播观看记录',
+            name: '观看记录',
             path: '/live/course/watch-records',
             access: 'live:watch:view',
             component: './live/watch-records',
           },
           {
-            name: '分类管理',
+            name: '直播预约记录跳转',
+            path: '/live/course/broadcast/:courseId/sessions/:sessionId/reserves',
+            hideInMenu: true,
+            access: 'live:reserve:view',
+            component: './live/record-redirect',
+          },
+          {
+            name: '直播观看记录跳转',
+            path: '/live/course/broadcast/:courseId/sessions/:sessionId/watch-records',
+            hideInMenu: true,
+            access: 'live:watch:view',
+            component: './live/record-redirect',
+          },
+        ],
+      },
+      {
+        name: '微赞直播',
+        routes: [
+          {
+            name: '直播设置',
+            path: '/live/course/live-room-setting',
+            access: 'live:room-setting:view',
+            component: './live/live-room-setting',
+          },
+        ],
+      },
+      {
+        name: '课程',
+        routes: [
+          {
+            name: '添加分类',
             path: '/live/course/category',
             access: 'live:category:view',
             component: './live/category',
           },
           {
-            name: '养生课程',
+            name: '添加专家',
+            path: '/live/course/expert',
+            access: 'live:expert:view',
+            component: './live/expert',
+          },
+          {
+            name: '添加课堂',
             path: '/live/course/health',
             access: 'live:health-course:view',
             component: './live/health',
           },
           {
-            name: '视频与课程资源管理',
+            name: '添加视频',
             path: '/live/course/video-resources',
             access: 'live:health-course:view',
             component: './live/video-resources',
@@ -397,61 +423,11 @@
             access: 'live:health-course:video:edit',
             component: './live/health/video-manage',
           },
-          {
-            name: '直播预约记录跳转',
-            path: '/live/course/broadcast/:courseId/sessions/:sessionId/reserves',
-            hideInMenu: true,
-            access: 'live:reserve:view',
-            component: './live/record-redirect',
-          },
-          {
-            name: '直播观看记录跳转',
-            path: '/live/course/broadcast/:courseId/sessions/:sessionId/watch-records',
-            hideInMenu: true,
-            access: 'live:watch:view',
-            component: './live/record-redirect',
-          },
-          {
-            name: '授课专家管理',
-            path: '/live/course/expert',
-            access: 'live:expert:view',
-            component: './live/expert',
-          },
-          {
-            path: '/live/course/project-introduction/create',
-            hideInMenu: true,
-            redirect: '/live/promotion/project-introduction/create',
-          },
-          {
-            path: '/live/course/project-introduction',
-            hideInMenu: true,
-            redirect: '/live/promotion/project-introduction',
-          },
-          {
-            path: '/live/course/project-introduction-password',
-            hideInMenu: true,
-            redirect: '/live/promotion/project-introduction-password',
-          },
-          {
-            path: '/live/course/health-popularization/create',
-            hideInMenu: true,
-            redirect: '/live/promotion/health-popularization/create',
-          },
-          {
-            path: '/live/course/health-popularization',
-            hideInMenu: true,
-            redirect: '/live/promotion/health-popularization',
-          },
         ],
       },
       {
-        path: '/live/promotion',
-        name: '宣传知识',
+        name: '科普',
         routes: [
-          {
-            path: '/live/promotion',
-            redirect: '/live/promotion/project-introduction',
-          },
           {
             name: '健康知识',
             path: '/live/promotion/health-popularization',
@@ -492,6 +468,17 @@
             access: 'live:health-promotion:edit',
             component: './live/health-promotion/manage',
           },
+        ],
+      },
+      {
+        name: '项目',
+        routes: [
+          {
+            name: '项目密码',
+            path: '/live/promotion/project-introduction-password',
+            access: 'live:project-introduction:view',
+            component: './live/project-introduction-password',
+          },
           {
             name: '项目介绍',
             path: '/live/promotion/project-introduction',
@@ -513,10 +500,29 @@
             component: './live/project-introduction/manage',
           },
           {
-            name: '项目介绍密码',
-            path: '/live/promotion/project-introduction-password',
-            access: 'live:project-introduction:view',
-            component: './live/project-introduction-password',
+            path: '/live/course/project-introduction/create',
+            hideInMenu: true,
+            redirect: '/live/promotion/project-introduction/create',
+          },
+          {
+            path: '/live/course/project-introduction',
+            hideInMenu: true,
+            redirect: '/live/promotion/project-introduction',
+          },
+          {
+            path: '/live/course/project-introduction-password',
+            hideInMenu: true,
+            redirect: '/live/promotion/project-introduction-password',
+          },
+          {
+            path: '/live/course/health-popularization/create',
+            hideInMenu: true,
+            redirect: '/live/promotion/health-popularization/create',
+          },
+          {
+            path: '/live/course/health-popularization',
+            hideInMenu: true,
+            redirect: '/live/promotion/health-popularization',
           },
         ],
       },
diff --git a/tcm-training-admin/src/app.tsx b/tcm-training-admin/src/app.tsx
index f0c27c3..f60de8d 100644
--- a/tcm-training-admin/src/app.tsx
+++ b/tcm-training-admin/src/app.tsx
@@ -1,4 +1,4 @@
-import type { Settings as LayoutSettings } from '@ant-design/pro-components';
+import type { MenuDataItem, Settings as LayoutSettings } from '@ant-design/pro-components';
 import { SettingDrawer } from '@ant-design/pro-components';
 import type { RunTimeLayoutConfig } from '@umijs/max';
 import { history } from '@umijs/max';
@@ -26,6 +26,16 @@
   loading?: boolean;
 };
 
+const ensureMenuKeys = (menuData: MenuDataItem[], parentKey = 'menu'): MenuDataItem[] =>
+  menuData.map((item, index) => {
+    const key = item.key || item.path || `${parentKey}-${String(item.name || index)}`;
+
+    return {
+      ...item,
+      key,
+      children: item.children ? ensureMenuKeys(item.children, String(key)) : item.children,
+    };
+  });
 
 /**
  * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
@@ -79,6 +89,7 @@
       );
     },
     ...initialState?.settings,
+    menuDataRender: ensureMenuKeys,
     pageTitleRender: () => '管理后台',
   };
 };
diff --git a/tcm-training-admin/src/pages/live/broadcast/index.tsx b/tcm-training-admin/src/pages/live/broadcast/index.tsx
index 1cae71c..54d541e 100644
--- a/tcm-training-admin/src/pages/live/broadcast/index.tsx
+++ b/tcm-training-admin/src/pages/live/broadcast/index.tsx
@@ -170,7 +170,7 @@
         <Space size={16} className="live-broadcast-table__actions">
           {canDetail ? (
             <button type="button" onClick={() => history.push(`/live/course/broadcast/${record.id}`)}>
-              详情
+              类直播
             </button>
           ) : null}
           {canEdit ? (
diff --git a/tcm-training-admin/src/pages/member/clinic/index.tsx b/tcm-training-admin/src/pages/member/clinic/index.tsx
index 321c11d..2690f29 100644
--- a/tcm-training-admin/src/pages/member/clinic/index.tsx
+++ b/tcm-training-admin/src/pages/member/clinic/index.tsx
@@ -12,6 +12,7 @@
   regionCode?: string[];
   name?: string;
   phone?: string;
+  agentName?: string;
   status?: number;
   createdRange?: Dayjs[];
 };
@@ -22,6 +23,7 @@
   phone?: string;
   regionName?: string;
   status: number;
+  agentName?: string;
   userCount: number;
   createdAt: string;
   businessLicenseUrl?: string;
@@ -34,6 +36,7 @@
   regionCode: getSelectedRegionCode(values.regionCode),
   name: values.name?.trim() || undefined,
   phone: values.phone?.trim() || undefined,
+  agentName: values.agentName?.trim() || undefined,
   status: values.status,
   createdFrom: values.createdRange?.[0]
     ? dayjs(values.createdRange[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
@@ -145,6 +148,12 @@
       render: (value) => (value === 1 ? '启用' : value === 2 ? '注销' : '禁用'),
     },
     {
+      title: '省代',
+      dataIndex: 'agentName',
+      width: 140,
+      render: (value) => value || '-',
+    },
+    {
       title: '注册时间',
       dataIndex: 'createdAt',
       width: 180,
@@ -197,6 +206,9 @@
                 ]}
               />
             </Form.Item>
+            <Form.Item label="省代" name="agentName">
+              <Input placeholder="请输入" allowClear />
+            </Form.Item>
             <Form.Item label="注册时间" name="createdRange">
               <DatePicker.RangePicker className="member-clinic-search-form__range" />
             </Form.Item>
diff --git a/traditionalApp/App.vue b/traditionalApp/App.vue
index b0e150a..b8d3dfa 100644
--- a/traditionalApp/App.vue
+++ b/traditionalApp/App.vue
@@ -11,10 +11,6 @@
 	import { cacheInviteCode } from '@/utils/invite-cache.js'
 	import { initJPushLifecycle } from '@/utils/push.js'
 	import {
-		checkAppVersionUpdate,
-		resumeForceUpdatePrompt
-	} from '@/utils/app-version.js'
-	import {
 		getOpeninstallInstallData,
 		initOpeninstall,
 		registerOpeninstallWakeUp
@@ -30,7 +26,6 @@
 		initOpeninstall()
 		registerOpeninstallWakeUp(handleOpeninstallPayload)
 		getOpeninstallInstallData(handleOpeninstallPayload)
-		checkAppVersionUpdate()
 		// #endif
 	})
 
@@ -39,9 +34,6 @@
 	 */
 	onShow(() => {
 		handleOpenByScheme()
-		// #ifdef APP-PLUS
-		resumeForceUpdatePrompt()
-		// #endif
 	})
 
 	/**
diff --git a/traditionalApp/config/index.js b/traditionalApp/config/index.js
index 968c0f7..d89ad39 100644
--- a/traditionalApp/config/index.js
+++ b/traditionalApp/config/index.js
@@ -2,7 +2,6 @@
 //	 BASE_URL: 'http://221.182.45.100:48080',
 	BASE_URL: 'https://haizhentong.com',
 	OPENINSTALL_URL: 'https://m.malink.cn/s/7bIzeu',
-	APP_STORE_URL: '',
 	uploadUrl: "https://haizhentong.com/api/open/file/upload",
 	imageUrl: 'http://221.182.45.100:11224/images/'
 };
diff --git a/traditionalApp/manifest.json b/traditionalApp/manifest.json
index 216a56a..e9d19f7 100644
--- a/traditionalApp/manifest.json
+++ b/traditionalApp/manifest.json
@@ -3,8 +3,8 @@
     "name" : "海诊康道",
     "appid" : "__UNI__7B5B0CA",
     "description" : "",
-    "versionName" : "1.0.23",
-    "versionCode" : 122,
+    "versionName" : "1.0.0",
+    "versionCode" : 100,
     "transformPx" : false,
     /* 5+App特有相关 */
     "app-plus" : {
diff --git a/traditionalApp/pages/articleDetail/articleDetail.vue b/traditionalApp/pages/articleDetail/articleDetail.vue
index 90597c9..8028439 100644
--- a/traditionalApp/pages/articleDetail/articleDetail.vue
+++ b/traditionalApp/pages/articleDetail/articleDetail.vue
@@ -21,7 +21,7 @@
 
 		<!-- 内容区域 -->
 		<view class="content-area">
-			<text class="content-title">{{ detail.title || '健康科普详情' }}</text>
+			<text v-if="isVideoType" class="content-title">{{ detail.title || '健康科普详情' }}</text>
 			<text v-if="isVideoType && videoSubtitle" class="content-subtitle">{{ videoSubtitle }}</text>
 			<rich-text v-if="!isVideoType" :nodes="contentNodes" class="rich-content"></rich-text>
 			<view v-if="loading" class="status-text">加载中...</view>
@@ -288,7 +288,6 @@
 	}
 
 	.rich-content {
-		margin-top: 24rpx;
 		width: 100%;
 		font-size: 28rpx;
 		line-height: 1.8;
diff --git a/traditionalApp/pages/good/projectDetail.vue b/traditionalApp/pages/good/projectDetail.vue
index dfd2943..dac4129 100644
--- a/traditionalApp/pages/good/projectDetail.vue
+++ b/traditionalApp/pages/good/projectDetail.vue
@@ -6,7 +6,7 @@
 		<!-- 导航栏 -->
 		<view class="nav-bar">
 			<image class="back-icon" src="/static/image/home/left.png" mode="aspectFit" @tap="goBack"></image>
-			<text class="nav-title">{{ detail.title || '详情' }}</text>
+			<text class="nav-title">详情</text>
 			<view class="nav-right-placeholder"></view>
 		</view>
 
@@ -34,8 +34,6 @@
 
 				<!-- 图文内容 -->
 				<view v-if="detail.contentType === 1">
-					<!-- 标题 -->
-					<view class="detail-title">{{ detail.title }}</view>
 					<!-- 内容 -->
 					<view v-if="detail.content" class="html-content">
 						<rich-text :nodes="detail.content"></rich-text>
diff --git a/traditionalApp/pages/home/home.vue b/traditionalApp/pages/home/home.vue
index 5648996..5faddfe 100644
--- a/traditionalApp/pages/home/home.vue
+++ b/traditionalApp/pages/home/home.vue
@@ -376,7 +376,7 @@
 			const userAgent = res?.data?.ua?.trim();
 			console.log('[goWeiZanLive] 解析目标URL:', targetUrl);
 
-			if (res?.code !== 0 || !targetUrl || !userAgent) {
+			if (res?.code !== 0 || !targetUrl) {
 				console.log('[goWeiZanLive] 获取跳转地址失败,code:', res?.code, 'msg:', res?.msg);
 				uni.showToast({ title: res?.msg || '获取直播间地址失败', icon: 'none' });
 				return;
@@ -1091,12 +1091,14 @@
 
 			.course-info {
 				flex: 1;
+				min-width: 0;
 				display: flex;
 				flex-direction: column;
 				justify-content: space-between;
 
 				.course-top {
 					display: flex;
+					min-width: 0;
 					align-items: center;
 					margin-top: 4rpx;
 
@@ -1114,6 +1116,7 @@
 						font-weight: bold;
 						color: #333;
 						flex: 1;
+						min-width: 0;
 						overflow: hidden;
 						text-overflow: ellipsis;
 						white-space: nowrap;
diff --git a/traditionalApp/pages/login/login.vue b/traditionalApp/pages/login/login.vue
index 67c2a4d..2e5b0d1 100644
--- a/traditionalApp/pages/login/login.vue
+++ b/traditionalApp/pages/login/login.vue
@@ -166,13 +166,9 @@
 	})
 
 	/**
-	 * 关闭登录页,优先返回上一页,没有页面栈时回首页。
+	 * 关闭登录页统一回到首页,避免从需要登录的 tabbar 页面返回后再次触发登录拦截。
 	 */
 	const handleClose = () => {
-		if (getCurrentPages().length > 1) {
-			uni.navigateBack()
-			return
-		}
 		uni.switchTab({
 			url: '/pages/home/home'
 		})
diff --git a/traditionalApp/pages/mine/mine.vue b/traditionalApp/pages/mine/mine.vue
index f057f8a..fd05866 100644
--- a/traditionalApp/pages/mine/mine.vue
+++ b/traditionalApp/pages/mine/mine.vue
@@ -36,7 +36,7 @@
 			</view>
 
 			<view class="list-section card-box">
-				<view class="list-item" v-for="(item, index) in bottomMenuList" :key="`${item.title}-${index}`" @tap="goPage(item)">
+				<view class="list-item" v-for="(item, index) in bottomMenuList" :key="`${item.title}-${index}`" @tap="handleBottomMenuClick(item)">
 					<view class="list-left">
 						<image class="list-icon" :class="item.iconClass" :src="item.icon" mode="aspectFit"></image>
 						<text class="list-title">{{ item.title }}</text>
@@ -55,7 +55,6 @@
 import { onLoad, onShow } from '@dcloudio/uni-app'
 import { getNoticeList } from '../notice/service'
 import { getUserCenter } from './service'
-import { getRuntimeVersionName } from '@/utils/app-version.js'
 import { hasStoredSession } from '@/utils/auth-session.js'
 import manifestConfig from '../../manifest.json'
 
@@ -104,7 +103,7 @@
 const statusBarHeight = ref(0)
 const unreadNoticeTotal = ref(0)
 const manifestVersionName = String(manifestConfig.versionName || '')
-const appVersionText = ref(manifestVersionName)
+const appVersionText = manifestVersionName
 const userCenter = ref<UserCenterData>(createDefaultUserCenter())
 const userRole = computed<UserRole>(() => {
 	return isDoctorLikeUser(userCenter.value) ? 'doctor' : 'personal'
@@ -216,7 +215,6 @@
 onLoad(() => {
 	const systemInfo = uni.getSystemInfoSync()
 	statusBarHeight.value = systemInfo.statusBarHeight || 20
-	resolveAppVersion()
 	syncCachedUserInfo()
 })
 
@@ -384,6 +382,13 @@
 }
 
 /**
+ * 处理底部普通菜单跳转。
+ */
+async function handleBottomMenuClick(item: MenuItem) {
+	goPage(item)
+}
+
+/**
  * 统一跳转个人中心相关页面。
  */
 function goPage(target: string | MenuItem) {
@@ -413,17 +418,6 @@
 			url: '/pages/login/login',
 		})
 	}, 200)
-}
-
-/**
- * APP 环境读取当前安装包版本,非 APP 环境使用 manifest 版本兜底。
- */
-function resolveAppVersion() {
-	getRuntimeVersionName(manifestVersionName).then((versionName) => {
-		appVersionText.value = versionName || manifestVersionName
-	}).catch(() => {
-		appVersionText.value = manifestVersionName
-	})
 }
 
 /**
@@ -710,6 +704,7 @@
 				color: #1A1A1A;
 				margin-left: 22rpx;
 			}
+
 		}
 	}
 }
diff --git a/traditionalApp/utils/app-version.js b/traditionalApp/utils/app-version.js
deleted file mode 100644
index 308eafa..0000000
--- a/traditionalApp/utils/app-version.js
+++ /dev/null
@@ -1,199 +0,0 @@
-import config from '@/config/index.js'
-
-const CHECK_VERSION_URL = '/api/app/version/check'
-let checkingVersion = false
-let updatePopupVisible = false
-let latestUpdateInfo = null
-
-/**
- * 检查 APP 是否存在管理后台配置的新版本。
- */
-export function checkAppVersionUpdate() {
-	// #ifndef APP-PLUS
-	return
-	// #endif
-	if (checkingVersion) {
-		return
-	}
-	checkingVersion = true
-	resolveRuntimeVersionInfo()
-		.then((runtimeInfo) => requestVersionCheck(runtimeInfo))
-		.then((payload) => {
-			const updateInfo = payload && payload.code === 0 ? payload.data : null
-			if (!updateInfo || updateInfo.hasUpdate !== true) {
-				return
-			}
-			const downloadUrl = resolveUpdateUrl(updateInfo)
-			if (!downloadUrl) {
-				return
-			}
-			showUpdateModal(Object.assign({}, updateInfo, {
-				downloadUrl
-			}))
-		})
-		.catch(() => {
-		})
-		.finally(() => {
-			checkingVersion = false
-		})
-}
-
-/**
- * 读取 APP 原生版本信息,优先使用 manifest 中的 versionCode。
- */
-function resolveRuntimeVersionInfo() {
-	return new Promise((resolve) => {
-		const fallbackInfo = {
-			platform: resolvePlatform(),
-			versionCode: 0,
-			versionName: ''
-		}
-		if (typeof plus === 'undefined' || !plus.runtime || typeof plus.runtime.getProperty !== 'function') {
-			resolve(fallbackInfo)
-			return
-		}
-		plus.runtime.getProperty(plus.runtime.appid, (info) => {
-			resolve({
-				platform: resolvePlatform(),
-				versionCode: normalizeVersionCode(info && info.versionCode),
-				versionName: normalizeText(info && info.version)
-			})
-		})
-	})
-}
-
-/**
- * 读取当前安装 APP 的展示版本号。
- */
-export function getRuntimeVersionName(fallbackVersionName = '') {
-	return resolveRuntimeVersionInfo().then((runtimeInfo) => {
-		return normalizeText(runtimeInfo.versionName) || normalizeText(fallbackVersionName)
-	})
-}
-
-/**
- * 将当前运行平台转换为后端版本检查平台值。
- */
-function resolvePlatform() {
-	const platform = normalizeText(uni.getSystemInfoSync().platform).toLowerCase()
-	return platform === 'ios' ? 'IOS' : 'ANDROID'
-}
-
-/**
- * 请求后端版本检查接口。
- */
-function requestVersionCheck(runtimeInfo) {
-	return new Promise((resolve, reject) => {
-		uni.request({
-			url: `${config.BASE_URL}${CHECK_VERSION_URL}`,
-			method: 'GET',
-			data: {
-				platform: runtimeInfo.platform,
-				currentVersionCode: runtimeInfo.versionCode,
-				currentVersionNo: runtimeInfo.versionName
-			},
-			header: {
-				'content-type': 'application/json'
-			},
-			success: (res) => {
-				resolve(res.data || {})
-			},
-			fail: reject
-		})
-	})
-}
-
-/**
- * 解析当前平台可用的更新地址,iOS 只允许跳转 App Store。
- */
-function resolveUpdateUrl(updateInfo) {
-	if (isIosPlatform()) {
-		return normalizeText(config.APP_STORE_URL)
-	}
-	return normalizeText(updateInfo.downloadUrl)
-}
-
-/**
- * 展示版本更新弹窗,强制更新时禁止跳过。
- */
-function showUpdateModal(updateInfo) {
-	latestUpdateInfo = updateInfo
-	if (updatePopupVisible) {
-		return
-	}
-	updatePopupVisible = true
-	const forceUpdate = updateInfo.forceUpdate === true
-	let confirmed = false
-	uni.showModal({
-		title: forceUpdate ? '发现新版本,请立即更新' : '发现新版本',
-		content: buildUpdateContent(updateInfo),
-		showCancel: !forceUpdate,
-		cancelText: '暂不更新',
-		confirmText: '立即更新',
-		success: (modalResult) => {
-			if (modalResult.confirm) {
-				confirmed = true
-				openDownloadUrl(updateInfo.downloadUrl)
-				return
-			}
-		},
-		complete: () => {
-			updatePopupVisible = false
-			if (forceUpdate && !confirmed) {
-				setTimeout(() => {
-					showUpdateModal(updateInfo)
-				}, 100)
-			}
-		}
-	})
-}
-
-/**
- * 拼接更新提示内容,确保后台更新说明能直接展示给用户。
- */
-function buildUpdateContent(updateInfo) {
-	const versionNo = normalizeText(updateInfo.latestVersionNo)
-	const updateNotice = normalizeText(updateInfo.updateNotice) || '请更新到最新版本后继续使用。'
-	return versionNo ? `最新版本:${versionNo}\n${updateNotice}` : updateNotice
-}
-
-/**
- * 打开后台配置的安装包或下载地址。
- */
-function openDownloadUrl(downloadUrl) {
-	if (!downloadUrl) {
-		return
-	}
-	plus.runtime.openURL(downloadUrl)
-}
-
-/**
- * APP 回到前台时,强制更新场景需要继续拦截用户使用旧版本。
- */
-export function resumeForceUpdatePrompt() {
-	if (latestUpdateInfo && latestUpdateInfo.forceUpdate === true && !updatePopupVisible) {
-		showUpdateModal(latestUpdateInfo)
-	}
-}
-
-/**
- * 判断当前是否为 iOS APP。
- */
-function isIosPlatform() {
-	return resolvePlatform() === 'IOS'
-}
-
-/**
- * 规范化可选字符串。
- */
-function normalizeText(value) {
-	return String(value || '').trim()
-}
-
-/**
- * 规范化原生 versionCode,避免异常值传到后端。
- */
-function normalizeVersionCode(value) {
-	const versionCode = Number(value || 0)
-	return Number.isFinite(versionCode) && versionCode >= 0 ? versionCode : 0
-}

--
Gitblit v1.7.1