diff --git a/CLAUDE.md b/CLAUDE.md index ae60216..12f7e8e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -163,3 +163,91 @@ Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. - 一次修复引入新 bug,调试时间翻倍 - 用户对代码质量失去信任 - 提交历史变成"反复横跳"的打补丁记录 + +--- + +## 接口开发规范 + +### 核心规则:添加/修改接口必须使用工程化方式完成 + +**每次新增或修改 API 接口时,必须以工程化、标准化方式完成**,禁止"能跑就行"的临时拼凑。完成后必须能直接通过代码审查,不需要大改。 + +### 工程化要求清单 + +1. **分层架构**(强制): + - `handler`(控制器):接收请求、参数绑定与校验、调用 service、组装响应 + - `service`(业务层):业务逻辑编排、事务控制、调用 repository + - `repository` / `dao`(数据层):纯数据库操作,不含业务逻辑 + - handler 中**禁止**直接调用 repository / 直接写 SQL + - service 中**禁止**直接操作 HTTP 请求/响应对象 + +2. **请求/响应 DTO**: + - 入参和出参使用独立的结构体(`XxxRequest` / `XxxResponse`) + - **禁止**直接用 DB model 当作入参或返回值 + - 字段命名遵循项目既有规范(snake_case / camelCase) + - 敏感字段(密码、手机号、token)在响应中**必须脱敏或排除** + +3. **参数校验**: + - 使用 `binding` / `validate` tag 在 handler 层做必填、长度、格式、枚举校验 + - 业务规则校验放在 service 层 + - 校验失败的错误信息要明确指出哪个字段、什么问题 + +4. **错误处理**: + - 使用项目统一的错误码/错误类型(如 `ErrCodeXxx`) + - **禁止**把原始 error 直接返回给前端 + - **禁止**用 `_` 吞掉错误 + - 关键业务错误必须打 ERROR 级别日志(含 trace_id / request_id) + +5. **日志规范**: + - 接口入口:记录 method、path、request_id、用户身份(脱敏) + - 业务关键节点:状态流转、跨服务调用、缓存命中/未命中 + - 异常退出:必须记录 stack trace 或 error cause + +6. **API 文档**: + - 同步更新 Swagger / OpenAPI 注释 + - 包含:接口描述、请求参数、响应示例、错误码列表、权限要求 + - 字段类型、是否必填、示例值都要写清楚 + +7. **数据库变更**: + - 表结构变更必须写 migration + - 索引、外键、唯一约束、默认值要显式声明 + - 影响现有数据的变更要考虑兼容方案(默认值、backfill) + +8. **缓存策略**: + - 是否需要缓存、用什么 key、过期时间、缓存更新/失效策略要明确 + - **禁止**缓存与 DB 数据不一致的方案(如只 set 不 delete) + +9. **测试**: + - service 层核心业务逻辑必须覆盖单元测试 + - handler 层至少一个 happy path + 一个 error case + - 数据库相关测试考虑使用事务回滚或测试容器 + +10. **遵循项目既有约定**: + - 命名风格、目录结构、文件命名、错误码定义与项目保持一致 + - 复用项目已有的工具函数、中间件、错误处理逻辑 + - **禁止**引入与项目风格冲突的新写法(例如项目用 snake_case 却写 camelCase) + +### 禁止的反模式 + +- ❌ 在 handler 里直接写 SQL / ORM 调用 +- ❌ 把 DB model 直接作为 API 入参或返回值 +- ❌ 复制粘贴老接口代码不做适配(路径、参数、错误处理不一致) +- ❌ 用 `if err != nil { return err }` 一把梭,没有业务错误码 +- ❌ 缺少或忘记更新 API 文档 +- ❌ 改了表结构但没写 migration +- ❌ 没有写测试或测试只覆盖了 happy path +- ❌ 临时引入新的库/框架(未和项目既有技术栈对齐) + +### 完成自检 + +接口写完后,逐项确认: + +- [ ] 分层结构正确(handler / service / repository 各司其职) +- [ ] 入参/出参是独立 DTO +- [ ] 参数校验完整(必填、长度、格式、边界) +- [ ] 错误处理统一(错误码 + 友好提示 + 日志) +- [ ] 关键路径有日志 +- [ ] Swagger 文档已更新 +- [ ] DB 变更已写 migration +- [ ] 相关测试已编写并通过 +- [ ] 命名、风格与项目既有代码一致 diff --git a/frontend/pages/square/components/HotCategoryBlock.vue b/frontend/pages/square/components/HotCategoryBlock.vue index bb11778..2278bba 100644 --- a/frontend/pages/square/components/HotCategoryBlock.vue +++ b/frontend/pages/square/components/HotCategoryBlock.vue @@ -111,7 +111,9 @@ " mode="aspectFit" /> - {{ formatCount(item.like_count) }} + {{ + formatCount(item.like_count) + }} {{ item.owner_nickname || item.creator_name || item.name || "" @@ -132,22 +134,13 @@ - + 加载中... - + — 没有更多了 — - + 暂无数据 @@ -203,7 +196,7 @@ const PAGE_SIZE = 10; const tabs = [ { key: "hot", - label: "热度榜", + label: "点赞榜", icon: "/static/square/galaxy/dianzanbang.png", iconWidth: 64, iconHeight: 72, @@ -380,7 +373,11 @@ const loadData = async ({ append = false } = {}) => { if (!raw) return; // 同步路径下已经有精确 URL(命中缓存 / 完整未过期)则无需再请求 const instant = it.cover_url; - if (instant && instant !== PLACEHOLDER_IMAGE && instant === getInstantAssetCoverUrl(raw)) { + if ( + instant && + instant !== PLACEHOLDER_IMAGE && + instant === getInstantAssetCoverUrl(raw) + ) { // 已是精确 URL;仍然异步校准一次以处理过期(getAssetCoverRealUrl 内部命中缓存就是同步快速返回) } getAssetCoverRealUrl(raw) @@ -447,16 +444,16 @@ onUnmounted(() => { display: flex; flex-direction: column; overflow: hidden; -// padding: 0 9.5rpx; + // padding: 0 9.5rpx; border-radius: 24rpx; position: relative; background: url("/static/square/galaxy/xbbj.png") center no-repeat; - background-size: 100% 100%; + background-size: 100% 100%; } .content-scroll { flex: 1; - min-height: 0; + min-height: 0; border-radius: 12px; overflow: hidden; } @@ -465,7 +462,7 @@ onUnmounted(() => { display: flex; justify-content: space-between; align-items: center; - padding: 0 80rpx; + padding: 0 64rpx; position: relative; top: 16rpx; margin: 0 24rpx; @@ -476,12 +473,16 @@ onUnmounted(() => { content: ""; position: absolute; inset: 0; - background: linear-gradient(184deg, rgba(255, 90, 93, 0.47) -36.55%, rgba(194, 235, 255, 0.47) 121.2%); -filter: blur(5.849999904632568px); + background: linear-gradient( + 184deg, + rgba(255, 90, 93, 0.47) -36.55%, + rgba(194, 235, 255, 0.47) 121.2% + ); + filter: blur(5.9px); // opacity: 0.8; // Figma 用的就是 filter: blur(图形模糊),不是 backdrop-filter(背景模糊) // filter: blur(3.7px); - -webkit-filter: blur(3.7px); + -webkit-filter: blur(5.9px); border-top-left-radius: 14px; border-top-right-radius: 13px; border-bottom-right-radius: 8px; @@ -491,7 +492,7 @@ filter: blur(5.849999904632568px); .ranking-tab-item { height: 80rpx; - width: 88rpx; + width: 99.2rpx; display: flex; align-items: center; flex-direction: column; @@ -500,7 +501,7 @@ filter: blur(5.849999904632568px); } .ranking-tab-item.active { - width: 96rpx; + width: 99.2rpx; height: 160rpx; top: 40rpx; z-index: 1; @@ -536,7 +537,6 @@ filter: blur(5.849999904632568px); .ranking-tab-icon { display: block; position: absolute; - } .ranking-tab-item.active .ranking-tab-label { @@ -606,23 +606,23 @@ filter: blur(5.849999904632568px); /* 内容网格 */ .items-grid { display: flex; - flex-direction: column; + flex-direction: column; position: relative; - z-index: 2; + z-index: 2; // background: linear-gradient( // 145.83deg, // rgba(255, 90, 93, 0.2) 16.63%, // rgba(76, 237, 255, 0.2) 48.19%, // rgba(255, 122, 124, 0.2) 83.71% // ); - - // pointer-events: none; - // backdrop-filter: blur(4.65px); - border-top-left-radius: 13px; + + // pointer-events: none; + // backdrop-filter: blur(4.65px); + border-top-left-radius: 13px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; - // opacity: 0.8; + // opacity: 0.8; padding: 40rpx 20rpx 32rpx; overflow: hidden; } @@ -713,7 +713,7 @@ filter: blur(5.849999904632568px); inset: 0; background: url("/static/square/galaxy/TOP4.png") center no-repeat; background-size: 100% 100%; - opacity: 0.8; + opacity: 0.8; pointer-events: none; z-index: 0; } @@ -796,7 +796,7 @@ filter: blur(5.849999904632568px); /* Top 排名标签(顶到右边) */ .top-badge { margin-left: auto; /* 推到右侧 */ - margin-right:8rpx; + margin-right: 16rpx; min-width: 100rpx; height: 36rpx; border-radius: 18rpx; diff --git a/frontend/static/square/galaxy/baoguangbang.png b/frontend/static/square/galaxy/baoguangbang.png index c122774..75e1174 100644 Binary files a/frontend/static/square/galaxy/baoguangbang.png and b/frontend/static/square/galaxy/baoguangbang.png differ diff --git a/frontend/static/square/galaxy/dianzanbang.png b/frontend/static/square/galaxy/dianzanbang.png index ffcffa1..f5ea286 100644 Binary files a/frontend/static/square/galaxy/dianzanbang.png and b/frontend/static/square/galaxy/dianzanbang.png differ diff --git a/frontend/static/square/galaxy/huoyuebang.png b/frontend/static/square/galaxy/huoyuebang.png index 9ed5781..6005d5b 100644 Binary files a/frontend/static/square/galaxy/huoyuebang.png and b/frontend/static/square/galaxy/huoyuebang.png differ diff --git a/frontend/static/square/galaxy/tongchengbang.png b/frontend/static/square/galaxy/tongchengbang.png index bc02f8f..8e04c7e 100644 Binary files a/frontend/static/square/galaxy/tongchengbang.png and b/frontend/static/square/galaxy/tongchengbang.png differ