From 60c63850eee487790b5b4974f74b0b217bd9f90e Mon Sep 17 00:00:00 2001 From: Peanut Date: Sun, 10 May 2026 11:38:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=20Redis=20=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E9=97=AE=E9=A2=98=E3=80=81=E5=9B=BA=E5=AE=9A=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=AB=AF=E5=8F=A3=E3=80=81=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BA=BA=E7=94=9F=E4=BA=8B=E4=BB=B6=E6=A8=A1=E5=9D=97=E5=8F=8A?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=9A=E4=B8=AA=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 Redis 超时:添加 commons-pool2 依赖,启用 Lettuce 连接池,超时提升至 15s - 固定 mini-program H5 端口为 5175,避免与 web 项目端口冲突 - 新增人生事件(life-event)模块:表单和详情页面 - 新增 EpicScript 灵感接口(Controller/Service/DTO) - 优化登录、引导、主页、记录、剧本详情等多个页面 - 优化服务管理脚本和 Nginx 配置 Co-Authored-By: Claude Opus 4.7 --- .gitignore | 1 + .../brainstorm/2026-05-09-redesign/.events | 3 + .../2026-05-09-redesign/.server-stopped | 1 + backend-single/pom.xml | 6 + .../controller/EpicScriptController.java | 29 + .../request/EpicScriptInspirationRequest.java | 28 + .../EpicScriptInspirationResponse.java | 17 + .../InspirationSuggestionResponse.java | 17 + .../emotion/service/EpicScriptService.java | 9 + .../service/impl/EpicScriptServiceImpl.java | 89 ++ .../src/main/resources/application-local.yml | 15 +- .../src/main/resources/application-prod.yml | 7 +- conf/emotion-museum.conf | 11 +- dev-services.py | 168 ++- mini-program/.env.development | 8 +- mini-program/package-lock.json | 498 ++++++- mini-program/package.json | 2 + mini-program/src/App.vue | 426 +----- mini-program/src/components/MusicPlayer.vue | 31 +- mini-program/src/pages.json | 22 + mini-program/src/pages/life-event/detail.vue | 310 ++++ mini-program/src/pages/life-event/form.vue | 404 +++++ mini-program/src/pages/login/index.vue | 2 +- mini-program/src/pages/main/MineView.vue | 248 ++++ mini-program/src/pages/main/RecordView.vue | 952 ++++++++---- .../src/pages/main/ScriptDetailView.vue | 498 ++++--- mini-program/src/pages/main/ScriptView.vue | 1306 +++++++---------- mini-program/src/pages/main/index.vue | 487 +++--- mini-program/src/pages/onboarding/index.vue | 1094 ++++++-------- mini-program/src/pages/profile/index.vue | 317 +--- mini-program/src/pages/splash/index.vue | 34 +- mini-program/src/services/auth.js | 6 + mini-program/src/services/epicScript.js | 192 ++- mini-program/src/services/request.js | 148 +- mini-program/src/stores/app.js | 195 ++- mini-program/vite.config.js | 7 +- 36 files changed, 4545 insertions(+), 3043 deletions(-) create mode 100644 .superpowers/brainstorm/2026-05-09-redesign/.events create mode 100644 .superpowers/brainstorm/2026-05-09-redesign/.server-stopped create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EpicScriptInspirationRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/EpicScriptInspirationResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/InspirationSuggestionResponse.java create mode 100644 mini-program/src/pages/life-event/detail.vue create mode 100644 mini-program/src/pages/life-event/form.vue create mode 100644 mini-program/src/pages/main/MineView.vue diff --git a/.gitignore b/.gitignore index da8de89..cbd3f95 100644 --- a/.gitignore +++ b/.gitignore @@ -374,3 +374,4 @@ check-https.sh comparison-report.png prototype-*.png OpenClaw*.md +.gstack/ diff --git a/.superpowers/brainstorm/2026-05-09-redesign/.events b/.superpowers/brainstorm/2026-05-09-redesign/.events new file mode 100644 index 0000000..4fabca4 --- /dev/null +++ b/.superpowers/brainstorm/2026-05-09-redesign/.events @@ -0,0 +1,3 @@ +{"type":"click","text":"一个\n \n 完整原型通过\n 重建所有八个原型屏幕,并在同一工作流中添加后端灵感端点/额外的 DTO。","choice":"a","id":null,"timestamp":1778377847647} +{"type":"click","text":"一个\n \n 完整原型通过\n 重建所有八个原型屏幕,并在同一工作流中添加后端灵感端点/额外的 DTO。","choice":"a","id":null,"timestamp":1778377848524} +{"type":"click","text":"B\n \n 前端优先原型匹配\n 重建小程序 UI 以匹配原型,同时保留现有 API;缺失的灵感功能使用本地建议和现有的 createScript。","choice":"b","id":null,"timestamp":1778377848920} diff --git a/.superpowers/brainstorm/2026-05-09-redesign/.server-stopped b/.superpowers/brainstorm/2026-05-09-redesign/.server-stopped new file mode 100644 index 0000000..5fdd4dd --- /dev/null +++ b/.superpowers/brainstorm/2026-05-09-redesign/.server-stopped @@ -0,0 +1 @@ +{"reason":"idle timeout","timestamp":1778258237598} diff --git a/backend-single/pom.xml b/backend-single/pom.xml index b5421c6..9b35a94 100644 --- a/backend-single/pom.xml +++ b/backend-single/pom.xml @@ -111,6 +111,12 @@ 2.0.40 + + + org.apache.commons + commons-pool2 + + org.apache.httpcomponents diff --git a/backend-single/src/main/java/com/emotion/controller/EpicScriptController.java b/backend-single/src/main/java/com/emotion/controller/EpicScriptController.java index 99a684e..bf5566e 100644 --- a/backend-single/src/main/java/com/emotion/controller/EpicScriptController.java +++ b/backend-single/src/main/java/com/emotion/controller/EpicScriptController.java @@ -3,9 +3,12 @@ package com.emotion.controller; import com.emotion.common.PageResult; import com.emotion.common.Result; import com.emotion.dto.request.EpicScriptCreateRequest; +import com.emotion.dto.request.EpicScriptInspirationRequest; import com.emotion.dto.request.EpicScriptPageRequest; import com.emotion.dto.request.EpicScriptUpdateRequest; +import com.emotion.dto.response.EpicScriptInspirationResponse; import com.emotion.dto.response.EpicScriptResponse; +import com.emotion.dto.response.InspirationSuggestionResponse; import com.emotion.service.EpicScriptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; @@ -45,6 +48,32 @@ public class EpicScriptController { return Result.success(scripts); } + @GetMapping(value = "/inspiration/recommendations") + public Result> getInspirationRecommendations() { + return Result.success(epicScriptService.getInspirationRecommendations()); + } + + @GetMapping(value = "/inspiration/random") + public Result> getRandomInspirations( + @RequestParam(required = false, defaultValue = "3") Integer size) { + return Result.success(epicScriptService.getRandomInspirations(size)); + } + + @PostMapping(value = "/inspiration/generate") + public Result generateFromInspiration( + @Valid @RequestBody EpicScriptInspirationRequest request) { + EpicScriptInspirationResponse response; + try { + response = epicScriptService.generateFromInspiration(request); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + if (response == null) { + return Result.error("灵感剧本生成失败"); + } + return Result.success(response); + } + /** * 根据ID获取爽文剧本详情 */ diff --git a/backend-single/src/main/java/com/emotion/dto/request/EpicScriptInspirationRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EpicScriptInspirationRequest.java new file mode 100644 index 0000000..03d6f40 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EpicScriptInspirationRequest.java @@ -0,0 +1,28 @@ +package com.emotion.dto.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EpicScriptInspirationRequest extends BaseRequest { + + @NotBlank(message = "灵感内容不能为空") + @Size(max = 500, message = "灵感内容不能超过500个字符") + private String prompt; + + private String mode = "inspiration"; + + private String style; + + private String length; + + private String characterInfo; + + private String lifeEventsSummary; + + private String source; +} diff --git a/backend-single/src/main/java/com/emotion/dto/response/EpicScriptInspirationResponse.java b/backend-single/src/main/java/com/emotion/dto/response/EpicScriptInspirationResponse.java new file mode 100644 index 0000000..14e4ee0 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/EpicScriptInspirationResponse.java @@ -0,0 +1,17 @@ +package com.emotion.dto.response; + +import lombok.Data; + +import java.util.List; + +@Data +public class EpicScriptInspirationResponse { + + private EpicScriptResponse script; + + private String prompt; + + private Integer remainingCount; + + private List suggestions; +} diff --git a/backend-single/src/main/java/com/emotion/dto/response/InspirationSuggestionResponse.java b/backend-single/src/main/java/com/emotion/dto/response/InspirationSuggestionResponse.java new file mode 100644 index 0000000..8f5c7a2 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/InspirationSuggestionResponse.java @@ -0,0 +1,17 @@ +package com.emotion.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class InspirationSuggestionResponse { + + private String text; + + private String tag; + + private String category; +} diff --git a/backend-single/src/main/java/com/emotion/service/EpicScriptService.java b/backend-single/src/main/java/com/emotion/service/EpicScriptService.java index c808317..b89a94e 100644 --- a/backend-single/src/main/java/com/emotion/service/EpicScriptService.java +++ b/backend-single/src/main/java/com/emotion/service/EpicScriptService.java @@ -3,9 +3,12 @@ package com.emotion.service; import com.baomidou.mybatisplus.extension.service.IService; import com.emotion.common.PageResult; import com.emotion.dto.request.EpicScriptCreateRequest; +import com.emotion.dto.request.EpicScriptInspirationRequest; import com.emotion.dto.request.EpicScriptPageRequest; import com.emotion.dto.request.EpicScriptUpdateRequest; +import com.emotion.dto.response.EpicScriptInspirationResponse; import com.emotion.dto.response.EpicScriptResponse; +import com.emotion.dto.response.InspirationSuggestionResponse; import com.emotion.entity.EpicScript; import java.util.List; @@ -49,6 +52,12 @@ public interface EpicScriptService extends IService { */ EpicScriptResponse createScript(EpicScriptCreateRequest request); + List getInspirationRecommendations(); + + List getRandomInspirations(Integer size); + + EpicScriptInspirationResponse generateFromInspiration(EpicScriptInspirationRequest request); + /** * 更新剧本 * diff --git a/backend-single/src/main/java/com/emotion/service/impl/EpicScriptServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/EpicScriptServiceImpl.java index 291c145..b07ed6e 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/EpicScriptServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/EpicScriptServiceImpl.java @@ -5,9 +5,12 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.emotion.common.PageResult; import com.emotion.dto.request.EpicScriptCreateRequest; +import com.emotion.dto.request.EpicScriptInspirationRequest; import com.emotion.dto.request.EpicScriptPageRequest; import com.emotion.dto.request.EpicScriptUpdateRequest; +import com.emotion.dto.response.EpicScriptInspirationResponse; import com.emotion.dto.response.EpicScriptResponse; +import com.emotion.dto.response.InspirationSuggestionResponse; import com.emotion.entity.EpicScript; import com.emotion.mapper.EpicScriptMapper; import com.emotion.service.AiChatService; @@ -21,7 +24,12 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,6 +46,17 @@ public class EpicScriptServiceImpl extends ServiceImpl INSPIRATION_SUGGESTIONS = List.of( + new InspirationSuggestionResponse("我想把最近一次低谷,改写成主角觉醒的开端。", "觉醒", "转折"), + new InspirationSuggestionResponse("如果我在最遗憾的选择里勇敢了一次,人生会怎样展开?", "遗憾", "重启"), + new InspirationSuggestionResponse("把一次普通的职场挑战,写成逆风翻盘的高光篇章。", "职场", "成长"), + new InspirationSuggestionResponse("我想见到十年后的自己,让 TA 给现在的我一封信。", "未来", "对话"), + new InspirationSuggestionResponse("把一段关系里的告别,写成重新认识自己的旅程。", "关系", "治愈"), + new InspirationSuggestionResponse("让我的童年记忆成为故事里的隐藏力量。", "童年", "力量"), + new InspirationSuggestionResponse("把一次失败的面试、考试或竞赛,改写成命运伏笔。", "挑战", "伏笔"), + new InspirationSuggestionResponse("写一个我终于不再讨好别人,开始选择自己的平行人生。", "自我", "选择") + ); /** * Coze工作流配置键 - 爽文剧本生成 @@ -167,6 +186,76 @@ public class EpicScriptServiceImpl extends ServiceImpl getInspirationRecommendations() { + return INSPIRATION_SUGGESTIONS; + } + + @Override + public List getRandomInspirations(Integer size) { + int limit = size == null ? 3 : Math.max(1, Math.min(size, INSPIRATION_SUGGESTIONS.size())); + List suggestions = new ArrayList<>(INSPIRATION_SUGGESTIONS); + Collections.shuffle(suggestions); + return suggestions.subList(0, limit); + } + + @Override + public EpicScriptInspirationResponse generateFromInspiration(EpicScriptInspirationRequest request) { + String currentUserId = UserContextHolder.getCurrentUserId(); + if (currentUserId == null) { + return null; + } + + int usedToday = countTodayScripts(currentUserId); + if (usedToday >= DAILY_INSPIRATION_LIMIT) { + throw new IllegalStateException("今日灵感生成次数已用完"); + } + + String prompt = request.getPrompt().trim(); + EpicScriptCreateRequest createRequest = new EpicScriptCreateRequest(); + createRequest.setTitle(buildInspirationTitle(prompt)); + createRequest.setTheme(prompt); + createRequest.setStyle(StringUtils.hasText(request.getStyle()) ? request.getStyle() : "career"); + createRequest.setLength(StringUtils.hasText(request.getLength()) ? request.getLength() : "medium"); + createRequest.setCharacterInfo(request.getCharacterInfo()); + createRequest.setLifeEventsSummary(request.getLifeEventsSummary()); + + Map plotJson = new HashMap<>(); + plotJson.put("mode", "inspiration"); + plotJson.put("prompt", prompt); + plotJson.put("source", StringUtils.hasText(request.getSource()) ? request.getSource() : "mini-program"); + createRequest.setPlotJson(plotJson); + + EpicScriptResponse script = createScript(createRequest); + EpicScriptInspirationResponse response = new EpicScriptInspirationResponse(); + response.setScript(script); + response.setPrompt(prompt); + response.setRemainingCount(Math.max(0, DAILY_INSPIRATION_LIMIT - usedToday - 1)); + response.setSuggestions(getRandomInspirations(3)); + return response; + } + + private int countTodayScripts(String userId) { + LocalDate today = LocalDate.now(); + LocalDateTime start = today.atStartOfDay(); + LocalDateTime end = today.plusDays(1).atStartOfDay(); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(EpicScript::getUserId, userId) + .eq(EpicScript::getIsDeleted, 0) + .ge(EpicScript::getCreateTime, start) + .lt(EpicScript::getCreateTime, end); + return Math.toIntExact(this.count(wrapper)); + } + + private String buildInspirationTitle(String prompt) { + String normalized = prompt.replaceAll("\\s+", " ").trim(); + if (normalized.length() <= 22) { + return normalized; + } + return normalized.substring(0, 22) + "..."; + } + /** * 调用Coze AI生成爽文剧本内容 * diff --git a/backend-single/src/main/resources/application-local.yml b/backend-single/src/main/resources/application-local.yml index b43d472..b4116e3 100644 --- a/backend-single/src/main/resources/application-local.yml +++ b/backend-single/src/main/resources/application-local.yml @@ -15,27 +15,28 @@ spring: minimum-idle: 10 maximum-pool-size: 50 auto-commit: true - idle-timeout: 600000 + idle-timeout: 120000 pool-name: EmotionHikariCP-Local - max-lifetime: 1800000 - connection-timeout: 30000 + max-lifetime: 300000 + keepalive-time: 60000 + connection-timeout: 10000 validation-timeout: 5000 leak-detection-threshold: 60000 - # Redis配置 - 与prod一致 + # Redis配置 - 远程Redis,增加超时和连接池优化 redis: host: 101.200.208.45 port: 6379 - timeout: 5000ms + timeout: 15000ms database: 0 password: EmotionMuseum2025*# lettuce: pool: max-active: 20 - max-wait: -1ms + max-wait: 10000ms max-idle: 10 min-idle: 5 - time-between-eviction-runs: 30s + shutdown-timeout: 100ms client-name: emotion-museum-client # 日志配置 - 本地开发详细日志 diff --git a/backend-single/src/main/resources/application-prod.yml b/backend-single/src/main/resources/application-prod.yml index 0064d83..a4f5899 100644 --- a/backend-single/src/main/resources/application-prod.yml +++ b/backend-single/src/main/resources/application-prod.yml @@ -15,10 +15,11 @@ spring: minimum-idle: 10 maximum-pool-size: 50 auto-commit: true - idle-timeout: 600000 + idle-timeout: 120000 pool-name: EmotionHikariCP-Prod - max-lifetime: 1800000 - connection-timeout: 30000 + max-lifetime: 300000 + keepalive-time: 60000 + connection-timeout: 10000 validation-timeout: 5000 leak-detection-threshold: 60000 diff --git a/conf/emotion-museum.conf b/conf/emotion-museum.conf index 122c301..094ee51 100644 --- a/conf/emotion-museum.conf +++ b/conf/emotion-museum.conf @@ -13,7 +13,7 @@ server { # HTTPS 服务器 server { - listen 443 ssl http2; + listen 443 ssl; server_name lifescript.happylifeos.com; # SSL 证书配置 @@ -22,10 +22,13 @@ server { # SSL 优化配置 ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + # WeChat mini program/Cronet compatibility: keep TLS 1.2 enabled with broad modern suites. + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers off; + ssl_ecdh_curve prime256v1:secp384r1; ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; + ssl_session_timeout 1d; + ssl_session_tickets off; # HSTS (可选,生产环境建议开启) # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; diff --git a/dev-services.py b/dev-services.py index bcf9584..870b68e 100644 --- a/dev-services.py +++ b/dev-services.py @@ -49,11 +49,17 @@ LOG_DIR.mkdir(exist_ok=True) SCRIPT_LOG = LOG_DIR / "dev-services.log" MAX_SCAN_DEPTH = 3 -SKIP_DIRS = {"node_modules", "__pycache__", ".git", "venv", ".venv", "target", "build", "dist", ".omc", ".next", ".nuxt", "coverage", "e2e"} +SKIP_DIRS = { + "node_modules", "__pycache__", ".git", "venv", ".venv", "target", "build", + "dist", ".omc", ".next", ".nuxt", "coverage", "e2e", +} +AUXILIARY_DIRS = {"tools", "scripts", "script", "bin", ".superpowers", ".gstack"} # 框架默认端口 DEFAULT_PORTS = { "vite": 5173, + "uniapp": 5173, + "taro": 5173, "next": 3000, "node-backend": 3000, "python": 8000, @@ -134,6 +140,8 @@ class Service: def type_label(self) -> str: labels = { "vite": "Vite 前端", + "uniapp": "UniApp H5", + "taro": "Taro H5", "next": "Next.js", "node-backend": "Node.js 后端", "python": "Python 后端", @@ -162,7 +170,7 @@ def _scan_directory(current: Path, root: Path, max_depth: int, services: list): return # 跳过黑名单 - if current.name in SKIP_DIRS: + if current != root and (current.name in SKIP_DIRS or current.name in AUXILIARY_DIRS): return # 尝试发现服务 @@ -206,6 +214,10 @@ def _detect_service(directory: Path) -> Optional[Service]: deps = {**pkg_data.get("dependencies", {}), **pkg_data.get("devDependencies", {})} scripts = pkg_data.get("scripts", {}) + if _is_uniapp_project(deps, scripts): + return _build_uniapp_service(directory, pkg_data) + if _is_taro_project(deps, scripts): + return _build_taro_service(directory, pkg_data) # Vite if "vite" in deps: return _build_vite_service(directory, pkg_data) @@ -263,7 +275,7 @@ def _read_env_file(directory: Path) -> dict: def _extract_port_from_env(directory: Path) -> Optional[int]: """从 .env 文件中提取端口号""" env_data = _read_env_file(directory) - for key in ["VITE_PORT", "PORT", "SERVER_PORT", "APP_PORT", "BACKEND_PORT"]: + for key in ["VITE_PORT", "VITE_APP_PORT", "PORT", "SERVER_PORT", "APP_PORT", "BACKEND_PORT"]: if key in env_data: try: return int(env_data[key]) @@ -291,13 +303,61 @@ def _extract_port_from_vite_config(directory: Path) -> Optional[int]: def _extract_port_from_scripts(pkg_data: dict) -> Optional[int]: """从 package.json scripts 中提取 --port 参数""" scripts = pkg_data.get("scripts", {}) - dev_script = scripts.get("dev", "") or scripts.get("start", "") - match = re.search(r'--port\s+(\d+)', dev_script) - if match: - return int(match.group(1)) + for script_name in ["dev:h5", "h5", "dev", "start"]: + script = scripts.get(script_name, "") + match = re.search(r'--port(?:=|\s+)(\d+)', script) + if match: + return int(match.group(1)) return None +def _service_has_explicit_port(svc: Service) -> bool: + if _extract_port_from_env(svc.project_dir) is not None: + return True + if svc.project_type in {"vite", "uniapp", "taro", "next"}: + return _extract_port_from_vite_config(svc.project_dir) is not None + return False + + +def _update_service_port(svc: Service, port: int): + old_port = svc.port + if old_port == port: + return + + svc.port = port + svc.health_url = re.sub(r":\d+", f":{port}", svc.health_url, count=1) + updated = [] + skip_next = False + for index, part in enumerate(svc.start_cmd): + if skip_next: + skip_next = False + continue + if part == "--port" and index + 1 < len(svc.start_cmd): + updated.extend([part, str(port)]) + skip_next = True + elif part.startswith("--port="): + updated.append(f"--port={port}") + elif f"--server.port={old_port}" in part: + updated.append(part.replace(f"--server.port={old_port}", f"--server.port={port}")) + else: + updated.append(part) + svc.start_cmd = updated + + +def assign_unique_ports(services: list[Service]) -> list[Service]: + reserved = {svc.port for svc in services if _service_has_explicit_port(svc)} + used = set() + for svc in services: + port = svc.port + if port in used or (port in reserved and not _service_has_explicit_port(svc)): + while port in used or port in reserved: + port += 1 + log(f"{svc.name} 端口 {svc.port} 与其他服务冲突,自动调整为 {port}", "WARN") + _update_service_port(svc, port) + used.add(svc.port) + return services + + def _extract_port_from_pom(pom_path: Path) -> Optional[int]: """从 pom.xml 提取 server.port""" try: @@ -558,6 +618,38 @@ def _find_python_entry(directory: Path) -> Optional[Path]: # 服务构建 # ============================================================ +def _is_uniapp_project(deps: dict, scripts: dict) -> bool: + dep_names = set(deps.keys()) + return ( + any(name.startswith("@dcloudio/") for name in dep_names) + or "uni-app" in dep_names + or any("uni " in value or value.startswith("uni") for value in scripts.values()) + ) + + +def _is_taro_project(deps: dict, scripts: dict) -> bool: + dep_names = set(deps.keys()) + return ( + any(name.startswith("@tarojs/") for name in dep_names) + or any("taro " in value or value.startswith("taro") for value in scripts.values()) + ) + + +def _get_package_manager(directory: Path) -> str: + if (directory / "pnpm-lock.yaml").exists(): + return shutil.which("pnpm") or "pnpm" + if (directory / "yarn.lock").exists(): + return shutil.which("yarn") or "yarn" + return shutil.which("npm") or "npm" + + +def _choose_h5_script(scripts: dict) -> str: + for name in ["dev:h5", "h5", "dev"]: + if name in scripts: + return name + return "dev" + + def _get_npm_cmd(directory: Path) -> list: """获取可用的包管理器命令,返回 [cmd, args...] 列表""" # 优先通过 node 直接执行 vite.js(避免 .cmd 的 shell 问题) @@ -583,6 +675,56 @@ def _get_npm_cmd(directory: Path) -> list: return ["npx"] +def _build_uniapp_service(directory: Path, pkg_data: dict) -> Service: + name = pkg_data.get("name", "uniapp") + if "/" in name: + name = name.split("/")[-1] + scripts = pkg_data.get("scripts", {}) + port = (_extract_port_from_env(directory) + or _extract_port_from_scripts(pkg_data) + or _extract_port_from_vite_config(directory) + or DEFAULT_PORTS["uniapp"]) + script = _choose_h5_script(scripts) + cmd = [_get_package_manager(directory), "run", script, "--", "--host", "127.0.0.1", "--port", str(port)] + + return Service( + name=f"{name.capitalize()} 前端", + project_type="uniapp", + project_dir=directory, + port=port, + start_cmd=cmd, + health_keyword="", + health_url=f"http://localhost:{port}", + health_timeout=120, + keyword_timeout=0, + ) + + +def _build_taro_service(directory: Path, pkg_data: dict) -> Service: + name = pkg_data.get("name", "taro") + if "/" in name: + name = name.split("/")[-1] + scripts = pkg_data.get("scripts", {}) + port = (_extract_port_from_env(directory) + or _extract_port_from_scripts(pkg_data) + or _extract_port_from_vite_config(directory) + or DEFAULT_PORTS["taro"]) + script = _choose_h5_script(scripts) + cmd = [_get_package_manager(directory), "run", script, "--", "--host", "127.0.0.1", "--port", str(port)] + + return Service( + name=f"{name.capitalize()} 前端", + project_type="taro", + project_dir=directory, + port=port, + start_cmd=cmd, + health_keyword="", + health_url=f"http://localhost:{port}", + health_timeout=120, + keyword_timeout=0, + ) + + def _build_vite_service(directory: Path, pkg_data: dict) -> Service: name = pkg_data.get("name", "前端") if "/" in name: @@ -1044,15 +1186,20 @@ def check_service_health(svc: Service) -> bool: log(f"健康检查开始: {svc.name}", "INFO") log_file = svc.log_path - if wait_for_log_keyword(log_file, svc.health_keyword, svc.keyword_timeout): + if not svc.health_keyword: + log(f"[1/2] Log keyword skipped: {svc.name}", "INFO") + elif wait_for_log_keyword(log_file, svc.health_keyword, svc.keyword_timeout): log(f"[1/2] 日志就绪: {svc.name}", "SUCCESS") else: - log(f"[1/2] 日志关键字超时: {svc.name}", "ERROR") + log(f"[1/2] 日志关键字超时: {svc.name}", "WARN") + if http_health_check(svc.health_url, svc.health_timeout): + log(f"[2/2] HTTP ready: {svc.name} ({svc.health_url})", "SUCCESS") + return True _print_log_tail(svc.name, log_file) return False if http_health_check(svc.health_url, svc.health_timeout): - log(f"[2/2] HTTP 就绪: {svc.name} ({svc.health_url})", "SUCCESS") + log(f"[2/2] HTTP ready: {svc.name} ({svc.health_url})", "SUCCESS") return True else: log(f"[2/2] HTTP 健康检查失败: {svc.name}", "ERROR") @@ -1424,6 +1571,7 @@ def main(): # 发现服务 services = discover_services(root_dir=root_dir) services = filter_services(services, args.svc_type, args.port) + services = assign_unique_ports(services) if args.action == "discover": log_section(f"发现的服务 (目录: {root_dir or CWD})") diff --git a/mini-program/.env.development b/mini-program/.env.development index 4e3a5a0..59f5162 100644 --- a/mini-program/.env.development +++ b/mini-program/.env.development @@ -1,9 +1,9 @@ # 开发环境配置(本地开发调试) VITE_APP_ENV=dev # 本地环境 -# VITE_API_BASE_URL=http://localhost:19089/api -# VITE_WS_URL=ws://localhost:19089/ws +VITE_API_BASE_URL=http://localhost:19089/api +VITE_WS_URL=ws://localhost:19089/ws # 测试环境 -VITE_API_BASE_URL=https://lifescript.happylifeos.com/api -VITE_WS_URL=wss://lifescript.happylifeos.com/ws +# VITE_API_BASE_URL=https://lifescript.happylifeos.com/api +# VITE_WS_URL=wss://lifescript.happylifeos.com/ws VITE_DEBUG=true diff --git a/mini-program/package-lock.json b/mini-program/package-lock.json index c90e316..64871a5 100644 --- a/mini-program/package-lock.json +++ b/mini-program/package-lock.json @@ -7,16 +7,21 @@ "name": "emotion-museum-uniapp", "dependencies": { "@dcloudio/uni-app": "3.0.0-alpha-5000120260211001", + "@dcloudio/uni-h5": "^3.0.0-alpha-5000120260211001", "@dcloudio/uni-mp-weixin": "3.0.0-alpha-5000120260211001", + "@vue/runtime-dom": "^3.4.21", "vue": "3.4.21" }, "devDependencies": { + "@dcloudio/uni-components": "3.0.0-alpha-5000120260211001", + "@dcloudio/uni-uts-v1": "3.0.0-alpha-5000120260211001", "@dcloudio/vite-plugin-uni": "3.0.0-alpha-5000120260211001", "@vitejs/plugin-vue": "^5.2.0", "@vue/compiler-sfc": "3.4.21", "@vue/server-renderer": "3.4.21", "@vue/shared": "3.4.21", "@vue/tsconfig": "^0.8.1", + "cross-env": "^10.1.0", "vite": "5.2.8" } }, @@ -1917,6 +1922,13 @@ "@dcloudio/types": "3.4.28" } }, + "node_modules/@dcloudio/uni-app-x": { + "version": "0.7.88", + "resolved": "https://registry.npmmirror.com/@dcloudio/uni-app-x/-/uni-app-x-0.7.88.tgz", + "integrity": "sha512-O+7puNyN8BpovM0RljyvhqpEjon4E53tC/DEBExmObbRBaAcCPtuMm4mdeBK7f9hTQ9KIzxQXohFuX0urVObvw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@dcloudio/uni-cli-shared": { "version": "3.0.0-alpha-5000120260211001", "resolved": "https://registry.npmmirror.com/@dcloudio/uni-cli-shared/-/uni-cli-shared-3.0.0-alpha-5000120260211001.tgz", @@ -2280,6 +2292,168 @@ "debug": "4.3.7" } }, + "node_modules/@dcloudio/uni-uts-v1": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uni-uts-v1/-/uni-uts-v1-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-MJ6Kb6eWc6fSfMkeLoPDTeqo3lK4WnUvWfSlSDxLX7VEoPWCv51JktxdgQ+8e7PgX8ZD+Fw5ALFZVxQqxi6OSw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/code-frame": "7.24.7", + "@dcloudio/uni-app-x": "0.7.88", + "@dcloudio/uts": "3.0.0-alpha-5000120260211001", + "@rollup/pluginutils": "5.1.0", + "@vue/shared": "3.4.21", + "adm-zip": "0.5.16", + "android-versions": "^1.8.1", + "colors": "^1.4.0", + "debug": "4.3.7", + "fast-glob": "3.3.3", + "find-cache-dir": "^3.3.2", + "fs-extra": "10.1.0", + "graphlib": "^2.1.8", + "jsonc-parser": "3.3.1", + "lodash": "^4.17.21", + "md5-file": "^5.0.0", + "object-hash": "^3.0.0", + "semver": "7.6.3", + "source-map": "^0.7.4", + "source-map-js": "1.2.1" + } + }, + "node_modules/@dcloudio/uni-uts-v1/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@dcloudio/uni-uts-v1/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@dcloudio/uts": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts/-/uts-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-E9NLFFCtagXRHbG8JeDSkwVmGzvo/4sfekc+z0SOXhiDUr95WMwtyNW11FptOh+MzpmXwuPVg2AH+OB1jkB5+Q==", + "dev": true, + "optionalDependencies": { + "@dcloudio/uts-darwin-arm64": "3.0.0-alpha-5000120260211001", + "@dcloudio/uts-darwin-x64": "3.0.0-alpha-5000120260211001", + "@dcloudio/uts-linux-x64-gnu": "3.0.0-alpha-5000120260211001", + "@dcloudio/uts-linux-x64-musl": "3.0.0-alpha-5000120260211001", + "@dcloudio/uts-win32-ia32-msvc": "3.0.0-alpha-5000120260211001", + "@dcloudio/uts-win32-x64-msvc": "3.0.0-alpha-5000120260211001" + } + }, + "node_modules/@dcloudio/uts-darwin-arm64": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts-darwin-arm64/-/uts-darwin-arm64-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-UPtg2Z1OqxQyzB/uAolBoe8jubMxUXURqjfoeFN+ChpRcQDKUnqwp/at2N8peIQL4s2y1GwnCjV3FJ7n76Ca2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@dcloudio/uts-darwin-x64": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts-darwin-x64/-/uts-darwin-x64-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-8URCkpWw4q7x6uebjeRq2FmxwzckDTf2EM/ztV2A+MLwCn46b1yRgIJJMSVQioATvhdOA5v78pZVuEG/Old1zQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@dcloudio/uts-linux-x64-gnu": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts-linux-x64-gnu/-/uts-linux-x64-gnu-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-mlfvpQQcTOBf/amWIRxDGKQh3GItBI7jflY7bsePU5iMJi/gHOnv22wRBvcuNwIdNydbjskwDNgnESLwZAF6pg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@dcloudio/uts-linux-x64-musl": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts-linux-x64-musl/-/uts-linux-x64-musl-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-+RTLm8dJQsqFV47ImsN1+90gNFUgTioOVYp5HA/EWXZzmCNrXadx+JGUurtON4j8Hzm3/TnWp4o2lv+uCCFFnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@dcloudio/uts-win32-ia32-msvc": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts-win32-ia32-msvc/-/uts-win32-ia32-msvc-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-wpNdG9uby8XUd/pkv7U3uanD8e0T/+dqwwGMdY+VGfIajPe4c8Tl9ouACmvwQrCKumrVIgCKIj247MCCytOiTg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@dcloudio/uts-win32-x64-msvc": { + "version": "3.0.0-alpha-5000120260211001", + "resolved": "https://registry.npmmirror.com/@dcloudio/uts-win32-x64-msvc/-/uts-win32-x64-msvc-3.0.0-alpha-5000120260211001.tgz", + "integrity": "sha512-GbwAEwuhJrcWN/8S05/II80cXx/bPH+okU9aaJpN26G1PcDxTWu9mxbYRzsEl89QxhBwQE2e7P4TRripmeEvFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@dcloudio/vite-plugin-uni": { "version": "3.0.0-alpha-5000120260211001", "resolved": "https://registry.npmmirror.com/@dcloudio/vite-plugin-uni/-/vite-plugin-uni-3.0.0-alpha-5000120260211001.tgz", @@ -2380,6 +2554,13 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -3445,7 +3626,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3459,7 +3639,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3473,7 +3652,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3487,7 +3665,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3501,7 +3678,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3515,7 +3691,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3529,7 +3704,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3543,7 +3717,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3557,7 +3730,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3571,7 +3743,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3585,7 +3756,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3599,7 +3769,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3613,7 +3782,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3627,7 +3795,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3641,7 +3808,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3655,7 +3821,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3669,7 +3834,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3683,7 +3847,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3697,7 +3860,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3711,7 +3873,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3725,7 +3886,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3739,7 +3899,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3753,7 +3912,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3767,7 +3925,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3781,7 +3938,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4222,6 +4378,29 @@ "node": ">=12.0" } }, + "node_modules/android-versions": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/android-versions/-/android-versions-1.9.0.tgz", + "integrity": "sha512-13O2B6PQMEM4ej9n13ePRQeckrCoKbZrvuzlLvK+9s2QmncpHDbYzZxhgapN32sJNoifN6VAHexLnd/6CYrs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.2" + } + }, + "node_modules/android-versions/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -4693,6 +4872,16 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", @@ -4700,6 +4889,13 @@ "dev": true, "license": "MIT" }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/compare-versions": { "version": "3.6.0", "resolved": "https://registry.npmmirror.com/compare-versions/-/compare-versions-3.6.0.tgz", @@ -4794,6 +4990,39 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-font-size-keywords": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz", @@ -5232,6 +5461,38 @@ "dev": true, "license": "MIT" }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -5420,6 +5681,16 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", @@ -5632,6 +5903,13 @@ "url": "https://github.com/sponsors/gjtorikian/" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/jimp": { "version": "0.10.3", "resolved": "https://registry.npmmirror.com/jimp/-/jimp-0.10.3.tgz", @@ -5835,6 +6113,26 @@ "node": ">=6" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -5866,6 +6164,22 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5876,6 +6190,19 @@ "node": ">= 0.4" } }, + "node_modules/md5-file": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/md5-file/-/md5-file-5.0.0.tgz", + "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", + "dev": true, + "license": "MIT", + "bin": { + "md5-file": "cli.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", @@ -6088,6 +6415,16 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", @@ -6133,6 +6470,45 @@ "yarn": "^1.22.4" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", @@ -6192,6 +6568,26 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", @@ -6257,6 +6653,19 @@ "pixelmatch": "bin/pixelmatch" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", @@ -6987,6 +7396,29 @@ "dev": true, "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", @@ -7612,6 +8044,22 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", diff --git a/mini-program/package.json b/mini-program/package.json index cb38ceb..217fbd9 100644 --- a/mini-program/package.json +++ b/mini-program/package.json @@ -11,7 +11,9 @@ }, "dependencies": { "@dcloudio/uni-app": "3.0.0-alpha-5000120260211001", + "@dcloudio/uni-h5": "^3.0.0-alpha-5000120260211001", "@dcloudio/uni-mp-weixin": "3.0.0-alpha-5000120260211001", + "@vue/runtime-dom": "^3.4.21", "vue": "3.4.21" }, "devDependencies": { diff --git a/mini-program/src/App.vue b/mini-program/src/App.vue index 8c200d5..4cd0572 100644 --- a/mini-program/src/App.vue +++ b/mini-program/src/App.vue @@ -2,6 +2,7 @@ import { ref } from 'vue' import { onLaunch, onShow, onHide } from '@dcloudio/uni-app' import { useAppStore } from './stores/app.js' +import { logRuntimeEnv } from './services/request.js' const statusBarHeight = ref(0) const safeAreaTop = ref(0) @@ -9,14 +10,11 @@ const safeAreaBottom = ref(0) onLaunch(async () => { console.log('App Launch') + logRuntimeEnv('app:onLaunch') const store = useAppStore() await store.initialize() - // 使用新的推荐 API 替代已弃用的 getSystemInfoSync - const deviceInfo = uni.getDeviceInfo() const windowInfo = uni.getWindowInfo() - const appBaseInfo = uni.getAppBaseInfo() - statusBarHeight.value = windowInfo.statusBarHeight || 20 safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20 safeAreaBottom.value = windowInfo.safeAreaInsets?.bottom || 0 @@ -35,318 +33,36 @@ onHide(() => { diff --git a/mini-program/src/components/MusicPlayer.vue b/mini-program/src/components/MusicPlayer.vue index 91ca666..35c82e9 100644 --- a/mini-program/src/components/MusicPlayer.vue +++ b/mini-program/src/components/MusicPlayer.vue @@ -6,7 +6,7 @@ @click="toggleMusic" > - 🎵 + @@ -127,8 +127,33 @@ defineExpose({ to { transform: rotate(360deg); } } -.music-icon { - font-size: 36rpx; +.music-note { + position: relative; + width: 26rpx; + height: 38rpx; z-index: 1; } + +.music-note::before { + content: ''; + position: absolute; + left: 2rpx; + bottom: 0; + width: 18rpx; + height: 14rpx; + border: 5rpx solid rgba(196, 181, 253, 0.86); + border-radius: 50%; +} + +.music-note::after { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 9rpx; + height: 34rpx; + border-radius: 6rpx; + background: rgba(196, 181, 253, 0.86); + box-shadow: 8rpx 3rpx 0 rgba(196, 181, 253, 0.42); +} diff --git a/mini-program/src/pages.json b/mini-program/src/pages.json index 37c18e3..5ba972f 100644 --- a/mini-program/src/pages.json +++ b/mini-program/src/pages.json @@ -31,9 +31,31 @@ "navigationBarTitleText": "剧本详情" } }, + { + "path": "pages/main/PathView", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "实现路径" + } + }, + { + "path": "pages/life-event/form", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "记录人生经历" + } + }, + { + "path": "pages/life-event/detail", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "人生事件" + } + }, { "path": "pages/profile/index", "style": { + "navigationStyle": "custom", "navigationBarTitleText": "个人中心" } } diff --git a/mini-program/src/pages/life-event/detail.vue b/mini-program/src/pages/life-event/detail.vue new file mode 100644 index 0000000..b9cd9aa --- /dev/null +++ b/mini-program/src/pages/life-event/detail.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/mini-program/src/pages/life-event/form.vue b/mini-program/src/pages/life-event/form.vue new file mode 100644 index 0000000..6ec707f --- /dev/null +++ b/mini-program/src/pages/life-event/form.vue @@ -0,0 +1,404 @@ +