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