فهرست منبع

feat: 同步后端许愿树列表接口,首页使用真实封面图

- tree.ts: GET改POST请求,匹配后端 @RequestBody WishingTreeListReq
- TreeCard: 优先显示接口返回的 coverImage,无图时回退渐变占位
- request.ts: 适配后端 Result<T> 响应格式 (code/msg/data)
- vite.config.ts: 新增 /api 代理到 localhost:8003

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tanlie 1 ماه پیش
والد
کامیت
8b5fc56209
24فایلهای تغییر یافته به همراه616 افزوده شده و 55 حذف شده
  1. 2 0
      .gitignore
  2. 68 5
      wishing-platform/platform-service/platform-service-mobile/pom.xml
  3. 59 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/FeignConfiguration.java
  4. 19 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/MobileMvcConfig.java
  5. 34 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/MyBatisPlusConfig.java
  6. 114 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/MyMetaObjectHandler.java
  7. 17 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/SecurityConfiguration.java
  8. 0 22
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/controller/TestController.java
  9. 19 6
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/controller/WishingTreeController.java
  10. 17 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/mapper/WishingTreeMapper.java
  11. 27 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/req/WishingTreeListReq.java
  12. 39 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/resp/WishingTreeListResp.java
  13. 25 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/WishTreeService.java
  14. 82 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/WishTreeServiceImpl.java
  15. 37 0
      wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-dev.yml
  16. 2 5
      wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-local.yml
  17. 1 2
      wishing-platform/platform-service/platform-service-mobile/src/main/resources/application.yml
  18. 1 1
      wishing-platform/platform-service/platform-service-upms/src/main/java/cn/qinys/platform/upms/config/UpmsMvcConfig.java
  19. 2 2
      wishing-platform/platform-service/platform-service-upms/src/main/resources/application-local.yml
  20. 6 5
      wishing-tree-h5/src/api/request.ts
  21. 19 4
      wishing-tree-h5/src/api/tree.ts
  22. 15 3
      wishing-tree-h5/src/components/TreeCard.vue
  23. 6 0
      wishing-tree-h5/vite.config.ts
  24. 5 0
      working-history.md

+ 2 - 0
.gitignore

@@ -14,3 +14,5 @@ wishing-platform/platform-service/platform-service-mobile/target/
 wishing-platform/platform-entity/platform-entity-upms/target/
 wishing-platform/platform-service/platform-service-upms/target/
 wishing-platform/platform-service/platform-service-admin/target/
+wishing-admin-vue3/.idea/
+wishing-admin-vue3/admin/

+ 68 - 5
wishing-platform/platform-service/platform-service-mobile/pom.xml

@@ -19,15 +19,15 @@
             <artifactId>platform-core-base</artifactId>
             <version>1.0.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.qinys</groupId>
             <artifactId>platform-entity-wishing</artifactId>
             <version>1.0.0-SNAPSHOT</version>
-        </dependency>
-        <!--数据库-->
-        <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <scope>compile</scope>
         </dependency>
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
@@ -38,5 +38,68 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-jsqlparser</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
     </dependencies>
+    <build>
+        <finalName>wishing-h5</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.yaml</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>3.5.14</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.12.0</version>
+                <configuration>
+                    <parameters>true</parameters>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>

+ 59 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/FeignConfiguration.java

@@ -0,0 +1,59 @@
+package cn.qinys.platform.config;
+
+
+import feign.RequestInterceptor;
+import feign.RequestTemplate;
+import feign.Retryer;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Feign配置类
+ *
+ * @author v-lishiquan.gx@chinatelecom.cn
+ * @date 2020-08-26
+ */
+@Configuration
+@Slf4j
+public class FeignConfiguration implements RequestInterceptor {
+
+    @Bean
+    Retryer feignRetryer() {
+        return Retryer.NEVER_RETRY;
+    }
+
+    @Override
+    public void apply(RequestTemplate requestTemplate) {
+        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+        if (Objects.nonNull(requestAttributes)) {
+            RequestContextHolder.setRequestAttributes(requestAttributes, true);
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+            HttpServletRequest request = attributes.getRequest();
+            // 添加请求头
+            Enumeration<String> headerNames = request.getHeaderNames();
+            if (headerNames != null) {
+                while (headerNames.hasMoreElements()) {
+                    String name = headerNames.nextElement();
+                    if(this.removeHead().contains(name)){
+                        continue;
+                    }
+                    String values = request.getHeader(name);
+                    requestTemplate.header(name, values);
+                }
+            }
+        }
+    }
+
+    private List<String> removeHead() {
+        return List.of("content-length");
+    }
+}

+ 19 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/MobileMvcConfig.java

@@ -0,0 +1,19 @@
+package cn.qinys.platform.config;
+
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+
+/**
+ * Description:
+ *
+ * @author tanlie
+ * Date: 2024/3/4 15:44
+ */
+@Configuration
+public class MobileMvcConfig implements WebMvcConfigurer {
+
+
+
+}

+ 34 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/MyBatisPlusConfig.java

@@ -0,0 +1,34 @@
+package cn.qinys.platform.config;
+
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+
+
+/**
+ * Description:
+ *
+ * @author tanlie
+ * Date: 2024/2/22 11:27
+ */
+@Configuration
+public class MyBatisPlusConfig {
+    /**
+     * 添加租户插件
+     * 添加分页插件
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
+        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
+        return interceptor;
+    }
+
+
+}

+ 114 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/MyMetaObjectHandler.java

@@ -0,0 +1,114 @@
+package cn.qinys.platform.config;
+
+import cn.qinys.platform.base.security.utils.CurrentUtils;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * MyBatis-Plus 自动填充处理器:
+ * - 支持 Java 字段类型 LocalDateTime / java.util.Date / Long (时间戳毫秒)
+ * - 填充字段名为 createdAt / updatedAt(与实体属性匹配,不依赖数据库列名)
+ */
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+    private static final String CREATED_FIELD = "createdAt";
+    private static final String UPDATED_FIELD = "updatedAt";
+    private static final String CREATED_BY_FIELD = "createdBy";
+    private static final String UPDATED_BY_FIELD = "updatedBy";
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        // createdAt
+        Object createdVal = getFieldValByName(CREATED_FIELD, metaObject);
+        if (Objects.isNull(createdVal) && hasGetter(metaObject, CREATED_FIELD)) {
+            Class<?> type = getFieldType(metaObject, CREATED_FIELD);
+            if (type != null && LocalDateTime.class.isAssignableFrom(type)) {
+                this.setFieldValByName(CREATED_FIELD, LocalDateTime.now(), metaObject);
+            } else if (type != null && Date.class.isAssignableFrom(type)) {
+                this.setFieldValByName(CREATED_FIELD, new Date(), metaObject);
+            } else if (type != null && (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type))) {
+                this.setFieldValByName(CREATED_FIELD, System.currentTimeMillis(), metaObject);
+            }
+        }
+
+        // updatedAt -> for insert also set updatedAt
+        Object updatedVal = getFieldValByName(UPDATED_FIELD, metaObject);
+        if (Objects.isNull(updatedVal) && hasGetter(metaObject, UPDATED_FIELD)) {
+            Class<?> type = getFieldType(metaObject, UPDATED_FIELD);
+            if (type != null && LocalDateTime.class.isAssignableFrom(type)) {
+                this.setFieldValByName(UPDATED_FIELD, LocalDateTime.now(), metaObject);
+            } else if (type != null && Date.class.isAssignableFrom(type)) {
+                this.setFieldValByName(UPDATED_FIELD, new Date(), metaObject);
+            } else if (type != null && (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type))) {
+                this.setFieldValByName(UPDATED_FIELD, System.currentTimeMillis(), metaObject);
+            }
+        }
+
+        // createdBy / updatedBy (set from current user)
+        String current = CurrentUtils.getCurrentUsername();
+        if (current == null || current.isEmpty()) {
+            current = "system";
+        }
+        if (hasGetter(metaObject, CREATED_BY_FIELD)) {
+            Object createdByVal = getFieldValByName(CREATED_BY_FIELD, metaObject);
+            if (Objects.isNull(createdByVal)) {
+                this.setFieldValByName(CREATED_BY_FIELD, current, metaObject);
+            }
+        }
+        if (hasGetter(metaObject, UPDATED_BY_FIELD)) {
+            Object updatedByVal = getFieldValByName(UPDATED_BY_FIELD, metaObject);
+            if (Objects.isNull(updatedByVal)) {
+                this.setFieldValByName(UPDATED_BY_FIELD, current, metaObject);
+            }
+        }
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        if (hasGetter(metaObject, UPDATED_FIELD)) {
+            Class<?> type = getFieldType(metaObject, UPDATED_FIELD);
+            if (type != null && LocalDateTime.class.isAssignableFrom(type)) {
+                this.setFieldValByName(UPDATED_FIELD, LocalDateTime.now(), metaObject);
+            } else if (type != null && Date.class.isAssignableFrom(type)) {
+                this.setFieldValByName(UPDATED_FIELD, new Date(), metaObject);
+            } else if (type != null && (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type))) {
+                this.setFieldValByName(UPDATED_FIELD, System.currentTimeMillis(), metaObject);
+            }
+        }
+        // updatedBy
+        if (hasGetter(metaObject, UPDATED_BY_FIELD)) {
+            String current = CurrentUtils.getCurrentUsername();
+            if (current == null || current.isEmpty()) {
+                current = "system";
+            }
+            this.setFieldValByName(UPDATED_BY_FIELD, current, metaObject);
+        }
+    }
+
+    private boolean hasGetter(MetaObject metaObject, String name) {
+        try {
+            return metaObject.hasGetter(name);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    private Class<?> getFieldType(MetaObject metaObject, String name) {
+        try {
+            Class<?> t = metaObject.getSetterType(name);
+            if (t == null) {
+                t = metaObject.getGetterType(name);
+            }
+            return t;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
+

+ 17 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/SecurityConfiguration.java

@@ -0,0 +1,17 @@
+package cn.qinys.platform.config;
+
+
+import cn.qinys.platform.base.security.BaseWebSecurityConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+
+/**
+ * Description:
+ *
+ * @author tanlie
+ * Date: 2024/1/9 16:41
+ */
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfiguration extends BaseWebSecurityConfiguration {
+}

+ 0 - 22
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/controller/TestController.java

@@ -1,22 +0,0 @@
-package cn.qinys.platform.mobile.controller;
-
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * @author lie tan
- * @description
- * @date 2026-05-17 22:12
- **/
-
-@RestController
-@RequestMapping("/api/mobile/test")
-public class TestController {
-
-
-    @RequestMapping("/ping")
-    public String pong() {
-        return "pong";
-    }
-
-}

+ 19 - 6
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/controller/WishingTreeController.java

@@ -1,23 +1,36 @@
 package cn.qinys.platform.mobile.controller;
 
 import cn.qinys.platform.base.response.Result;
+import cn.qinys.platform.mobile.req.WishingTreeListReq;
+import cn.qinys.platform.mobile.resp.WishingTreeListResp;
+import cn.qinys.platform.mobile.service.WishTreeService;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 /**
  * @author lie tan
- * @description
+ * @description 许愿树移动端接口
  * @date 2026-05-17 22:25
  **/
-
 @RestController
 @RequestMapping("/api/mobile/wishingtree")
 public class WishingTreeController {
 
-    @RequestMapping("/ping")
-    public Result<String> pong() {
+    @Resource
+    private WishTreeService wishTreeService;
 
-        return new Result<>("wishingtree pong");
+    /**
+     * 查询附近许愿树列表
+     */
+    @PostMapping("/list")
+    public Result<List<WishingTreeListResp>> list(@RequestBody @Valid WishingTreeListReq req) {
+        List<WishingTreeListResp> list = wishTreeService.listNearbyTrees(req);
+        return new Result<>(list);
     }
-
 }

+ 17 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/mapper/WishingTreeMapper.java

@@ -0,0 +1,17 @@
+package cn.qinys.platform.mobile.mapper;
+
+import cn.qinys.platform.entity.wishing.WishingTree;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author lie tan
+ * @description
+ * @date 2026-05-23 12:41
+ **/
+@Mapper
+public interface WishingTreeMapper extends BaseMapper<WishingTree> {
+
+
+
+}

+ 27 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/req/WishingTreeListReq.java

@@ -0,0 +1,27 @@
+package cn.qinys.platform.mobile.req;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * @author lie tan
+ * @description 许愿树列表响应
+ * @date 2026-05-23 13:47
+ **/
+@Data
+public class WishingTreeListReq implements Serializable {
+
+    @NotNull(message = "lng can not be null")
+    private BigDecimal lng;
+
+    @NotNull(message = "lat can not be null")
+    private BigDecimal lat;
+
+    private Integer maxDistance = 10000;
+
+    private Integer limit = 100;
+}

+ 39 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/resp/WishingTreeListResp.java

@@ -0,0 +1,39 @@
+package cn.qinys.platform.mobile.resp;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * @author lie tan
+ * @description 许愿树列表响应
+ * @date 2026-05-23 13:47
+ **/
+@Data
+public class WishingTreeListResp implements Serializable {
+
+    private Long id;
+
+    private String name;
+
+    private String description;
+
+    private BigDecimal longitude;
+
+    private BigDecimal latitude;
+
+    private String address;
+
+    private Integer radius;
+
+    private String coverImage;
+
+    private Integer totalWishes;
+
+    /** 距离(米) */
+    private Integer distance;
+
+    /** 是否在可许愿范围内 */
+    private Boolean isInRange;
+}

+ 25 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/WishTreeService.java

@@ -0,0 +1,25 @@
+package cn.qinys.platform.mobile.service;
+
+import cn.qinys.platform.mobile.req.WishingTreeListReq;
+import cn.qinys.platform.mobile.resp.WishingTreeListResp;
+
+import java.util.List;
+
+/**
+ * @author lie tan
+ * @description 许愿树服务
+ * @date 2026-05-24 21:33
+ **/
+public interface WishTreeService {
+
+    /**
+     * 查询附近许愿树列表
+     *
+     * @param lng         用户经度
+     * @param lat         用户纬度
+     * @param maxDistance 最大距离(米),默认 50000
+     * @param limit       最大返回条数,默认 100
+     * @return 许愿树列表(含距离、是否在范围内)
+     */
+    List<WishingTreeListResp> listNearbyTrees(WishingTreeListReq req);
+}

+ 82 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/WishTreeServiceImpl.java

@@ -0,0 +1,82 @@
+package cn.qinys.platform.mobile.service;
+
+import cn.qinys.platform.entity.wishing.WishingTree;
+import cn.qinys.platform.mobile.mapper.WishingTreeMapper;
+import cn.qinys.platform.mobile.req.WishingTreeListReq;
+import cn.qinys.platform.mobile.resp.WishingTreeListResp;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author lie tan
+ * @description 许愿树服务实现
+ * @date 2026-05-24 21:34
+ **/
+@Slf4j
+@Service
+public class WishTreeServiceImpl implements WishTreeService {
+
+    @Resource
+    private WishingTreeMapper wishingTreeMapper;
+
+    private static final int EARTH_RADIUS = 6371000; // 地球半径(米)
+
+    @Override
+    public List<WishingTreeListResp> listNearbyTrees(WishingTreeListReq req) {
+
+
+        // 查询所有启用的许愿树
+        List<WishingTree> trees = wishingTreeMapper.selectList(
+                new LambdaQueryWrapper<WishingTree>()
+                        .eq(WishingTree::getIsActive, 1)
+        );
+
+        List<WishingTreeListResp> result = new ArrayList<>();
+        for (WishingTree tree : trees) {
+            int dist = haversine(
+                    req.getLng().doubleValue(), req.getLat().doubleValue(),
+                    tree.getLongitude().doubleValue(), tree.getLatitude().doubleValue()
+            );
+
+            WishingTreeListResp resp = new WishingTreeListResp();
+            resp.setId(tree.getId());
+            resp.setName(tree.getName());
+            resp.setDescription(tree.getDescription());
+            resp.setLongitude(tree.getLongitude());
+            resp.setLatitude(tree.getLatitude());
+            resp.setAddress(tree.getAddress());
+            resp.setRadius(tree.getRadius());
+            resp.setCoverImage(tree.getCoverImage());
+            resp.setTotalWishes(tree.getTotalWishes());
+            resp.setDistance(dist);
+            resp.setIsInRange(dist <= (tree.getRadius() != null ? tree.getRadius() : 100));
+            result.add(resp);
+        }
+
+        // 按距离排序,限制返回数量
+        return result.stream()
+                .sorted(Comparator.comparingInt(WishingTreeListResp::getDistance))
+                .limit(req.getLimit())
+                .collect(Collectors.toList());
+    }
+
+    /** Haversine 公式计算两点间距离(米) */
+    private int haversine(double lng1, double lat1, double lng2, double lat2) {
+        double dLat = Math.toRadians(lat2 - lat1);
+        double dLng = Math.toRadians(lng2 - lng1);
+        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
+                * Math.sin(dLng / 2) * Math.sin(dLng / 2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+        return (int) Math.round(EARTH_RADIUS * c);
+    }
+}

+ 37 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-dev.yml

@@ -0,0 +1,37 @@
+logging:
+  level:
+    cn.qinys.platform: debug
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 192.168.1.7:8848
+        namespace: a62810f4-be60-4973-8bd0-8199d19526ef
+        username: nacos
+        password: h8zG&mehJ#.s7V
+        group: wishing-local
+
+  data:
+    redis:
+      host: 192.168.1.7
+      port: 6379
+      password: "XinchanR@2022###"
+      database: 3
+
+  datasource:
+    username: root
+    password: "@@qinys12346.."
+    url: jdbc:mysql://192.168.1.7:3306/wishing_tree?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
+    driver-class-name: com.mysql.cj.jdbc.Driver
+
+mybatis-plus:
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    map-underscore-to-camel-case: true
+  mapper-locations: classpath*:com/ctsi/platform/mobile/*/mapper/xml/*.xml
+  global-config:
+    db-config:
+      logic-delete-field: deleted
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+

+ 2 - 5
wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-local.yml

@@ -10,6 +10,7 @@ spring:
         username: nacos
         password: h8zG&mehJ#.s7V
         group: wishing-local
+        ip: 192.168.1.8
 
   data:
     redis:
@@ -21,7 +22,7 @@ spring:
   datasource:
     username: root
     password: "@@qinys12346.."
-    url: jdbc:mysql://192.168.1.7:3306/xxglgj_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
+    url: jdbc:mysql://192.168.1.7:3306/wishing_tree?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
     driver-class-name: com.mysql.cj.jdbc.Driver
 
 mybatis-plus:
@@ -35,7 +36,3 @@ mybatis-plus:
       logic-delete-value: 1
       logic-not-delete-value: 0
 
-system:
-  base-url: https://wish.qinys.cn/dgapi/mobile/ext/file/
-  file-directory: /server/file/
-

+ 1 - 2
wishing-platform/platform-service/platform-service-mobile/src/main/resources/application.yml

@@ -15,6 +15,5 @@ spring:
 
 jwt:
   ignore-urls:
-    - /api/mobile/test/**
-    - /ext/**
+    - /**
 

+ 1 - 1
wishing-platform/platform-service/platform-service-upms/src/main/java/cn/qinys/platform/upms/config/UpmsMvcConfig.java

@@ -31,7 +31,7 @@ public class UpmsMvcConfig implements WebMvcConfigurer {
      */
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
-        registry.addResourceHandler("/ext/file/**")
+        registry.addResourceHandler("/upms/ext/file/**")
                 .addResourceLocations(
                         "file:" + systemProperties.getFileDirectory(),
                         "file:" + systemProperties.getTempDirectory(),

+ 2 - 2
wishing-platform/platform-service/platform-service-upms/src/main/resources/application-local.yml

@@ -36,8 +36,8 @@ mybatis-plus:
       logic-not-delete-value: 0
 
 system:
-  base-url: https://ai.qinys.cn/dgapi/mobile/ext/file/
-  file-directory: /xinchan/server/file/
+  base-url: https://wish.qinys.cn/dgapi/upms/ext/file/
+  file-directory: /file/
 
 
 sms:

+ 6 - 5
wishing-tree-h5/src/api/request.ts

@@ -2,12 +2,12 @@ import { showToast } from 'vant'
 import { getStorage } from '@/utils/storage'
 
 // 当前使用 mock 模式
-const USE_MOCK = true
+const USE_MOCK = false
 
 export interface ApiResponse<T = any> {
   code: number
+  msg: string
   data: T
-  message?: string
 }
 
 export async function request<T = any>(url: string, options?: RequestInit): Promise<ApiResponse<T>> {
@@ -22,13 +22,14 @@ export async function request<T = any>(url: string, options?: RequestInit): Prom
   }
 
   try {
-    const res = await fetch(`/api${url}`, {
+    const baseUrl = import.meta.env.VITE_API_BASE || '/api'
+    const res = await fetch(`${baseUrl}${url}`, {
       ...options,
       headers: { ...headers, ...(options?.headers as Record<string, string> || {}) },
     })
     const json = await res.json()
-    if (json.code !== 0) {
-      showToast(json.message || '请求失败')
+    if (json.code !== 200) {
+      showToast(json.msg || '请求失败')
     }
     return json
   } catch (err: any) {

+ 19 - 4
wishing-tree-h5/src/api/tree.ts

@@ -1,9 +1,24 @@
 import { getNearbyTrees, getTreeById } from '@/mock/tree'
+import { request } from './request'
 
-export function fetchNearbyTrees(lng: number, lat: number, maxDistance?: number, limit?: number) {
-  return getNearbyTrees(lng, lat, maxDistance, limit)
+const USE_MOCK = false
+
+export async function fetchNearbyTrees(lng: number, lat: number, maxDistance = 10000, limit = 100) {
+  if (USE_MOCK) return getNearbyTrees(lng, lat, maxDistance, limit)
+
+  const res = await request<any[]>('/mobile/wishingtree/list', {
+    method: 'POST',
+    body: JSON.stringify({ lng, lat, maxDistance, limit }),
+  })
+  return res.data.map((item: any) => ({
+    ...item,
+    isInRange: item.isInRange ?? false,
+  }))
 }
 
-export function fetchTreeDetail(id: number) {
-  return getTreeById(id)
+export async function fetchTreeDetail(id: number) {
+  if (USE_MOCK) return getTreeById(id)
+
+  const res = await request<any>(`/mobile/wishingtree/${id}`)
+  return res.data
 }

+ 15 - 3
wishing-tree-h5/src/components/TreeCard.vue

@@ -1,8 +1,11 @@
 <template>
   <div class="tree-card" @click="$emit('click')">
-    <div class="tree-cover" :style="{ background: gradient }">
-      <span class="tree-emoji">{{ emoji }}</span>
-      <span class="tree-label">{{ tree.name }}</span>
+    <div class="tree-cover" :style="tree.coverImage ? {} : { background: gradient }">
+      <img v-if="tree.coverImage" :src="tree.coverImage" class="tree-img" />
+      <template v-else>
+        <span class="tree-emoji">{{ emoji }}</span>
+        <span class="tree-label">{{ tree.name }}</span>
+      </template>
     </div>
     <div class="tree-card-info">
       <h4 class="tree-name">{{ tree.name }}</h4>
@@ -69,6 +72,15 @@ function formatDistance(m: number) {
   gap: 6px;
   color: #fff;
   overflow: hidden;
+  position: relative;
+}
+.tree-img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  position: absolute;
+  top: 0;
+  left: 0;
 }
 .tree-emoji {
   font-size: 40px;

+ 6 - 0
wishing-tree-h5/vite.config.ts

@@ -19,5 +19,11 @@ export default defineConfig({
   server: {
     host: '0.0.0.0',
     port: 3000,
+    proxy: {
+      '/api': {
+        target: 'http://localhost:8003',
+        changeOrigin: true,
+      },
+    },
   },
 })

+ 5 - 0
working-history.md

@@ -9,3 +9,8 @@ Resume this session with:
 claude --resume 0ef000c9-0a77-4f89-a939-4a6ccbe466a2
 ~~~
 
+~~~
+vue3 claude --resume d2dac950-8361-4629-885e-c782d3760ce5
+
+~~~
+