diff --git a/docs/points-mall-api-full.md b/docs/points-mall-api-full.md deleted file mode 100644 index 43bd5dd..0000000 --- a/docs/points-mall-api-full.md +++ /dev/null @@ -1,578 +0,0 @@ -# 积分商城后端 API 设计文档 - -## 一、整体设计概述 - -### 1.1 设计目标 -- 实现 API 调用消费金额与积分的自动累积(1 美元 = 1000 积分) -- 提供完整的积分商城商品管理与兑换流程 -- 支持实物商品的收货地址管理 -- 积分与用户 ID 强绑定,确保数据一致性 - -### 1.2 汇率规则 -- **1 USD = 1000 积分** -- 积分计算以实际消费金额为准,精确到小数点后 2 位 -- 消费金额四舍五入后转换为积分 - ---- - -## 二、数据库表结构设计 - -### 2.1 用户积分表 (user_points) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | BIGINT | 主键 | PRIMARY KEY, AUTO_INCREMENT | -| user_id | VARCHAR(64) | 用户 ID | NOT NULL, INDEX | -| points | BIGINT | 当前积分余额 | NOT NULL, DEFAULT 0 | -| total_earned | BIGINT | 累计获得积分 | NOT NULL, DEFAULT 0 | -| total_spent | BIGINT | 累计消耗积分 | NOT NULL, DEFAULT 0 | -| level | VARCHAR(32) | 用户等级 | DEFAULT 'Lv.1' | -| created_at | DATETIME | 创建时间 | NOT NULL | -| updated_at | DATETIME | 更新时间 | NOT NULL | -| version | INT | 乐观锁版本 | NOT NULL, DEFAULT 0 | - -**索引:** -- `uk_user_id`: UNIQUE INDEX on `user_id` - -### 2.2 积分流水表 (point_transactions) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | BIGINT | 主键 | PRIMARY KEY, AUTO_INCREMENT | -| user_id | VARCHAR(64) | 用户 ID | NOT NULL, INDEX | -| type | VARCHAR(32) | 类型:earn/spend | NOT NULL | -| points | BIGINT | 变动积分(正数获得,负数消耗) | NOT NULL | -| balance | BIGINT | 变动后余额 | NOT NULL | -| source_type | VARCHAR(32) | 来源类型 | NOT NULL | -| source_id | VARCHAR(64) | 来源 ID | NULL | -| description | VARCHAR(255) | 描述 | NULL | -| created_at | DATETIME | 创建时间 | NOT NULL | - -**说明:** -- `source_type` 枚举:`api_call`(API 调用)、`exchange`(兑换商品)、`activity`(活动奖励) -- `source_id`:对应 API 调用记录 ID 或订单 ID 等 - -### 2.3 积分商城商品表 (point_products) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | VARCHAR(64) | 商品 ID | PRIMARY KEY | -| category_id | VARCHAR(64) | 分类 ID | NOT NULL, INDEX | -| name | VARCHAR(128) | 商品名称 | NOT NULL | -| subtitle | VARCHAR(255) | 副标题 | NULL | -| description | TEXT | 详细描述 | NULL | -| cover_url | VARCHAR(512) | 封面图片 | NULL | -| points_price | BIGINT | 所需积分 | NOT NULL | -| original_price | DECIMAL(10,2) | 原价(美元) | NULL | -| stock | INT | 库存 | NOT NULL, DEFAULT 0 | -| sold | INT | 已售数量 | NOT NULL, DEFAULT 0 | -| tags | JSON | 标签数组 | NULL | -| weight | DECIMAL(8,2) | 重量(kg) | NULL | -| is_physical | TINYINT | 是否实物商品 | NOT NULL, DEFAULT 1 | -| is_published | TINYINT | 是否上架 | NOT NULL, DEFAULT 0 | -| sort_order | INT | 排序权重 | NOT NULL, DEFAULT 0 | -| created_at | DATETIME | 创建时间 | NOT NULL | -| updated_at | DATETIME | 更新时间 | NOT NULL | - -### 2.4 商品分类表 (point_categories) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | VARCHAR(64) | 分类 ID | PRIMARY KEY | -| name | VARCHAR(64) | 分类名称 | NOT NULL | -| icon_url | VARCHAR(512) | 图标 | NULL | -| sort_order | INT | 排序权重 | NOT NULL, DEFAULT 0 | -| is_enabled | TINYINT | 是否启用 | NOT NULL, DEFAULT 1 | -| created_at | DATETIME | 创建时间 | NOT NULL | - -### 2.5 兑换订单表 (point_orders) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | VARCHAR(64) | 订单 ID | PRIMARY KEY | -| user_id | VARCHAR(64) | 用户 ID | NOT NULL, INDEX | -| product_id | VARCHAR(64) | 商品 ID | NOT NULL | -| product_name | VARCHAR(128) | 商品名称快照 | NOT NULL | -| points_price | BIGINT | 消耗积分 | NOT NULL | -| status | VARCHAR(32) | 订单状态 | NOT NULL, INDEX | -| recipient_name | VARCHAR(64) | 收货人姓名 | NOT NULL | -| phone | VARCHAR(32) | 联系电话 | NOT NULL | -| province | VARCHAR(64) | 省份 | NOT NULL | -| city | VARCHAR(64) | 城市 | NOT NULL | -| district | VARCHAR(64) | 区/县 | NOT NULL | -| address | VARCHAR(512) | 详细地址 | NOT NULL | -| zip_code | VARCHAR(16) | 邮政编码 | NULL | -| tracking_company | VARCHAR(64) | 物流公司 | NULL | -| tracking_number | VARCHAR(64) | 物流单号 | NULL | -| shipped_at | DATETIME | 发货时间 | NULL | -| delivered_at | DATETIME | 签收时间 | NULL | -| remark | VARCHAR(255) | 备注 | NULL | -| created_at | DATETIME | 创建时间 | NOT NULL | -| updated_at | DATETIME | 更新时间 | NOT NULL | - -**状态枚举 (status):** -- `pending`:待处理 -- `confirmed`:已确认 -- `shipped`:已发货 -- `delivered`:已签收 -- `cancelled`:已取消 -- `refunded`:已退款 - -### 2.6 Banner 表 (point_banners) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | VARCHAR(64) | Banner ID | PRIMARY KEY | -| title | VARCHAR(128) | 标题 | NOT NULL | -| subtitle | VARCHAR(255) | 副标题 | NULL | -| image_url | VARCHAR(512) | 图片 URL | NOT NULL | -| link_url | VARCHAR(512) | 跳转链接 | NULL | -| sort_order | INT | 排序权重 | NOT NULL, DEFAULT 0 | -| is_enabled | TINYINT | 是否启用 | NOT NULL, DEFAULT 1 | -| created_at | DATETIME | 创建时间 | NOT NULL | - -### 2.7 促销入口表 (point_promo_entries) - -| 字段名 | 类型 | 说明 | 约束 | -|--------|------|------|------| -| id | VARCHAR(64) | 入口 ID | PRIMARY KEY | -| title | VARCHAR(64) | 标题 | NOT NULL | -| subtitle | VARCHAR(128) | 副标题 | NULL | -| icon_url | VARCHAR(512) | 图标 URL | NULL | -| link_url | VARCHAR(512) | 跳转链接 | NULL | -| sort_order | INT | 排序权重 | NOT NULL, DEFAULT 0 | -| is_enabled | TINYINT | 是否启用 | NOT NULL, DEFAULT 1 | -| created_at | DATETIME | 创建时间 | NOT NULL | - ---- - -## 三、API 接口设计 - -### 3.1 通用响应格式 - -```json -{ - "code": 0, - "message": "success", - "data": {} -} -``` - ---- - -### 3.2 积分商城概览接口 - -**GET** `/points-mall/overview` - -**描述:** 获取积分商城首页概览数据,包括用户积分、分类、Banner、促销入口等 - -**响应数据:** -```json -{ - "me": { - "points": 1280, - "level": "Lv.2", - "totalEarned": 5680, - "totalSpent": 4400 - }, - "categories": [ - { - "id": "all", - "name": "全部", - "sort": 0 - }, - { - "id": "digital", - "name": "虚拟权益", - "sort": 1 - } - ], - "banners": [ - { - "id": "b1", - "title": "限时活动", - "subtitle": "Up to 25% Off", - "imageUrl": "https://...", - "linkUrl": "https://..." - } - ], - "promoEntries": [ - { - "id": "p1", - "title": "促销活动", - "subtitle": "本周精选", - "iconUrl": "https://...", - "linkUrl": "https://..." - } - ] -} -``` - ---- - -### 3.3 商品列表接口 - -**GET** `/points-mall/products` - -**描述:** 获取商品列表,支持分页、筛选、排序 - -**请求参数:** - -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| categoryId | String | 否 | 分类 ID,不传则查询全部 | -| q | String | 否 | 搜索关键词 | -| sort | String | 否 | 排序方式:`popular`(热度)、`newest`(最新)、`price_asc`(价格升序)、`price_desc`(价格降序),默认 `popular` | -| page | Int | 否 | 页码,默认 1 | -| pageSize | Int | 否 | 每页数量,默认 24 | - -**响应数据:** -```json -{ - "page": 1, - "pageSize": 24, - "total": 120, - "items": [ - { - "id": "prod_001", - "categoryId": "digital", - "name": "商品名称", - "subtitle": "商品简短描述", - "coverUrl": "https://...", - "pointsPrice": 1990, - "stock": 99, - "sold": 12, - "tags": ["限时", "热卖"], - "isPhysical": true - } - ] -} -``` - ---- - -### 3.4 商品详情接口 - -**GET** `/points-mall/products/{id}` - -**描述:** 获取单个商品的详细信息 - -**路径参数:** -- `id`: 商品 ID - -**响应数据:** -```json -{ - "id": "prod_001", - "categoryId": "digital", - "name": "商品名称", - "subtitle": "商品简短描述", - "description": "商品详细描述...", - "coverUrl": "https://...", - "imageUrls": ["https://...", "https://..."], - "pointsPrice": 1990, - "originalPrice": 19.90, - "stock": 99, - "sold": 12, - "tags": ["限时", "热卖"], - "isPhysical": true, - "weight": 0.5 -} -``` - ---- - -### 3.5 兑换商品接口 - -**POST** `/points-mall/exchange` - -**描述:** 提交兑换订单,扣减积分,创建订单 - -**请求体:** -```json -{ - "productId": "prod_001", - "recipientName": "张三", - "phone": "13800138000", - "province": "广东省", - "city": "深圳市", - "district": "南山区", - "address": "科技园南区XX路XX号", - "zipCode": "518000" -} -``` - -**字段说明:** - -| 字段名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| productId | String | 是 | 商品 ID | -| recipientName | String | 是 | 收货人姓名 | -| phone | String | 是 | 手机号码 | -| province | String | 是 | 省份 | -| city | String | 是 | 城市 | -| district | String | 是 | 区/县 | -| address | String | 是 | 详细地址 | -| zipCode | String | 否 | 邮政编码 | - -**响应数据:** -```json -{ - "orderId": "order_20240601_0001", - "pointsDeducted": 1990, - "remainingPoints": 10810 -} -``` - -**错误码:** - -| code | message | 说明 | -|------|---------|------| -| 40001 | 积分不足 | 用户积分不足以兑换该商品 | -| 40002 | 商品已下架 | 商品未发布或已下架 | -| 40003 | 库存不足 | 商品库存不足 | -| 40004 | 商品不存在 | 商品 ID 无效 | - ---- - -### 3.6 兑换订单列表接口 - -**GET** `/points-mall/orders` - -**描述:** 获取用户的兑换订单列表 - -**请求参数:** - -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| status | String | 否 | 订单状态筛选 | -| page | Int | 否 | 页码,默认 1 | -| pageSize | Int | 否 | 每页数量,默认 20 | - -**响应数据:** -```json -{ - "page": 1, - "pageSize": 20, - "total": 5, - "items": [ - { - "id": "order_20240601_0001", - "productId": "prod_001", - "productName": "商品名称", - "coverUrl": "https://...", - "pointsPrice": 1990, - "status": "shipped", - "statusText": "已发货", - "trackingCompany": "顺丰速运", - "trackingNumber": "SF1234567890", - "createdAt": "2024-06-01T10:30:00Z" - } - ] -} -``` - ---- - -### 3.7 订单详情接口 - -**GET** `/points-mall/orders/{id}` - -**描述:** 获取单个订单的详细信息 - -**路径参数:** -- `id`: 订单 ID - -**响应数据:** -```json -{ - "id": "order_20240601_0001", - "productId": "prod_001", - "productName": "商品名称", - "coverUrl": "https://...", - "pointsPrice": 1990, - "status": "shipped", - "statusText": "已发货", - "recipientName": "张三", - "phone": "13800138000", - "province": "广东省", - "city": "深圳市", - "district": "南山区", - "address": "科技园南区XX路XX号", - "zipCode": "518000", - "trackingCompany": "顺丰速运", - "trackingNumber": "SF1234567890", - "shippedAt": "2024-06-02T14:00:00Z", - "createdAt": "2024-06-01T10:30:00Z" -} -``` - ---- - -### 3.8 积分流水接口 - -**GET** `/points-mall/transactions` - -**描述:** 获取用户积分变动记录 - -**请求参数:** - -| 参数名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| type | String | 否 | 类型:`earn`(收入)、`spend`(支出) | -| page | Int | 否 | 页码,默认 1 | -| pageSize | Int | 否 | 每页数量,默认 20 | - -**响应数据:** -```json -{ - "page": 1, - "pageSize": 20, - "total": 150, - "items": [ - { - "id": 1001, - "type": "earn", - "points": 500, - "balance": 12800, - "sourceType": "api_call", - "description": "API 调用奖励", - "createdAt": "2024-06-01T10:30:00Z" - }, - { - "id": 1000, - "type": "spend", - "points": -1990, - "balance": 12300, - "sourceType": "exchange", - "sourceId": "order_20240601_0001", - "description": "兑换商品:XXX", - "createdAt": "2024-06-01T09:00:00Z" - } - ] -} -``` - ---- - -## 四、积分累积逻辑 - -### 4.1 积分累积触发时机 - -积分累积在以下场景触发: -1. **API 调用完成时**:每次成功的 LLM API 调用后,根据实际消费金额计算积分 -2. **活动奖励**:通过活动接口手动发放积分 - -### 4.2 积分计算公式 - -``` -积分 = 实际消费金额(USD) × 1000 -``` - -**示例:** -- 消费 $0.002 → 2 积分 -- 消费 $0.5 → 500 积分 -- 消费 $1.2 → 1200 积分 - -### 4.3 积分入账流程 - -1. API 调用完成,记录 token 使用量和费用 -2. 计算积分:`points = floor(costUSD * 1000)` 或四舍五入 -3. 使用事务更新 `user_points` 表: - - `points` += 新增积分 - - `total_earned` += 新增积分 -4. 写入 `point_transactions` 流水记录 -5. **注意**:需要使用乐观锁防止并发问题 - ---- - -## 五、兑换流程设计 - -### 5.1 兑换流程图 - -``` -用户点击兑换 - ↓ -验证积分是否足够 - ↓ -验证商品是否上架 - ↓ -验证库存是否充足 - ↓ -【事务开始】 -扣减用户积分 - ↓ -扣减商品库存 - ↓ -增加商品已售数量 - ↓ -创建兑换订单 - ↓ -写入积分消费流水 - ↓ -【事务提交】 - ↓ -返回订单信息 -``` - -### 5.2 并发控制 - -1. **用户积分表**:使用乐观锁(`version` 字段)防止超扣 -2. **商品库存**:使用 `UPDATE ... WHERE stock >= 1` 原子操作 -3. **事务隔离**:使用可重复读(REPEATABLE READ)级别 - ---- - -## 六、管理后台接口(可选) - -### 6.1 商品管理 - -- **POST** `/admin/points-mall/products` - 创建商品 -- **PUT** `/admin/points-mall/products/{id}` - 更新商品 -- **DELETE** `/admin/points-mall/products/{id}` - 删除商品 -- **PATCH** `/admin/points-mall/products/{id}/publish` - 上架/下架商品 - -### 6.2 订单管理 - -- **GET** `/admin/points-mall/orders` - 订单列表 -- **PUT** `/admin/points-mall/orders/{id}/ship` - 发货(填写物流信息) -- **PUT** `/admin/points-mall/orders/{id}/confirm` - 确认订单 -- **PUT** `/admin/points-mall/orders/{id}/cancel` - 取消订单(退还积分) - ---- - -## 七、注意事项 - -### 7.1 数据一致性 -- 所有涉及积分变动的操作必须使用数据库事务 -- 使用乐观锁防止并发扣减问题 -- 积分流水表是审计的重要依据,不可删除或修改 - -### 7.2 安全考虑 -- 所有接口必须经过用户认证 -- 积分扣减必须验证当前用户的积分余额 -- 防止重复提交兑换请求(可使用幂等键) - -### 7.3 性能优化 -- 用户积分数据可考虑缓存(如 Redis) -- 商品列表接口建议加缓存 -- 积分流水表建议按时间分区 - -### 7.4 监控告警 -- 积分扣除失败告警 -- 库存不足告警 -- 异常大额积分变动告警 - ---- - -## 八、前端已实现功能 - -前端已完成以下功能开发,等待后端接口对接: - -1. ✅ 积分商城首页 UI,包含积分展示 -2. ✅ 商品分类筛选、搜索、排序 -3. ✅ 商品卡片列表展示 -4. ✅ 兑换弹窗,包含收货地址表单 -5. ✅ 积分不足提示 -6. ✅ 统计页面展示消费金额与积分对应关系 -7. ✅ 1 USD = 1000 积分汇率展示 diff --git a/docs/points-mall-api.md b/docs/points-mall-api.md deleted file mode 100644 index 10a2a29..0000000 --- a/docs/points-mall-api.md +++ /dev/null @@ -1,371 +0,0 @@ -# 积分商城(Points Mall)接口需求说明 - -本文档用于对接 aura-web 的「积分商城」页面数据与兑换流程,供后端实现接口与数据表结构时参考。 - -## 页面结构对应的数据 - -页面 UI 由 4 个区域组成: - -1. 头部商品分类导航栏(横向) -2. 公告栏 -3. banner 区 + 促销活动入口(2 个入口卡片) -4. 商品内容区(商品列表 + 搜索/排序/分页) - -## 统一约定 - -- BaseURL:`https://api.hoyidata.com/aura/v1` -- 鉴权:沿用当前登录态(Cookie / Session)或 `Authorization: Bearer `(以现有登录实现为准),接口需校验登录态 -- 图片字段:返回绝对 URL(推荐 CDN 域名),前端不拼接 -- 时间字段:建议统一使用毫秒时间戳(number) -- 失败格式:沿用现有 aura 接口的错误格式(HTTP Status + JSON message/error) -- `GET /points-mall/overview` 已废弃(HTTP 410),前端需按数据维度分别调用 /me、/categories、/announcements、/banners、/promo-entries - -## 接口列表(前端必需) - -### 1) 我的积分 - -`GET /points-mall/me` - -**Response** -```json -{ - "points": 1280, - "level": "Lv.2", - "totalSpentUSD": 0.174 -} -``` - -### 2) 分类 - -`GET /points-mall/categories` - -**Response** -```json -[ - { "id": "all", "name": "全部", "sort": 0 }, - { "id": "digital", "name": "虚拟权益", "sort": 1 } -] -``` - -### 3) 公告 - -`GET /points-mall/announcements` - -**Response** -```json -[ - { - "id": "a1", - "title": "公告:积分规则升级中", - "content": "本期暂不开放兑换,页面仅用于 UI 预览。", - "linkUrl": "https://..." - } -] -``` - -### 4) Banner - -`GET /points-mall/banners` - -**Response** -```json -[ - { - "id": "b1", - "title": "限时上新", - "subtitle": "Up to 25% Off", - "imageUrl": "https://cdn.../banner.png", - "linkUrl": "https://..." - } -] -``` - -### 5) 促销入口 - -`GET /points-mall/promo-entries` - -**Response** -```json -[ - { - "id": "p1", - "title": "促销活动", - "subtitle": "本周精选", - "iconUrl": "https://cdn.../promo.png", - "linkUrl": "https://..." - }, - { - "id": "p2", - "title": "积分任务", - "subtitle": "快速涨积分", - "iconUrl": "https://cdn.../tasks.png", - "linkUrl": "https://..." - } -] -``` - -### 6) 商品列表(搜索/筛选/排序/分页) - -`GET /points-mall/products` - -**Query** -- `categoryId`:可选(分类 ID;不传代表全部) -- `q`:可选(搜索关键字;匹配 name/subtitle) -- `sort`:可选,默认 `popular` - - `popular`:热度优先(建议按 sold 或近期兑换量) - - `newest`:最新上架 - - `price_asc`:积分从低到高 - - `price_desc`:积分从高到低 -- `page`:可选,默认 `1` -- `pageSize`:可选,默认 `24` - -**Response** -```json -{ - "page": 1, - "pageSize": 24, - "total": 120, - "items": [ - { - "id": "prd_001", - "categoryId": "digital", - "name": "Claude Pro 月卡", - "subtitle": "兑换后获得激活码", - "coverUrl": "https://cdn.../cover.png", - "pointsPrice": 1999, - "stock": 99, - "sold": 12, - "tags": ["热卖", "限时"] - } - ] -} -``` - -### 7) 商品详情 - -`GET /points-mall/products/{productId}` - -**Response(建议)** -```json -{ - "id": "prd_001", - "categoryId": "digital", - "name": "Claude Pro 月卡", - "subtitle": "兑换后获得激活码", - "coverUrl": "https://cdn.../cover.png", - "imageUrls": ["https://cdn.../1.png", "https://cdn.../2.png"], - "pointsPrice": 1999, - "stock": 99, - "sold": 12, - "tags": ["热卖", "限时"], - "description": "富文本/Markdown 均可,建议 Markdown", - "type": "virtual", - "delivery": { - "mode": "code", - "tips": "兑换成功后在【订单详情】查看兑换码" - } -} -``` - -### 8) 创建兑换订单(扣积分) - -`POST /points-mall/orders` - -**Request** -```json -{ - "productId": "prd_001", - "quantity": 1 -} -``` - -**Response** -```json -{ - "orderId": "ord_001", - "status": "paid", - "pointsCost": 1999, - "remainingPoints": 281 -} -``` - -**关键规则** -- 需要幂等:建议支持 `Idempotency-Key` header,避免重复扣积分 -- 校验库存与用户积分 -- 虚拟商品/实物商品可共用该接口,但实物商品需要补充收货信息(可扩展字段) - -### 9) 订单列表 / 订单详情 - -`GET /points-mall/orders` - -**Query(建议)** -- `page` / `pageSize` -- `status`:可选 - -`GET /points-mall/orders/{orderId}` - -**Response(建议)** -```json -{ - "id": "ord_001", - "status": "paid", - "createdAt": 1730000000000, - "product": { - "id": "prd_001", - "name": "Claude Pro 月卡", - "coverUrl": "https://cdn.../cover.png" - }, - "quantity": 1, - "pointsCost": 1999, - "delivery": { - "mode": "code", - "code": "XXXX-YYYY-ZZZZ" - } -} -``` - -### 10) 积分流水(可选,但强烈建议) - -用于解释积分变动与对账。 - -`GET /points-mall/me/points-ledger` - -**Query(建议)** -- `page` / `pageSize` - -**Response(建议)** -```json -{ - "page": 1, - "pageSize": 20, - "total": 300, - "items": [ - { - "id": "pl_001", - "createdAt": 1730000000000, - "change": -1999, - "balance": 281, - "reason": "兑换 Claude Pro 月卡", - "bizType": "points_mall_order", - "bizId": "ord_001" - } - ] -} -``` - -## 数据表结构建议(后端实现参考) - -说明:下述为建议表结构,字段类型可按现有数据库规范调整;建议均包含 `created_at / updated_at` 与必要索引。 - -### 1) `points_mall_categories`(分类) -- `id`(PK, string/uuid) -- `name`(varchar) -- `sort`(int) -- `enabled`(bool) - -索引: -- `enabled, sort` - -### 2) `points_mall_announcements`(公告) -- `id`(PK) -- `title`(varchar) -- `content`(text) -- `link_url`(varchar, nullable) -- `start_at`(bigint, nullable) -- `end_at`(bigint, nullable) -- `enabled`(bool) -- `sort`(int) - -索引: -- `enabled, sort` -- `start_at, end_at` - -### 3) `points_mall_banners`(banner) -- `id`(PK) -- `title`(varchar) -- `subtitle`(varchar, nullable) -- `image_url`(varchar) -- `link_url`(varchar, nullable) -- `start_at`(bigint, nullable) -- `end_at`(bigint, nullable) -- `enabled`(bool) -- `sort`(int) - -索引: -- `enabled, sort` -- `start_at, end_at` - -### 4) `points_mall_promo_entries`(促销入口) -- `id`(PK) -- `title`(varchar) -- `subtitle`(varchar, nullable) -- `icon_url`(varchar, nullable) -- `link_url`(varchar, nullable) -- `enabled`(bool) -- `sort`(int) - -索引: -- `enabled, sort` - -### 5) `points_mall_products`(商品) -- `id`(PK) -- `category_id`(FK -> categories.id) -- `name`(varchar) -- `subtitle`(varchar, nullable) -- `description`(text / markdown) -- `cover_url`(varchar) -- `image_urls`(json/text,数组) -- `type`(enum: `virtual` | `physical`) -- `points_price`(int) -- `stock`(int) -- `sold`(int,累计兑换量,或可由订单聚合) -- `tags`(json/text,数组) -- `enabled`(bool) -- `start_at`(bigint, nullable) -- `end_at`(bigint, nullable) -- `sort`(int) - -索引: -- `enabled, start_at, end_at` -- `category_id, enabled, sort` -- 搜索:`name`/`subtitle` 建议全文索引或 LIKE(视数据库而定) - -### 6) `points_mall_orders`(订单) -- `id`(PK) -- `user_id`(FK) -- `product_id`(FK) -- `quantity`(int) -- `points_cost`(int) -- `status`(enum: `created` | `paid` | `delivered` | `canceled` | `refunded`) -- `delivery_mode`(enum: `code` | `shipping`) -- `delivery_payload`(json,虚拟码/物流信息等) -- `idempotency_key`(varchar, nullable,建议唯一索引 + user_id 组合) - -索引: -- `user_id, created_at desc` -- `status, created_at desc` -- `user_id, idempotency_key`(唯一) - -### 7) `points_ledger`(积分流水) -- `id`(PK) -- `user_id`(FK) -- `change`(int,正负) -- `balance`(int) -- `reason`(varchar) -- `biz_type`(varchar) -- `biz_id`(varchar) - -索引: -- `user_id, created_at desc` -- `biz_type, biz_id` - -## 前端实现已对齐的字段 - -前端已按以下字段读取: -- 分类:`id/name/sort` -- 公告:`title/content/linkUrl` -- banner:`title/subtitle/imageUrl/linkUrl` -- 促销入口:`title/subtitle/iconUrl/linkUrl` -- 商品列表:`id/categoryId/name/subtitle/coverUrl/pointsPrice/stock/sold/tags` - -如后端字段命名需要用 snake_case,可在接口层做映射,但建议直接使用上述 camelCase,减少前端 mapping。 diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index c5594e1..607b7bf 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -51,7 +51,7 @@ const NAV_GROUPS: Array<{ { label: '商城', items: [ - { to: '/points-mall', icon: , label: 'Token商城' } + { to: '/points-mall', icon: , label: '积分商城' } ] } ]; diff --git a/src/pages/LoginPage/styles/login-page-web.css b/src/pages/LoginPage/styles/login-page-web.css index 6a6d8af..9e14dbb 100644 --- a/src/pages/LoginPage/styles/login-page-web.css +++ b/src/pages/LoginPage/styles/login-page-web.css @@ -31,7 +31,7 @@ justify-content: initial; overflow-y: auto; overflow-x: hidden; - padding: 1.25rem 1rem 1.5rem; + padding: 20px 16px 24px; background: var(--gradient-hero); } @@ -40,42 +40,42 @@ min-height: auto; display: flex; flex-direction: column; - gap: 1.25rem; + gap: 20px; } .login-page-web .login-brand-panel { - padding: 0.25rem 0 0; + padding: 4px 0 0; } .login-page-web .login-brand-header { - margin-bottom: 1.5rem; + margin-bottom: 24px; } .login-page-web .login-title { max-width: none; - font-size: 2rem; + font-size: 32px; line-height: 1.18; font-weight: 900; } .login-page-web .login-subtitle { max-width: none; - margin-top: 0.875rem; - font-size: 0.9375rem; + margin-top: 14px; + font-size: 15px; } .login-page-web .login-features { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.625rem; - margin-top: 1rem; + gap: 10px; + margin-top: 16px; } .login-page-web .login-feature-item { - min-height: 2.75rem; + min-height: 44px; border: 1px solid rgba(17, 103, 255, 0.14); - border-radius: 0.875rem; - padding: 0.625rem; + border-radius: 14px; + padding: 10px; background: rgba(255, 255, 255, 0.78); font-weight: 700; } diff --git a/src/pages/PointsMallPage/PointsMallPageLogic.ts b/src/pages/PointsMallPage/PointsMallPageLogic.ts index c27470b..164a5e8 100644 --- a/src/pages/PointsMallPage/PointsMallPageLogic.ts +++ b/src/pages/PointsMallPage/PointsMallPageLogic.ts @@ -42,7 +42,8 @@ export function usePointsMallPageLogic() { ...rawMe, points: Number((rawMe as any)?.points ?? 0), }; - const cats = categoriesRes.status === 'fulfilled' ? categoriesRes.value : MOCK_OVERVIEW.categories; + const loadedCats = categoriesRes.status === 'fulfilled' ? categoriesRes.value : MOCK_OVERVIEW.categories; + const cats = loadedCats?.length ? loadedCats : MOCK_OVERVIEW.categories; const announcements = announcementsRes.status === 'fulfilled' ? announcementsRes.value : MOCK_OVERVIEW.announcements; const banners = bannersRes.status === 'fulfilled' ? bannersRes.value : MOCK_OVERVIEW.banners; const promoEntries = promoEntriesRes.status === 'fulfilled' ? promoEntriesRes.value : MOCK_OVERVIEW.promoEntries; @@ -53,10 +54,10 @@ export function usePointsMallPageLogic() { setCategoryId(cats[0].id); } if (meRes.status === 'rejected') { - message.error('获取Token信息失败,请稍后重试'); + message.error('获取积分信息失败,请稍后重试'); } } catch { - message.error('获取Token信息失败,请稍后重试'); + message.error('获取积分信息失败,请稍后重试'); setOverview({ ...MOCK_OVERVIEW, me: { points: 0, level: 'Lv.0' } }); setCategories(MOCK_OVERVIEW.categories); } finally { @@ -74,7 +75,19 @@ export function usePointsMallPageLogic() { page, pageSize, }); - setProductsRes(res); + if (Array.isArray(res.items) && res.items.length > 0) { + setProductsRes(res); + return; + } + const fallback = MOCK_PRODUCTS.filter((p) => (categoryId === 'all' ? true : p.categoryId === categoryId)).filter((p) => + q ? (p.name + p.subtitle).toLowerCase().includes(q.toLowerCase()) : true + ); + setProductsRes({ + page, + pageSize, + total: fallback.length, + items: fallback.slice((page - 1) * pageSize, page * pageSize), + }); } catch { const filtered = MOCK_PRODUCTS.filter((p) => (categoryId === 'all' ? true : p.categoryId === categoryId)).filter((p) => q ? (p.name + p.subtitle).toLowerCase().includes(q.toLowerCase()) : true @@ -135,7 +148,7 @@ export function usePointsMallPageLogic() { return { ...prev, me: { ...prev.me, points: remainingPoints } }; }); } - message.success('已冻结Token并预扣库存,请继续填写收件信息'); + message.success('已冻结积分并预扣库存,请继续填写收件信息'); } catch (e: any) { const msg = e?.response?.data?.message || e?.response?.data?.error || e?.message || '兑换失败,请稍后重试'; message.error(msg); diff --git a/src/pages/PointsMallPage/components/PointsMallH5Products.tsx b/src/pages/PointsMallPage/components/PointsMallH5Products.tsx index b3517f6..091a00c 100644 --- a/src/pages/PointsMallPage/components/PointsMallH5Products.tsx +++ b/src/pages/PointsMallPage/components/PointsMallH5Products.tsx @@ -2,6 +2,7 @@ import { SearchOutlined } from '@ant-design/icons'; import { Button, Card, Empty, Input, Select, Space, Spin, Tag } from 'antd'; import type { PointsMallProduct } from '../../../api'; import type { PointsMallPageLogicOutput } from '../PointsMallPageLogic'; +import '../styles/points-mall-h5-products.css'; interface Props { logic: PointsMallPageLogicOutput; @@ -52,8 +53,8 @@ export default function PointsMallH5Products({ logic }: Props) { options={[ { value: 'popular', label: '热度优先' }, { value: 'newest', label: '最新上架' }, - { value: 'price_asc', label: 'Token从低到高' }, - { value: 'price_desc', label: 'Token从高到低' } + { value: 'price_asc', label: '积分从低到高' }, + { value: 'price_desc', label: '积分从高到低' } ]} /> {products.map((p: PointsMallProduct) => (
-
+
@@ -197,7 +197,7 @@ export default function PointsMallPageWeb({ logic }: Props) {
{p.pointsPrice >= 1000 ? `${(p.pointsPrice / 1000).toFixed(1)} K` : Number(p.pointsPrice).toLocaleString()} - Token + 积分
diff --git a/src/pages/PointsMallPage/mocks.ts b/src/pages/PointsMallPage/mocks.ts index c3e28e1..c88552e 100644 --- a/src/pages/PointsMallPage/mocks.ts +++ b/src/pages/PointsMallPage/mocks.ts @@ -13,7 +13,7 @@ export const MOCK_OVERVIEW: PointsMallOverview = { banners: [{ id: 'b1', title: '本期活动', subtitle: 'Up to 25% Off', imageUrl: '', linkUrl: '' }], promoEntries: [ { id: 'p1', title: '促销活动', subtitle: '本周精选', linkUrl: '' }, - { id: 'p2', title: 'Token任务', subtitle: '快速涨Token', linkUrl: '' } + { id: 'p2', title: '积分任务', subtitle: '快速涨积分', linkUrl: '' } ] }; diff --git a/src/pages/PointsMallPage/styles/points-mall-h5-products.css b/src/pages/PointsMallPage/styles/points-mall-h5-products.css new file mode 100644 index 0000000..c49605c --- /dev/null +++ b/src/pages/PointsMallPage/styles/points-mall-h5-products.css @@ -0,0 +1,101 @@ +.points-mall-page-h5 .h5-points-mall-products-card .ant-card-body { + padding: 0.75rem; +} + +.points-mall-page-h5 .h5-points-mall-products-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; +} + +.points-mall-page-h5 .h5-product-tile { + min-width: 0; + padding: 0; + border-radius: 0.75rem; + overflow: hidden; +} + +.points-mall-page-h5 .h5-product-tile-cover { + position: relative; + width: 100%; + height: auto; + aspect-ratio: 1; + background: + radial-gradient(circle at 28% 24%, rgba(30, 134, 255, 0.22), transparent 34%), + linear-gradient(135deg, rgba(17, 103, 255, 0.14), rgba(104, 185, 255, 0.12)); +} + +.h5-product-tile-image { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.points-mall-page-h5 .h5-product-tile-tag { + position: absolute; + top: 0.25rem; + left: 0.25rem; + max-width: calc(100% - 0.5rem); + margin: 0; + padding: 0 0.3125rem; + font-size: 0.625rem; + line-height: 1.25rem; + border-radius: 999px; +} + +.points-mall-page-h5 .h5-product-tile .points-mall-product-body { + padding: 0.4375rem; + gap: 0.3125rem; +} + +.points-mall-page-h5 .h5-product-tile .points-mall-product-name { + min-height: 2.125rem; + margin: 0; + color: var(--color-text); + font-size: 0.75rem; + font-weight: 700; + line-height: 1.38; + white-space: normal; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.points-mall-page-h5 .h5-product-tile .h5-points-mall-product-price-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 1.875rem; + align-items: center; + gap: 0.25rem; +} + +.points-mall-page-h5 .h5-product-tile .h5-points-mall-product-price-row > div { + min-width: 0; + white-space: nowrap; +} + +.points-mall-page-h5 .h5-product-tile .points-mall-product-price { + font-size: 0.75rem; + font-weight: 900; +} + +.points-mall-page-h5 .h5-product-tile .points-mall-product-price-label { + margin-left: 0.125rem; + font-size: 0.625rem; +} + +.points-mall-page-h5 .h5-product-tile .points-mall-product-exchange-btn { + width: 1.875rem; + min-width: 1.875rem; + height: 1.625rem; + padding: 0; + border-radius: 0.5rem; + font-size: 0.6875rem; + font-weight: 800; +} + +@media (max-width: 340px) { + .points-mall-page-h5 .h5-points-mall-products-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} diff --git a/src/pages/PointsMallPage/styles/points-mall-h5.css b/src/pages/PointsMallPage/styles/points-mall-h5.css index 288a73b..228039c 100644 --- a/src/pages/PointsMallPage/styles/points-mall-h5.css +++ b/src/pages/PointsMallPage/styles/points-mall-h5.css @@ -4,13 +4,12 @@ } .points-mall-page-h5 .h5-points-mall-hero { - padding: 1rem 0.75rem; + padding: 0.875rem 0.75rem; border-radius: 1.125rem; background: var(--gradient-hero); } .points-mall-page-h5 .h5-points-mall-header, -.points-mall-page-h5 .h5-points-balance-row, .points-mall-page-h5 .h5-points-mall-banner-header, .points-mall-page-h5 .h5-points-mall-product-price-row { flex-direction: column; @@ -23,28 +22,27 @@ } .points-mall-page-h5 .h5-page-title { - margin-bottom: 0.375rem; - font-size: 1.5rem; + margin-bottom: 0.25rem; + font-size: 1.375rem; } .points-mall-page-h5 .h5-page-subtitle { - font-size: 0.8125rem; - line-height: 1.5; + font-size: 0.75rem; + line-height: 1.45; } .points-mall-page-h5 .h5-points-mall-balance-card, .points-mall-page-h5 .points-balance-main, -.points-mall-page-h5 .points-mall-banner-action, -.points-mall-page-h5 .points-mall-product-exchange-btn { +.points-mall-page-h5 .points-mall-banner-action { width: 100%; } -.points-mall-page-h5 .h5-points-mall-balance-card, -.points-mall-page-h5 .h5-points-mall-product-card { - padding: 0.75rem; +.points-mall-page-h5 .h5-points-mall-balance-card { + padding: 0.625rem; } .points-mall-page-h5 .h5-points-balance-row { + align-items: center; gap: 0.5rem; } @@ -57,7 +55,7 @@ } .points-mall-page-h5 .points-balance-value { - font-size: 1.75rem; + font-size: 1.5rem; } .points-mall-level-tag { @@ -73,34 +71,41 @@ } .points-mall-page-h5 .h5-points-mall-category-row { + flex-wrap: nowrap; gap: 0.375rem; + overflow-x: auto; + scrollbar-width: none; +} + +.points-mall-page-h5 .h5-points-mall-category-row::-webkit-scrollbar { + display: none; } .points-mall-page-h5 .h5-points-mall-category-label { - width: 100%; - margin-bottom: 0.5rem; + width: auto; + margin-bottom: 0; + white-space: nowrap; } .points-mall-category-button { + flex: 0 0 auto; border-radius: 999px; font-size: 0.75rem; } .points-mall-page-h5 .h5-points-mall-banner-section, -.points-mall-page-h5 .h5-points-mall-promo-grid, -.points-mall-page-h5 .h5-points-mall-products-grid { +.points-mall-page-h5 .h5-points-mall-promo-grid { display: grid; grid-template-columns: 1fr; } .points-mall-page-h5 .h5-points-mall-banner-card { width: 100%; - min-height: 10rem; - padding: 1rem; + min-height: auto; + padding: 0.75rem; } -.points-mall-page-h5 .h5-points-mall-banner-header, -.points-mall-page-h5 .h5-points-mall-product-price-row { +.points-mall-page-h5 .h5-points-mall-banner-header { gap: 0.5rem; } @@ -137,7 +142,7 @@ .points-mall-page-h5 .h5-points-mall-filters-row { flex-direction: column; - gap: 0.625rem; + gap: 0.5rem; } .points-mall-filter-search, @@ -150,7 +155,7 @@ .points-mall-filter-selects { display: grid; - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr) 5.875rem; gap: 0.5rem; } @@ -175,26 +180,6 @@ margin-top: 2.5rem; } -.points-mall-page-h5 .h5-points-mall-products-grid { - gap: 0.75rem; -} - -.points-mall-page-h5 .points-mall-product-name { - font-size: 0.9375rem; -} - -.points-mall-page-h5 .points-mall-product-desc { - font-size: 0.75rem; -} - -.points-mall-page-h5 .points-mall-product-tag { - font-size: 0.6875rem; -} - -.points-mall-page-h5 .points-mall-product-price { - font-size: 1.25rem; -} - .points-mall-page-h5 .h5-points-mall-pagination { margin-top: 1rem; } diff --git a/src/pages/StatsPage/components/StatsPageWeb.tsx b/src/pages/StatsPage/components/StatsPageWeb.tsx index aef9e30..2ecd0cc 100644 --- a/src/pages/StatsPage/components/StatsPageWeb.tsx +++ b/src/pages/StatsPage/components/StatsPageWeb.tsx @@ -23,6 +23,7 @@ export default function StatsPageWeb({ logic }: { logic: StatsPageLogic }) {

调用统计

不只是查看调用数量,而是帮助你感知哪些智能体正在被频繁使用、最近的消息趋势如何,以及整体会话是否健康增长。 + 每消费 1 美元可获得 1000 积分。

diff --git a/src/pages/StatsPage/components/StatsPointsCard.tsx b/src/pages/StatsPage/components/StatsPointsCard.tsx index 7510792..8d8efbe 100644 --- a/src/pages/StatsPage/components/StatsPointsCard.tsx +++ b/src/pages/StatsPage/components/StatsPointsCard.tsx @@ -6,9 +6,9 @@ export default function StatsPointsCard({ logic }: { logic: StatsPageLogic }) { return (
1 USD = 1000 Token} + extra={1 USD = 1000 积分} >
@@ -22,7 +22,7 @@ export default function StatsPointsCard({ logic }: { logic: StatsPageLogic }) {
- 累计Token + 累计积分
{logic.formatPoints(logic.totalPoints)}
可用于兑换商城商品
@@ -30,10 +30,10 @@ export default function StatsPointsCard({ logic }: { logic: StatsPageLogic }) {
- Token兑换汇率 + 积分汇率
- {/*
100:1
*/} - {/*
每消费 100 token 获得 1 Token
*/} +
1:1000
+
每消费 1 美元获得 1000 积分
diff --git a/src/pages/StatsPage/styles/stats-page-web.css b/src/pages/StatsPage/styles/stats-page-web.css index 3808d03..5ea334d 100644 --- a/src/pages/StatsPage/styles/stats-page-web.css +++ b/src/pages/StatsPage/styles/stats-page-web.css @@ -3,17 +3,17 @@ } .stats-page-web .stats-page-title { - margin-bottom: 0.625rem; + margin-bottom: 10px; } .stats-page-web .stats-page-subtitle { margin-top: 0; - font-size: 0.9375rem; + font-size: 15px; line-height: 1.75; } .stats-page-web .stats-page-main-grid { - margin-top: 1.125rem; + margin-top: 18px; } .stats-page-web .stats-page-card-icon { diff --git a/src/pages/WorkflowsPage/styles/workflows-page-web.css b/src/pages/WorkflowsPage/styles/workflows-page-web.css index 1ff127e..b910039 100644 --- a/src/pages/WorkflowsPage/styles/workflows-page-web.css +++ b/src/pages/WorkflowsPage/styles/workflows-page-web.css @@ -3,9 +3,9 @@ } .workflows-hero { - border-radius: 1.5rem; - padding: 1.875rem; - margin-bottom: 1.5rem; + border-radius: 24px; + padding: 30px; + margin-bottom: 24px; background: var(--gradient-hero); } @@ -13,78 +13,78 @@ display: flex; justify-content: space-between; align-items: flex-start; - gap: 1.25rem; + gap: 20px; flex-wrap: wrap; - margin-bottom: 1.25rem; + margin-bottom: 20px; } .workflows-hero-copy { - max-width: 42.5rem; + max-width: 680px; } .workflows-title { - margin: 1rem 0 0.625rem; + margin: 16px 0 10px; } .workflows-subtitle { margin-top: 0; - font-size: 0.9375rem; + font-size: 15px; line-height: 1.75; } .workflows-create-btn { - height: 2.875rem; - padding: 0 1.125rem; + height: 46px; + padding: 0 18px; font-weight: 700; } .workflows-state-spin { display: block; - margin-top: 2rem; + margin-top: 32px; } .workflows-empty-card { - border-radius: 1.375rem; - padding: 3.375rem 1.5rem; + border-radius: 22px; + padding: 54px 24px; } .workflows-card-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(21.25rem, 1fr)); - gap: 1.125rem; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 18px; } .workflow-card-schedule { - border-radius: 1rem; - padding: 1rem; + border-radius: 16px; + padding: 16px; background: linear-gradient(180deg, rgba(248, 251, 255, 0.92), rgba(255, 255, 255, 0.96)); border: 1px solid rgba(148, 163, 184, 0.14); - margin-bottom: 1rem; + margin-bottom: 16px; } .workflow-card-schedule-label { - gap: 0.5rem; + gap: 8px; color: var(--color-text-secondary); - font-size: 0.8125rem; - margin-bottom: 0.625rem; + font-size: 13px; + margin-bottom: 10px; } .workflow-card-schedule-time { color: var(--color-text-tertiary); - font-size: 0.78125rem; - margin-top: 0.625rem; + font-size: 12.5px; + margin-top: 10px; } .workflow-card-actions { - gap: 0.5rem; + gap: 8px; margin-top: auto; - padding-top: 1rem; + padding-top: 16px; border-top: 1px solid var(--color-border); } .workflow-action-primary, .workflow-action-secondary { - height: 2.5rem; + height: 40px; } .workflow-action-primary { diff --git a/src/styles.css b/src/styles.css index 314a25c..e60705c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1960,9 +1960,6 @@ span.mention { .points-mall-product-cover { height: 156px; background: linear-gradient(135deg, rgba(8,145,178,0.12) 0%, rgba(59,130,246,0.10) 55%, rgba(34,197,94,0.10) 100%); - background-size: cover; - background-position: center; - background-repeat: no-repeat; } .points-mall-product-body { diff --git a/src/styles/app-shell-h5.css b/src/styles/app-shell-h5.css index 718d893..e973f46 100644 --- a/src/styles/app-shell-h5.css +++ b/src/styles/app-shell-h5.css @@ -7,7 +7,7 @@ padding: 0 0.625rem; border-bottom: 1px solid var(--color-border); background: rgba(255, 255, 255, 0.92); - backdrop-filter: blur(18px); + backdrop-filter: blur(1.125rem); } .mobile-topbar-title { @@ -47,24 +47,24 @@ cursor: pointer; justify-content: space-between; background: var(--color-surface-2); - margin-bottom: 8px; + margin-bottom: 0.5rem; } .sidebar-search-label { display: flex; align-items: center; - gap: 10px; + gap: 0.625rem; } .sidebar-scroll { flex: 1; overflow-y: auto; - margin-right: -4px; - padding-right: 4px; + margin-right: -0.25rem; + padding-right: 0.25rem; } .sidebar-user { - margin-top: 8px; + margin-top: 0.5rem; } .sidebar-user-avatar { @@ -73,7 +73,7 @@ } .sidebar-user-name { - font-size: 13px; + font-size: 0.8125rem; color: var(--color-text); font-weight: 600; overflow: hidden; @@ -82,6 +82,6 @@ } .sidebar-user-role { - font-size: 11px; + font-size: 0.6875rem; color: var(--color-text-tertiary); }