Files
hejiu/common/api.js
T
cg 18e7b4fd50 feat(api): 添加成就系统并升级SDK
- 升级 HaveADrink SDK 从 1.0.16 到 1.0.17 版本
- 新增 GetAchievements API 接口及相关数据类型定义
- 添加 17 个成就定义,包括打卡次数、连续打卡、标准杯累计、酒类探索等类别
- 实现成就解锁逻辑计算,支持 category_records 类型成就判断
- 创建成就徽章对接文档,定义接口格式和存储建议
- 在用户资料页面集成成就展示功能,优化图标匹配逻辑
2026-08-17 23:07:23 +08:00

372 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 碰盏日记 - API 服务层
* 基于 HaveADrink SDK 封装后端接口
*/
import HaveADrink, { GetAchievementsReq } from 'HaveADrink'
import UniAppWebSocket from './uni-websocket'
// ==========================================
// 后端服务地址(切换环境只需改这里)
// ==========================================
export const API_HOST = 'https://dev.wash-painting.cn'
// ==========================================
// 401 跳登录页防重锁:token 失效时多个接口会同时返回 401,
// 各自 reLaunch 会并发冲突导致 reLaunch:fail timeout,只放行第一次
// ==========================================
let authRedirectLock = false
function redirectToLogin() {
if (authRedirectLock) return
authRedirectLock = true
setTimeout(() => { authRedirectLock = false }, 3000)
const pages = getCurrentPages()
const current = pages.length ? pages[pages.length - 1].route : ''
// 已在登录页则不重复跳转
if (current === 'pages/login/login') return
uni.reLaunch({ url: '/pages/login/login', fail: () => {} })
}
// ==========================================
// HTTP 请求函数(供 SDK 内部调用)
// 参考 goodBooth 项目的 fetchsomething
// ==========================================
function httpRequest(url, params) {
// 支持静默模式(如后台刷新token),不弹 toast 不跳转
const silent = params.silent === true
return new Promise((resolve, reject) => {
// 自动注入 Authorization token
const headers = Object.assign({}, params.headers || {})
const token = uni.getStorageSync('auth_token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
uni.request({
url: url,
method: params.method,
params: params.query || undefined,
data: params.data,
header: headers,
success: (res) => {
const code = res.statusCode
if (code === 200 || code === 201 || code === 204) {
// 业务级错误:HTTP 200 但 fail:true
if (res.data && res.data.fail === true) {
const bizMsg = res.data.msg || '操作失败'
if (!silent) {
uni.showToast({ title: bizMsg, icon: 'none', duration: 2500 })
}
reject({ msg: bizMsg, code })
return
}
resolve(res.data)
} else if (code === 401 ) {
// token/refresh_token 过期或无效,清除登录态
uni.removeStorageSync('auth_token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('is_logged_in')
if (!silent) {
redirectToLogin()
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none', duration: 2500 })
}
reject({ msg: '登录已过期,请重新登录', code })
} else {
const rawMsg = (res.data && res.data.msg) || (res.data && res.data.errmsg) || '请求失败'
const errMsg = `[${code}] ${rawMsg}`
if (!silent) {
uni.showToast({ title: errMsg, icon: 'none', duration: 2500 })
}
reject({ msg: errMsg, code })
}
},
fail: (err) => {
const msg = (err && err.errMsg) || '网络请求失败'
if (!silent) {
uni.showToast({ title: msg, icon: 'none', duration: 2500 })
}
reject({ msg })
}
})
})
}
// ==========================================
// 文件上传函数(供 SDK 内部调用)
// ==========================================
function uploadRequest(url, params) {
return new Promise((resolve, reject) => {
const headers = Object.assign({}, params.headers || {})
const token = uni.getStorageSync('auth_token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
uni.uploadFile({
url: url,
filePath: params.data.image || params.data.fileName || params.data.filePath,
name: params.data.name || 'image',
header: headers,
formData: params.data.formData || {},
success: (uploadRes) => {
try {
const data = typeof uploadRes.data === 'string'
? JSON.parse(uploadRes.data)
: uploadRes.data
resolve(data)
} catch (e) {
resolve(uploadRes.data)
}
},
fail: (err) => {
reject({ msg: err.errMsg || '上传失败' })
}
})
})
}
// ==========================================
// 初始化 SDK 实例
// (WebSocket 适配器使用 common/uni-websocket.js,已修复 wx.connectSocket 降级问题)
// ==========================================
const client = new HaveADrink({
host: API_HOST,
http_request: httpRequest,
upload: uploadRequest,
websocket: UniAppWebSocket,
token: uni.getStorageSync('auth_token') || ''
})
// ==========================================
// 导出 SDK 实例(各页面直接 import 使用)
// ==========================================
export default client
// ==========================================
// Token 管理辅助函数
// ==========================================
/** 登录成功后保存 token 并同步到 SDK */
export function saveAuthTokens(data) {
if (data.token) {
uni.setStorageSync('auth_token', data.token)
client.setToken(data.token)
}
if (data.refresh_token) {
uni.setStorageSync('refresh_token', data.refresh_token)
}
}
/** 清除所有认证信息 */
export function clearAuth() {
uni.removeStorageSync('auth_token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('is_logged_in')
uni.removeStorageSync('is_guest')
uni.removeStorageSync('user_info')
client.setToken('')
}
/** 尝试用 refresh_token 刷新 token(静默,不弹提示不跳转) */
export async function refreshAuthToken() {
const refreshToken = uni.getStorageSync('refresh_token')
if (!refreshToken) return false
try {
const res = await client.RefreshToken({ refresh_token: refreshToken, silent: true })
if (res && res.token) {
saveAuthTokens(res)
return true
}
return false
} catch (e) {
// 静默失败:refresh token 无效时清除残留凭证
uni.removeStorageSync('refresh_token')
return false
}
}
// ==========================================
// 酒友圈 API(基于 HaveADrink SDK 真实接口)
// ==========================================
/** 获取动态信息流(游标分页) */
export async function GetCircleFeeds({ lastId = 0, pageSize = 10 } = {}) {
const res = await client.GetFeeds({ lastId, pageSize })
return { data: res }
}
/** 点赞动态 */
export async function LikeFeed({ feedId }) {
const res = await client.LikeFeed({ id: feedId })
return { data: res }
}
/** 取消点赞 */
export async function UnlikeFeed({ feedId }) {
const res = await client.UnlikeFeed({ id: feedId })
return { data: res }
}
/** 删除动态(仅本人可删,后端会校验归属) */
export async function DeleteFeed({ feedId }) {
const res = await client.DeleteFeed({ id: feedId })
return { data: res }
}
/** 获取动态评论 */
export async function GetFeedComments({ feedId }) {
const res = await client.GetFeedComments({ id: feedId })
return { data: res }
}
/** 发表评论 */
export async function AddComment({ feedId, content }) {
const res = await client.AddComment({ id: feedId, content })
return { data: res }
}
/** 发布动态 */
export async function PublishFeed({ text, images, recordId, visibility }) {
const res = await client.PublishFeed({ text, images, recordId, visibility })
return { data: res }
}
/** 获取酒友列表 */
export async function GetFriends({ keyword = '' } = {}) {
const res = await client.GetFriends({ keyword })
return { data: res }
}
/** 获取好友请求(支持关键词搜索) */
export async function GetFriendRequests({ keyword = '' } = {}) {
const res = await client.GetFriendRequests({ keyword })
return { data: res }
}
/** 发送好友请求(创建邀请,SDK v1.0.12:无需 userId,响应返回 inviteCode 邀请码) */
export async function SendFriendRequest({ message = '' } = {}) {
const res = await client.SendFriendRequest({ message })
return { data: res }
}
/** 接受好友请求(id 为好友请求ID,即邀请码 inviteCode) */
export async function AcceptFriendRequest({ requestId }) {
const res = await client.AcceptFriendRequest({ id: requestId })
return { data: res }
}
/** 删除酒友 */
export async function RemoveFriend({ userId }) {
const res = await client.RemoveFriend({ userId })
return { data: res }
}
/** 获取酒局列表 */
export async function GetEvents({ status = '' } = {}) {
const res = await client.GetEvents({ status })
return { data: res }
}
/** 获取酒局详情 */
export async function GetEventDetail({ eventId }) {
const res = await client.GetEventDetail({ id: eventId })
return { data: res }
}
/** 发起酒局 */
export async function CreateEvent({ title, time, location, maxPeople, note, latitude, longitude }) {
const geo = (latitude != null && longitude != null) ? { latitude, longitude } : undefined
const res = await client.CreateEvent({ title, time, location, maxPeople, geo, note })
return { data: res }
}
/** 修改酒局(仅发起人可改,后端会校验归属) */
export async function UpdateEvent({ eventId, title, time, location, maxPeople, note, latitude, longitude }) {
const geo = (latitude != null && longitude != null) ? { latitude, longitude } : undefined
const res = await client.UpdateEvent({ id: eventId, title, time, location, maxPeople, geo, note })
return { data: res }
}
/** 删除酒局(仅发起人可删,后端会校验归属) */
export async function DeleteEvent({ eventId }) {
const res = await client.DeleteEvent({ id: eventId })
return { data: res }
}
/** 报名酒局 */
export async function JoinEvent({ eventId }) {
const res = await client.JoinEvent({ id: eventId })
return { data: res }
}
/** 取消报名 */
export async function QuitEvent({ eventId }) {
const res = await client.QuitEvent({ id: eventId })
return { data: res }
}
/** 酒局签到 */
export async function CheckInEvent({ eventId }) {
const res = await client.CheckInEvent({ id: eventId })
return { data: res }
}
/** 审核报名(仅发起人可操作;approve: 1=待审核 2=通过 3=拒绝) */
export async function ApproveEvent({ eventId, userId, approve }) {
const res = await client.ApproveEvent({ id: eventId, userId, approve })
return { data: res }
}
// ==========================================
// 私信 API(基于 HaveADrink SDK 真实接口)
// ==========================================
/** 获取会话列表 */
export async function GetConversations() {
const res = await client.GetConversations({})
return { data: res }
}
/** 获取历史消息(游标分页) */
export async function GetMessages({ conversationId, lastID = 0, pageSize = 20 } = {}) {
const res = await client.GetMessages({ conversationId, lastID, pageSize })
return { data: res }
}
/** 标记会话已读 */
export async function MarkRead({ conversationId }) {
const res = await client.MarkRead({ id: conversationId })
return { data: res }
}
/** 获取未读消息总数 */
export async function GetUnreadCount() {
const res = await client.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
// ==========================================
/** 上传图片(返回 url) */
export async function UploadImage({ filePath }) {
const res = await client.UploadImage({ image: filePath })
return { data: res }
}
/** 更新当前用户头像(avatar 必须是上传后的 http(s) 远程地址,需 token) */
export async function UpdateUserAvatar({ avatar }) {
const res = await client.UpdateUserAvatar({ avatar })
return { data: res }
}