diff --git a/common/api.js b/common/api.js
index 6e783b4..ea31dc6 100644
--- a/common/api.js
+++ b/common/api.js
@@ -1,7 +1,7 @@
/* 碰盏日记 - API 服务层
* 基于 HaveADrink SDK 封装后端接口
*/
-import HaveADrink from 'HaveADrink'
+import HaveADrink, { GetAchievementsReq } from 'HaveADrink'
import UniAppWebSocket from './uni-websocket'
// ==========================================
@@ -343,6 +343,17 @@ export async function GetUnreadCount() {
return { data: res }
}
+// ==========================================
+// 成就 API
+// ==========================================
+
+/** 获取成就列表(使用 SDK GetAchievementsReq 请求实例) */
+export async function GetAchievements() {
+ const req = new GetAchievementsReq()
+ const res = await client.GetAchievements(req)
+ return { data: res }
+}
+
// ==========================================
// 文件上传 API
// ==========================================
diff --git a/common/constants.js b/common/constants.js
index f3852ac..21b9e15 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -179,6 +179,7 @@ export const DRINK_QUOTES = [
// 成就定义
export const ACHIEVEMENTS = [
+ // 打卡次数类
{
id: 'first_checkin',
name: '初来乍到',
@@ -187,11 +188,33 @@ export const ACHIEVEMENTS = [
condition: { type: 'total_records', value: 1 }
},
{
- id: 'hundred_cups',
- name: '百杯不醉',
- desc: '累计饮用100标准杯',
- icon: '🏆',
- condition: { type: 'total_standard_cups', value: 100 }
+ id: 'records_10',
+ name: '小有心得',
+ desc: '累计打卡10次,记录已成习惯',
+ icon: '📖',
+ condition: { type: 'total_records', value: 10 }
+ },
+ {
+ id: 'records_50',
+ name: '记录老手',
+ desc: '累计打卡50次,每一杯都有迹可循',
+ icon: '✒️',
+ condition: { type: 'total_records', value: 50 }
+ },
+ {
+ id: 'records_100',
+ name: '酒档宗师',
+ desc: '累计打卡100次,个人酒档初具规模',
+ icon: '🏵️',
+ condition: { type: 'total_records', value: 100 }
+ },
+ // 连续打卡类
+ {
+ id: 'streak_7',
+ name: '七日之约',
+ desc: '连续打卡7天,一周不落',
+ icon: '📅',
+ condition: { type: 'streak_days', value: 7 }
},
{
id: 'streak_30',
@@ -201,11 +224,48 @@ export const ACHIEVEMENTS = [
condition: { type: 'streak_days', value: 30 }
},
{
- id: 'baijiu_master',
- name: '白酒达人',
- desc: '白酒累计打卡20次',
- icon: '/static/icons/baijiu.svg',
- condition: { type: 'category_records', category: 'baijiu', value: 20 }
+ id: 'streak_100',
+ name: '百日坚持',
+ desc: '连续打卡100天,毅力惊人',
+ icon: '💯',
+ condition: { type: 'streak_days', value: 100 }
+ },
+ // 标准杯累计类
+ {
+ id: 'cups_10',
+ name: '微醺初体验',
+ desc: '累计饮用10标准杯',
+ icon: '🥂',
+ condition: { type: 'total_standard_cups', value: 10 }
+ },
+ {
+ id: 'cups_50',
+ name: '五十杯之路',
+ desc: '累计饮用50标准杯,微醺是最好的状态',
+ icon: '🍻',
+ condition: { type: 'total_standard_cups', value: 50 }
+ },
+ {
+ id: 'hundred_cups',
+ name: '百杯不醉',
+ desc: '累计饮用100标准杯',
+ icon: '🏆',
+ condition: { type: 'total_standard_cups', value: 100 }
+ },
+ {
+ id: 'cups_300',
+ name: '海量传说',
+ desc: '累计饮用300标准杯,记得理性饮酒哦',
+ icon: '🌊',
+ condition: { type: 'total_standard_cups', value: 300 }
+ },
+ // 酒类探索类
+ {
+ id: 'variety_3',
+ name: '探索起步',
+ desc: '尝试过3种不同类型的酒',
+ icon: '🧭',
+ condition: { type: 'category_variety', value: 3 }
},
{
id: 'tasting_master',
@@ -213,6 +273,35 @@ export const ACHIEVEMENTS = [
desc: '尝试过5种以上不同类型的酒',
icon: '🌈',
condition: { type: 'category_variety', value: 5 }
+ },
+ {
+ id: 'variety_8',
+ name: '酒类百科',
+ desc: '尝试过8种以上不同类型的酒,尝遍百味',
+ icon: '📚',
+ condition: { type: 'category_variety', value: 8 }
+ },
+ // 单一酒类深耕类
+ {
+ id: 'baijiu_master',
+ name: '白酒达人',
+ desc: '白酒累计打卡20次',
+ icon: '/static/icons/baijiu.svg',
+ condition: { type: 'category_records', category: 'baijiu', value: 20 }
+ },
+ {
+ id: 'beer_master',
+ name: '啤酒之王',
+ desc: '啤酒累计打卡20次,冰爽永不停',
+ icon: '🍺',
+ condition: { type: 'category_records', category: 'beer', value: 20 }
+ },
+ {
+ id: 'wine_master',
+ name: '红酒绅士',
+ desc: '红酒累计打卡20次,品味微酸的美好',
+ icon: '🍷',
+ condition: { type: 'category_records', category: 'wine', value: 20 }
}
]
diff --git a/common/utils.js b/common/utils.js
index 1a7800b..4596558 100644
--- a/common/utils.js
+++ b/common/utils.js
@@ -388,8 +388,9 @@ export function checkAchievements(stats, achievements) {
unlocked = stats.categoryVariety >= value
break
case 'category_records':
- // 需要更细粒度的数据,这里简化处理
- unlocked = false
+ // 依赖 stats.categoryRecords({ baijiu: n, beer: n, ... });
+ // 统计中无该字段时保守判为未解锁
+ unlocked = ((stats.categoryRecords && stats.categoryRecords[category]) || 0) >= value
break
}
diff --git a/docs/成就徽章对接文档.md b/docs/成就徽章对接文档.md
new file mode 100644
index 0000000..8ccb885
--- /dev/null
+++ b/docs/成就徽章对接文档.md
@@ -0,0 +1,127 @@
+# 成就徽章体系 - 后端对接文档
+
+> 前端已将成就体系从 5 个扩充到 17 个,本文档供后端对齐成就定义、解锁判定与接口返回格式。
+> 前端本地已实现完整兜底计算,后端接口未就绪或返回为空时不影响前端展示。
+
+---
+
+## 一、成就清单(共 17 个)
+
+### 1. 打卡次数类(total_records)
+
+| id | 名称 | 描述 | 解锁条件 |
+|----|------|------|----------|
+| first_checkin | 初来乍到 | 完成第一次打卡,开启你的饮酒记录之旅 | 累计打卡 ≥ 1 次 |
+| records_10 | 小有心得 | 累计打卡10次,记录已成习惯 | 累计打卡 ≥ 10 次 |
+| records_50 | 记录老手 | 累计打卡50次,每一杯都有迹可循 | 累计打卡 ≥ 50 次 |
+| records_100 | 酒档宗师 | 累计打卡100次,个人酒档初具规模 | 累计打卡 ≥ 100 次 |
+
+### 2. 连续打卡类(streak_days)
+
+| id | 名称 | 描述 | 解锁条件 |
+|----|------|------|----------|
+| streak_7 | 七日之约 | 连续打卡7天,一周不落 | 连续打卡 ≥ 7 天 |
+| streak_30 | 签到王者 | 连续打卡30天,自律的记录者 | 连续打卡 ≥ 30 天 |
+| streak_100 | 百日坚持 | 连续打卡100天,毅力惊人 | 连续打卡 ≥ 100 天 |
+
+### 3. 标准杯累计类(total_standard_cups)
+
+| id | 名称 | 描述 | 解锁条件 |
+|----|------|------|----------|
+| cups_10 | 微醺初体验 | 累计饮用10标准杯 | 累计标准杯 ≥ 10 |
+| cups_50 | 五十杯之路 | 累计饮用50标准杯,微醺是最好的状态 | 累计标准杯 ≥ 50 |
+| hundred_cups | 百杯不醉 | 累计饮用100标准杯 | 累计标准杯 ≥ 100 |
+| cups_300 | 海量传说 | 累计饮用300标准杯,记得理性饮酒哦 | 累计标准杯 ≥ 300 |
+
+### 4. 酒类探索类(category_variety)
+
+| id | 名称 | 描述 | 解锁条件 |
+|----|------|------|----------|
+| variety_3 | 探索起步 | 尝试过3种不同类型的酒 | 酒类品种数 ≥ 3 |
+| tasting_master | 品鉴师 | 尝试过5种以上不同类型的酒 | 酒类品种数 ≥ 5 |
+| variety_8 | 酒类百科 | 尝试过8种以上不同类型的酒,尝遍百味 | 酒类品种数 ≥ 8 |
+
+### 5. 单一酒类深耕类(category_records)
+
+| id | 名称 | 描述 | 解锁条件 |
+|----|------|------|----------|
+| baijiu_master | 白酒达人 | 白酒累计打卡20次 | category=baijiu,打卡 ≥ 20 次 |
+| beer_master | 啤酒之王 | 啤酒累计打卡20次,冰爽永不停 | category=beer,打卡 ≥ 20 次 |
+| wine_master | 红酒绅士 | 红酒累计打卡20次,品味微酸的美好 | category=wine,打卡 ≥ 20 次 |
+
+---
+
+## 二、条件类型说明
+
+| type | 说明 | value 含义 | 附加字段 |
+|------|------|-----------|----------|
+| total_records | 总打卡次数 | 次数 | 无 |
+| streak_days | 连续打卡天数 | 天数 | 无 |
+| total_standard_cups | 累计标准杯 | 杯数 | 无 |
+| category_variety | 尝试过的酒类品种数 | 品种数 | 无 |
+| category_records | 某酒类打卡次数 | 次数 | `category`(baijiu / beer / wine,后续可扩展其他酒类) |
+
+**判定规则统一为 `>=`**(达到阈值即解锁,解锁后不回退)。
+
+---
+
+## 三、接口要求
+
+### 3.1 GET /achievements 响应格式
+
+```json
+{
+ "achievements": [
+ {
+ "id": "first_checkin",
+ "name": "初来乍到",
+ "desc": "完成第一次打卡,开启你的饮酒记录之旅",
+ "unlocked": true,
+ "unlockedAt": "2026-08-10T20:00:00.000Z",
+ "condition": { "type": "total_records", "value": 1 },
+ "progress": 1.0
+ }
+ ]
+}
+```
+
+**字段约定:**
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|:---:|------|
+| id | string | 是 | 成就ID,必须与本文档清单一致 |
+| name / desc | string | 是 | 名称与描述,与清单保持一致 |
+| unlocked | boolean | 是 | 是否已解锁 |
+| unlockedAt | string/null | 否 | 解锁时间,ISO 8601 |
+| condition | object | 是 | 与清单一致;category_records 需带 category |
+| progress | number | 否 | 进度 0~1(当前值/目标值,封顶 1) |
+| icon | string | 否 | **后端无需返回有效图标**,前端忽略该字段,统一使用本地预置图标 |
+
+### 3.2 注意事项
+
+1. **必须返回全量 17 条**(含未解锁的),前端按列表顺序渲染成就墙;不要只返回已解锁项
+2. **icon 字段可不返回**:后端返回的图片路径前端不会使用,图标由前端本地控制
+3. **progress 计算**:`min(当前累计值 / value, 1)`;category_records 按对应酒类的打卡次数计算
+4. **解锁检测时机**:建议在创建打卡记录后异步触发(避免阻塞主流程),比对全部成就条件并写入解锁记录
+5. **幂等**:同一成就解锁只记录一次,`unlocked_at` 以首次解锁时间为准
+6. **id 兼容**:前端取值顺序为 `achievement_id || id`,两者返回其一即可
+
+### 3.3 存储建议
+
+```sql
+CREATE TABLE user_achievements (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ user_id VARCHAR(32) NOT NULL,
+ achievement_id VARCHAR(32) NOT NULL,
+ unlocked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE INDEX idx_user_achievement (user_id, achievement_id)
+);
+```
+
+---
+
+## 四、前端兜底策略(供后端了解)
+
+- 接口失败或返回空数组时,前端用本地统计(totalRecords / totalCups / streak / categoryVariety)对照上述条件自行计算解锁状态
+- 本地计算 `category_records` 类成就依赖 `stats.categoryRecords`(各酒类打卡次数映射),统计接口若能返回该字段可提升兜底准确度
+- 因此后端即使暂未实现,前端也不会报错,只是解锁以本地统计为准
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
index 1dc2c4e..b127061 100644
--- a/node_modules/.package-lock.json
+++ b/node_modules/.package-lock.json
@@ -4,9 +4,9 @@
"requires": true,
"packages": {
"node_modules/HaveADrink": {
- "version": "1.0.16",
- "resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.16.tgz",
- "integrity": "sha512-NKaV3AZHHVYT23Eo4WGTzZ7H+FVfQoHa8EE3k5SnHCi9yxGAxnExBqRYde5AEFI1pJNe7OzqzbxAykxN757LXQ==",
+ "version": "1.0.17",
+ "resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.17.tgz",
+ "integrity": "sha512-7aKlBtul4YCZLlGEF72OtaGsNzWX4nJlPvvxt7iJ7hu/x7MLD6yQWuc/IshlEf94A7aQmDxJubOdcrBcoLQjWw==",
"license": "ISC"
}
}
diff --git a/node_modules/HaveADrink/README.md b/node_modules/HaveADrink/README.md
index d994092..1498d25 100644
--- a/node_modules/HaveADrink/README.md
+++ b/node_modules/HaveADrink/README.md
@@ -14,7 +14,7 @@
* 6. 社交模块 - 朋友圈动态、点赞
-**版本:** v1.0.16
+**版本:** v1.0.17
## 安装
@@ -220,6 +220,53 @@ const req = new GetAchievementsReq()
client.GetAchievements(req).then(...).catch(...)
```
+### 获取用户所有成就列表。需登录认证,返回全量17项及解锁进度
+
+
+
+GET `/api/have_a_drink/v1/achievements/achievements`
+
+#### 请求参数
+|名称|类型|校验规则|说明|
+|:-|:-|:-|:-|
+
+
+
+
+#### 返回值
+|名称|类型|说明|
+|:-|:-:|:-|
+|achievements|`Array`| 全量 17 个成就(含未解锁)
|
+
+
+
+**AchievementItem**
+|名称|类型|说明|
+|:-|:-|:-|
+|id|`string`| 成就唯一标识(如 first_checkin)|
+|name|`string`| 成就名称|
+|desc|`string`| 成就描述|
+|unlocked|`boolean`| 是否已解锁|
+|unlockedAt|`string`| 解锁时间(ISO 8601),未解锁时为 null|
+|condition|`Condition`| 解锁条件|
+|progress|`number`| 进度 (0~1)|
+|icon,omitempty|`string`| 图标资源(前端忽略该字段,后端可省略)|
+
+
+**Condition**
+|名称|类型|说明|
+|:-|:-|:-|
+|type|`ConditionType`| 条件类型|
+|value|`number`| 目标阈值|
+|category,omitempty|`string`| 酒类分类(仅当 Type = category_records 时有效)|
+
+
+
+```javascript
+const req = new GetAchievementListReq()
+client.GetAchievementList(req).then(...).catch(...)
+```
+
### 获取朋友圈动态
@@ -1092,6 +1139,7 @@ client.GetUserProfile(req).then(...).catch(...)
### 更新用户偏好
+**已废弃:** 未使用
PUT `/api/have_a_drink/v1/user/user/preferences`
diff --git a/node_modules/HaveADrink/index.d.ts b/node_modules/HaveADrink/index.d.ts
index 5f36209..2d7cac0 100644
--- a/node_modules/HaveADrink/index.d.ts
+++ b/node_modules/HaveADrink/index.d.ts
@@ -65,6 +65,35 @@ export declare class AchievementConditionType extends Enum {
static getLabel(v: number|string): string;
}
+/**
+ * 成就条件类型
+ */
+export declare class ConditionType extends Enum {
+ /**
+ * 总打卡次数
+ */
+ static TotalRecords: ConditionType;
+ /**
+ * 连续打卡天数
+ */
+ static StreakDays: ConditionType;
+ /**
+ * 累计标准杯数
+ */
+ static TotalStandardCups: ConditionType;
+ /**
+ * 尝试过的酒类品种数
+ */
+ static CategoryVariety: ConditionType;
+ /**
+ * 某酒类打卡次数(需配合 category 字段)
+ */
+ static CategoryRecords: ConditionType;
+
+ static entries(): Array