feat(api): 添加成就系统并升级SDK
- 升级 HaveADrink SDK 从 1.0.16 到 1.0.17 版本 - 新增 GetAchievements API 接口及相关数据类型定义 - 添加 17 个成就定义,包括打卡次数、连续打卡、标准杯累计、酒类探索等类别 - 实现成就解锁逻辑计算,支持 category_records 类型成就判断 - 创建成就徽章对接文档,定义接口格式和存储建议 - 在用户资料页面集成成就展示功能,优化图标匹配逻辑
This commit is contained in:
+12
-1
@@ -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
|
||||
// ==========================================
|
||||
|
||||
+99
-10
@@ -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 }
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
+3
-2
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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`(各酒类打卡次数映射),统计接口若能返回该字段可提升兜底准确度
|
||||
- 因此后端即使暂未实现,前端也不会报错,只是解锁以本地统计为准
|
||||
+3
-3
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+49
-1
@@ -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项及解锁进度
|
||||
|
||||
|
||||
|
||||
<font color="green">GET</font> `/api/have_a_drink/v1/achievements/achievements`
|
||||
|
||||
#### 请求参数
|
||||
|名称|类型|校验规则|说明|
|
||||
|:-|:-|:-|:-|
|
||||
|
||||
|
||||
|
||||
|
||||
#### 返回值
|
||||
|名称|类型|说明|
|
||||
|:-|:-:|:-|
|
||||
|achievements|`Array<AchievementItem>`| 全量 17 个成就(含未解锁)<br>|
|
||||
|
||||
|
||||
|
||||
**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(...)
|
||||
### 更新用户偏好
|
||||
|
||||
|
||||
**已废弃:** 未使用
|
||||
|
||||
<font color="green">PUT</font> `/api/have_a_drink/v1/user/user/preferences`
|
||||
|
||||
|
||||
+178
@@ -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<option>;
|
||||
static getLabel(v: number|string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 酒类分类
|
||||
*/
|
||||
@@ -939,6 +968,146 @@ export declare class AchievementsData {
|
||||
static fromObject(o: Object): AchievementsData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空请求(用于无参接口)
|
||||
*/
|
||||
export declare class GetAchievementListReq {
|
||||
[key: string]: any;
|
||||
|
||||
/**
|
||||
*/
|
||||
constructor();
|
||||
/**
|
||||
* 从对象创建 GetAchievementListReq
|
||||
*
|
||||
* @param o Object
|
||||
*/
|
||||
static fromObject(o: Object): GetAchievementListReq;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成就条件详情
|
||||
*/
|
||||
export declare class Condition {
|
||||
[key: string]: any;
|
||||
/**
|
||||
* 条件类型
|
||||
*/
|
||||
type: ConditionType;
|
||||
/**
|
||||
* 目标阈值
|
||||
*/
|
||||
value: number;
|
||||
/**
|
||||
* 酒类分类(仅当 Type = category_records 时有效)
|
||||
*/
|
||||
category,omitempty: string;
|
||||
|
||||
/**
|
||||
* @param type ConditionType 条件类型
|
||||
* @param value number 目标阈值
|
||||
* @param category,omitempty string 酒类分类(仅当 Type = category_records 时有效)
|
||||
*/
|
||||
constructor(type: ConditionType,value: number,category,omitempty: string,);
|
||||
/**
|
||||
* 从对象创建 Condition
|
||||
*
|
||||
* @param o Object
|
||||
* - type: ConditionType, // 条件类型
|
||||
* - value: number, // 目标阈值
|
||||
* - category,omitempty: string, // 酒类分类(仅当 Type = category_records 时有效)
|
||||
*/
|
||||
static fromObject(o: Object): Condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个成就项
|
||||
*/
|
||||
export declare class AchievementItem {
|
||||
[key: string]: any;
|
||||
/**
|
||||
* 成就唯一标识(如 first_checkin)
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* 成就名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 成就描述
|
||||
*/
|
||||
desc: string;
|
||||
/**
|
||||
* 是否已解锁
|
||||
*/
|
||||
unlocked: boolean;
|
||||
/**
|
||||
* 解锁时间(ISO 8601),未解锁时为 null
|
||||
*/
|
||||
unlockedAt: string;
|
||||
/**
|
||||
* 解锁条件
|
||||
*/
|
||||
condition: Condition;
|
||||
/**
|
||||
* 进度 (0~1)
|
||||
*/
|
||||
progress: number;
|
||||
/**
|
||||
* 图标资源(前端忽略该字段,后端可省略)
|
||||
*/
|
||||
icon,omitempty: string;
|
||||
|
||||
/**
|
||||
* @param id string 成就唯一标识(如 first_checkin)
|
||||
* @param name string 成就名称
|
||||
* @param desc string 成就描述
|
||||
* @param unlocked boolean 是否已解锁
|
||||
* @param unlockedAt string 解锁时间(ISO 8601),未解锁时为 null
|
||||
* @param condition Condition 解锁条件
|
||||
* @param progress number 进度 (0~1)
|
||||
* @param icon,omitempty string 图标资源(前端忽略该字段,后端可省略)
|
||||
*/
|
||||
constructor(id: string,name: string,desc: string,unlocked: boolean,unlockedAt: string,condition: Condition,progress: number,icon,omitempty: string,);
|
||||
/**
|
||||
* 从对象创建 AchievementItem
|
||||
*
|
||||
* @param o Object
|
||||
* - id: string, // 成就唯一标识(如 first_checkin)
|
||||
* - name: string, // 成就名称
|
||||
* - desc: string, // 成就描述
|
||||
* - unlocked: boolean, // 是否已解锁
|
||||
* - unlockedAt: string, // 解锁时间(ISO 8601),未解锁时为 null
|
||||
* - condition: Condition, // 解锁条件
|
||||
* - progress: number, // 进度 (0~1)
|
||||
* - icon,omitempty: string, // 图标资源(前端忽略该字段,后端可省略)
|
||||
*/
|
||||
static fromObject(o: Object): AchievementItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成就列表响应
|
||||
*/
|
||||
export declare class GetAchievementListResp {
|
||||
[key: string]: any;
|
||||
/**
|
||||
* 全量 17 个成就(含未解锁)
|
||||
*/
|
||||
achievements: Array<AchievementItem>;
|
||||
|
||||
/**
|
||||
* @param achievements Array<AchievementItem> 全量 17 个成就(含未解锁)
|
||||
*/
|
||||
constructor(achievements: Array<AchievementItem>,);
|
||||
/**
|
||||
* 从对象创建 GetAchievementListResp
|
||||
*
|
||||
* @param o Object
|
||||
* - achievements: Array<AchievementItem>, // 全量 17 个成就(含未解锁)
|
||||
*/
|
||||
static fromObject(o: Object): GetAchievementListResp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 朋友圈动态项
|
||||
*/
|
||||
@@ -5152,6 +5321,14 @@ export default class HaveADrink {
|
||||
*/
|
||||
public GetAchievements(req: GetAchievementsReq) : Promise<GetAchievementsResp>;
|
||||
|
||||
/**
|
||||
* 获取用户所有成就列表。需登录认证,返回全量17项及解锁进度
|
||||
*
|
||||
* @param req GetAchievementListReq 空请求(用于无参接口)
|
||||
* @return GetAchievementListResp 获取成就列表响应
|
||||
*/
|
||||
public GetAchievementList(req: GetAchievementListReq) : Promise<GetAchievementListResp>;
|
||||
|
||||
/**
|
||||
* 获取朋友圈动态
|
||||
*
|
||||
@@ -5254,6 +5431,7 @@ export default class HaveADrink {
|
||||
/**
|
||||
* 更新用户偏好
|
||||
*
|
||||
* @deprecated 未使用
|
||||
* @param req UpdatePreferencesReq 更新用户偏好请求
|
||||
* @return UpdatePreferencesResp 更新用户偏好响应
|
||||
*/
|
||||
|
||||
+158
@@ -76,6 +76,51 @@ export class AchievementConditionType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 成就条件类型
|
||||
*/
|
||||
export class ConditionType {
|
||||
/**
|
||||
* 总打卡次数
|
||||
*/
|
||||
static TotalRecords = "total_records";
|
||||
/**
|
||||
* 连续打卡天数
|
||||
*/
|
||||
static StreakDays = "streak_days";
|
||||
/**
|
||||
* 累计标准杯数
|
||||
*/
|
||||
static TotalStandardCups = "total_standard_cups";
|
||||
/**
|
||||
* 尝试过的酒类品种数
|
||||
*/
|
||||
static CategoryVariety = "category_variety";
|
||||
/**
|
||||
* 某酒类打卡次数(需配合 category 字段)
|
||||
*/
|
||||
static CategoryRecords = "category_records";
|
||||
|
||||
static entries() {
|
||||
return [
|
||||
{ value: ConditionType.TotalRecords, label: '总打卡次数' },
|
||||
{ value: ConditionType.StreakDays, label: '连续打卡天数' },
|
||||
{ value: ConditionType.TotalStandardCups, label: '累计标准杯数' },
|
||||
{ value: ConditionType.CategoryVariety, label: '尝试过的酒类品种数' },
|
||||
{ value: ConditionType.CategoryRecords, label: '某酒类打卡次数(需配合 category 字段)' },
|
||||
];
|
||||
}
|
||||
static getLabel(v) {
|
||||
switch(v) {
|
||||
case ConditionType.TotalRecords: return '总打卡次数';
|
||||
case ConditionType.StreakDays: return '连续打卡天数';
|
||||
case ConditionType.TotalStandardCups: return '累计标准杯数';
|
||||
case ConditionType.CategoryVariety: return '尝试过的酒类品种数';
|
||||
case ConditionType.CategoryRecords: return '某酒类打卡次数(需配合 category 字段)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 酒类分类
|
||||
*/
|
||||
@@ -936,6 +981,86 @@ export class AchievementsData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 空请求(用于无参接口)
|
||||
*/
|
||||
export class GetAchievementListReq {
|
||||
/**
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
}
|
||||
static fromObject(o) {
|
||||
return new GetAchievementListReq();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 成就条件详情
|
||||
*/
|
||||
export class Condition {
|
||||
/**
|
||||
* @param type: ConditionType 条件类型
|
||||
* @param value: number 目标阈值
|
||||
* @param category,omitempty: string 酒类分类(仅当 Type = category_records 时有效)
|
||||
*/
|
||||
constructor(type,value,category,omitempty,) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
this.category,omitempty = category,omitempty;
|
||||
|
||||
}
|
||||
static fromObject(o) {
|
||||
return new Condition(o.type,o.value,o.category,omitempty,);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个成就项
|
||||
*/
|
||||
export class AchievementItem {
|
||||
/**
|
||||
* @param id: string 成就唯一标识(如 first_checkin)
|
||||
* @param name: string 成就名称
|
||||
* @param desc: string 成就描述
|
||||
* @param unlocked: boolean 是否已解锁
|
||||
* @param unlockedAt: string 解锁时间(ISO 8601),未解锁时为 null
|
||||
* @param condition: Condition 解锁条件
|
||||
* @param progress: number 进度 (0~1)
|
||||
* @param icon,omitempty: string 图标资源(前端忽略该字段,后端可省略)
|
||||
*/
|
||||
constructor(id,name,desc,unlocked,unlockedAt,condition,progress,icon,omitempty,) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
this.unlocked = unlocked;
|
||||
this.unlockedAt = unlockedAt;
|
||||
this.condition = condition;
|
||||
this.progress = progress;
|
||||
this.icon,omitempty = icon,omitempty;
|
||||
|
||||
}
|
||||
static fromObject(o) {
|
||||
return new AchievementItem(o.id,o.name,o.desc,o.unlocked,o.unlockedAt,o.condition,o.progress,o.icon,omitempty,);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成就列表响应
|
||||
*/
|
||||
export class GetAchievementListResp {
|
||||
/**
|
||||
* @param achievements: Array<AchievementItem> 全量 17 个成就(含未解锁)
|
||||
*/
|
||||
constructor(achievements,) {
|
||||
this.achievements = achievements;
|
||||
|
||||
}
|
||||
static fromObject(o) {
|
||||
return new GetAchievementListResp(o.achievements,);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 朋友圈动态项
|
||||
*/
|
||||
@@ -3736,6 +3861,39 @@ export default class HaveADrink {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有成就列表。需登录认证,返回全量17项及解锁进度
|
||||
*/
|
||||
GetAchievementList(req) {
|
||||
return new Promise((reslove, reject)=>{
|
||||
let data = req;
|
||||
let url = `${this.host}/api/have_a_drink/v1/achievements/achievements`;
|
||||
const query = Object.keys(data).map((x)=>(`${x}=${data[x]}`)).join('&');
|
||||
|
||||
this.http_request(url, {
|
||||
uri: '/api/have_a_drink/v1/achievements/achievements',
|
||||
method: 'GET',
|
||||
query: query,
|
||||
data: data,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'X-Token': this.token,
|
||||
},
|
||||
}).then((data)=>{
|
||||
if (data.hasOwnProperty("fail")) {
|
||||
if (data.fail) {
|
||||
reject(data.msg);
|
||||
} else {
|
||||
reslove(data.data);
|
||||
}
|
||||
} else {
|
||||
reslove(data);
|
||||
}
|
||||
}).catch((err)=>reject(err));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取朋友圈动态
|
||||
*/
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "HaveADrink",
|
||||
"type": "module",
|
||||
"version": "v1.0.16",
|
||||
"version": "v1.0.17",
|
||||
"description": "喝酒了么 API 服务",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
Generated
+4
-4
@@ -5,13 +5,13 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"HaveADrink": "^1.0.16"
|
||||
"HaveADrink": "^1.0.17"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"HaveADrink": "^1.0.16"
|
||||
"HaveADrink": "^1.0.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
|
||||
<script>
|
||||
import AchievementBadge from '../../components/AchievementBadge.vue'
|
||||
import client, { clearAuth } from '../../common/api'
|
||||
import client, { clearAuth, GetAchievements } from '../../common/api'
|
||||
import { checkAchievements, getCatIcon, isIconPath, calcLocalStreak, isValidAvatar } from '../../common/utils'
|
||||
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
|
||||
import themeMixin from '../../common/theme-mixin'
|
||||
@@ -290,7 +290,7 @@ export default {
|
||||
const [userResp, statsResp, achieveResp] = await Promise.allSettled([
|
||||
client.GetUserProfile({}),
|
||||
client.GetStatsOverview({}),
|
||||
client.GetAchievements({})
|
||||
GetAchievements()
|
||||
])
|
||||
|
||||
// 用户信息
|
||||
@@ -332,8 +332,8 @@ export default {
|
||||
id: a.achievement_id || a.id,
|
||||
name: a.name,
|
||||
desc: a.desc,
|
||||
// 后端返回的 icon 是不存在的图片路径,按顺序复用本地 emoji 图标
|
||||
icon: this.resolveAchievementIcon(i),
|
||||
// 后端返回的 icon 是不存在的图片路径,优先按成就 id 匹配本地 emoji 图标
|
||||
icon: this.resolveAchievementIcon(a.achievement_id || a.id, i),
|
||||
condition: a.condition || '',
|
||||
unlocked: !!a.unlocked,
|
||||
progress: a.progress || 0
|
||||
@@ -373,8 +373,11 @@ export default {
|
||||
console.warn('本地计算连续天数失败:', e)
|
||||
}
|
||||
},
|
||||
// 后端返回的 icon 是无效图片路径,按索引复用本地成就的 emoji 图标(超出后循环取)
|
||||
resolveAchievementIcon(index) {
|
||||
// 后端返回的 icon 是无效图片路径,优先按成就 id 匹配本地 ACHIEVEMENTS 的 emoji 图标,
|
||||
// 匹配不到(后端新增成就)时再按索引循环兜底
|
||||
resolveAchievementIcon(id, index) {
|
||||
const local = ACHIEVEMENTS.find(a => a.id === id)
|
||||
if (local) return local.icon
|
||||
if (!ACHIEVEMENTS.length) return '🏅'
|
||||
return ACHIEVEMENTS[index % ACHIEVEMENTS.length].icon
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user