feat(api): 升级 HaveADrink 依赖并实现用户头像上传功能
- 将 HaveADrink 依赖从 1.0.15 升级至 1.0.16 版本 - 新增 UpdateUserAvatar API 接口用于更新用户头像 - 添加 isValidAvatar 工具函数验证头像 URL 有效性 - 在登录流程中集成头像上传逻辑,支持微信头像选择 - 更新多个组件中的头像显示逻辑,过滤无效头像地址 - 优化 tabBar 主题应用逻辑,修复异步错误处理问题 - 修复微信新规范下头像选择的临时路径处理机制
This commit is contained in:
@@ -336,3 +336,9 @@ export async function UploadImage({ filePath }) {
|
|||||||
const res = await client.UploadImage({ image: filePath })
|
const res = await client.UploadImage({ image: filePath })
|
||||||
return { data: res }
|
return { data: res }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 更新当前用户头像(avatar 必须是上传后的 http(s) 远程地址,需 token) */
|
||||||
|
export async function UpdateUserAvatar({ avatar }) {
|
||||||
|
const res = await client.UpdateUserAvatar({ avatar })
|
||||||
|
return { data: res }
|
||||||
|
}
|
||||||
|
|||||||
+32
-2
@@ -15,6 +15,15 @@ export function isIconPath(val) {
|
|||||||
return typeof val === 'string' && val.charAt(0) === '/'
|
return typeof val === 'string' && val.charAt(0) === '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断头像地址是否可用
|
||||||
|
* wxfile://tmp_ 等本地临时路径仅本机当次有效,不能用于展示他人头像,
|
||||||
|
* 展示层对此类地址应回退为占位头像
|
||||||
|
*/
|
||||||
|
export function isValidAvatar(url) {
|
||||||
|
return typeof url === 'string' && /^https?:\/\//.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标准杯换算引擎
|
* 标准杯换算引擎
|
||||||
* 核心公式: 标准杯 = (饮用量ml × 酒精度数% × 0.8) / 10
|
* 核心公式: 标准杯 = (饮用量ml × 酒精度数% × 0.8) / 10
|
||||||
@@ -303,7 +312,25 @@ export function applyNavBarTheme(theme) {
|
|||||||
|
|
||||||
export function applyTabBarTheme(theme) {
|
export function applyTabBarTheme(theme) {
|
||||||
// 仅在 tabBar 页面上调用 setTabBarStyle,避免报错
|
// 仅在 tabBar 页面上调用 setTabBarStyle,避免报错
|
||||||
const tabBarPages = ['pages/index/index', 'pages/profile/profile']
|
// 页面清单直接从 pages.json 读取,新增 tab 页无需改这里
|
||||||
|
let tabBarPages = []
|
||||||
|
try {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
tabBarPages = (typeof __wxConfig !== 'undefined' && __wxConfig.tabBar && __wxConfig.tabBar.list)
|
||||||
|
? __wxConfig.tabBar.list.map(t => t.pagePath)
|
||||||
|
: []
|
||||||
|
// #endif
|
||||||
|
if (!tabBarPages.length && typeof require === 'function') {
|
||||||
|
const cfg = require('../pages.json')
|
||||||
|
tabBarPages = (cfg.tabBar && cfg.tabBar.list) ? cfg.tabBar.list.map(t => t.pagePath) : []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
tabBarPages = []
|
||||||
|
}
|
||||||
|
// 两种方式都取不到清单时兜底,保证主题正常应用
|
||||||
|
if (!tabBarPages.length) {
|
||||||
|
tabBarPages = ['pages/index/index', 'pages/circle/circle', 'pages/profile/profile']
|
||||||
|
}
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
if (!pages.length) return
|
if (!pages.length) return
|
||||||
const currentPath = pages[pages.length - 1].route
|
const currentPath = pages[pages.length - 1].route
|
||||||
@@ -329,7 +356,10 @@ export function applyTabBarTheme(theme) {
|
|||||||
color: s.color,
|
color: s.color,
|
||||||
selectedColor: s.selectedColor,
|
selectedColor: s.selectedColor,
|
||||||
backgroundColor: s.backgroundColor,
|
backgroundColor: s.backgroundColor,
|
||||||
borderStyle: s.borderStyle
|
borderStyle: s.borderStyle,
|
||||||
|
// 必须传 fail 回调吞错:不传时 uni API 返回 Promise,
|
||||||
|
// 异步 reject 无法被 try/catch 捕获,会产生 UnhandledPromiseRejection
|
||||||
|
fail: () => {}
|
||||||
})
|
})
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="comment-item">
|
<view class="comment-item">
|
||||||
<view class="comment-avatar-wrap">
|
<view class="comment-avatar-wrap">
|
||||||
<image v-if="comment.avatar" class="comment-avatar" :src="comment.avatar" mode="aspectFill"></image>
|
<image v-if="isValidAvatar(comment.avatar)" class="comment-avatar" :src="comment.avatar" mode="aspectFill"></image>
|
||||||
<view v-else class="comment-avatar comment-avatar-ph">
|
<view v-else class="comment-avatar comment-avatar-ph">
|
||||||
<text class="comment-avatar-text">{{ comment.nickname ? comment.nickname[0] : '酒' }}</text>
|
<text class="comment-avatar-text">{{ comment.nickname ? comment.nickname[0] : '酒' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -17,10 +17,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { isValidAvatar } from '../common/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CommentItem',
|
name: 'CommentItem',
|
||||||
props: {
|
props: {
|
||||||
comment: { type: Object, default: () => ({}) }
|
comment: { type: Object, default: () => ({}) }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isValidAvatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- 头部:头像+昵称+时间 -->
|
<!-- 头部:头像+昵称+时间 -->
|
||||||
<view class="feed-header">
|
<view class="feed-header">
|
||||||
<view class="feed-avatar-wrap">
|
<view class="feed-avatar-wrap">
|
||||||
<image v-if="feed.avatar" class="feed-avatar" :src="feed.avatar" mode="aspectFill"></image>
|
<image v-if="isValidAvatar(feed.avatar)" class="feed-avatar" :src="feed.avatar" mode="aspectFill"></image>
|
||||||
<view v-else class="feed-avatar feed-avatar-ph">
|
<view v-else class="feed-avatar feed-avatar-ph">
|
||||||
<text class="feed-avatar-text">{{ feed.nickname ? feed.nickname[0] : '酒' }}</text>
|
<text class="feed-avatar-text">{{ feed.nickname ? feed.nickname[0] : '酒' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getCatIcon, isIconPath } from '../common/utils'
|
import { getCatIcon, isIconPath, isValidAvatar } from '../common/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FeedCard',
|
name: 'FeedCard',
|
||||||
@@ -92,6 +92,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
getCatIcon,
|
getCatIcon,
|
||||||
isIconPath,
|
isIconPath,
|
||||||
|
isValidAvatar,
|
||||||
/** 拉取图片真实尺寸,用于按比例计算展示高度 */
|
/** 拉取图片真实尺寸,用于按比例计算展示高度 */
|
||||||
loadImgMeta(list) {
|
loadImgMeta(list) {
|
||||||
if (!list || !list.length) return
|
if (!list || !list.length) return
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="friend-item" @click="$emit('item-click', friend)">
|
<view class="friend-item" @click="$emit('item-click', friend)">
|
||||||
<view class="friend-avatar-wrap">
|
<view class="friend-avatar-wrap">
|
||||||
<image v-if="friend.avatar" class="friend-avatar" :src="friend.avatar" mode="aspectFill"></image>
|
<image v-if="isValidAvatar(friend.avatar)" class="friend-avatar" :src="friend.avatar" mode="aspectFill"></image>
|
||||||
<view v-else class="friend-avatar friend-avatar-ph">
|
<view v-else class="friend-avatar friend-avatar-ph">
|
||||||
<text class="friend-avatar-text">{{ friend.nickname ? friend.nickname[0] : '酒' }}</text>
|
<text class="friend-avatar-text">{{ friend.nickname ? friend.nickname[0] : '酒' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -16,12 +16,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { isValidAvatar } from '../common/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FriendItem',
|
name: 'FriendItem',
|
||||||
props: {
|
props: {
|
||||||
friend: { type: Object, default: () => ({}) }
|
friend: { type: Object, default: () => ({}) }
|
||||||
},
|
},
|
||||||
emits: ['item-click']
|
emits: ['item-click'],
|
||||||
|
methods: {
|
||||||
|
isValidAvatar
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -4,9 +4,9 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"node_modules/HaveADrink": {
|
"node_modules/HaveADrink": {
|
||||||
"version": "1.0.15",
|
"version": "1.0.16",
|
||||||
"resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.15.tgz",
|
"resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.16.tgz",
|
||||||
"integrity": "sha512-ZKgrIKuwXXuKRY2D34FRNUwlr1Y4bAUtXwrjpoHwLPa30KNGVvuXhfYvxAWf5cDvTJw3b/lm8JSSYwCqcIZ3Bw==",
|
"integrity": "sha512-NKaV3AZHHVYT23Eo4WGTzZ7H+FVfQoHa8EE3k5SnHCi9yxGAxnExBqRYde5AEFI1pJNe7OzqzbxAykxN757LXQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-1
@@ -14,7 +14,7 @@
|
|||||||
* 6. 社交模块 - 朋友圈动态、点赞
|
* 6. 社交模块 - 朋友圈动态、点赞
|
||||||
|
|
||||||
|
|
||||||
**版本:** v1.0.15
|
**版本:** v1.0.16
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@@ -355,6 +355,7 @@ client.GetFeed(req).then(...).catch(...)
|
|||||||
### 上传照片
|
### 上传照片
|
||||||
|
|
||||||
|
|
||||||
|
**已废弃:** 请使用 upload.api 上传照片
|
||||||
|
|
||||||
<font color="green">POST</font> `/api/have_a_drink/v1/photo/upload/photo`
|
<font color="green">POST</font> `/api/have_a_drink/v1/photo/upload/photo`
|
||||||
|
|
||||||
@@ -382,6 +383,7 @@ client.UploadPhoto(req).then(...).catch(...)
|
|||||||
### 批量上传照片
|
### 批量上传照片
|
||||||
|
|
||||||
|
|
||||||
|
**已废弃:** 请使用 upload.api 批量上传照片
|
||||||
|
|
||||||
<font color="green">POST</font> `/api/have_a_drink/v1/photo/upload/photos`
|
<font color="green">POST</font> `/api/have_a_drink/v1/photo/upload/photos`
|
||||||
|
|
||||||
@@ -1144,6 +1146,33 @@ const req = new GetUserBasicInfoReq()
|
|||||||
client.GetUserBasicInfo(req).then(...).catch(...)
|
client.GetUserBasicInfo(req).then(...).catch(...)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 更新用户头像
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<font color="green">PUT</font> `/api/have_a_drink/v1/user/user/avatar`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
|名称|类型|校验规则|说明|
|
||||||
|
|:-|:-|:-|:-|
|
||||||
|
|avatar|`string`|| 头像URL|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 返回值
|
||||||
|
|名称|类型|说明|
|
||||||
|
|:-|:-:|:-|
|
||||||
|
|ok|`boolean`| 是否成功<br>|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const req = new UpdateUserAvatarReq()
|
||||||
|
client.UpdateUserAvatar(req).then(...).catch(...)
|
||||||
|
```
|
||||||
|
|
||||||
### 上传文件
|
### 上传文件
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+56
@@ -2573,6 +2573,52 @@ export declare class GetUserBasicInfoResp {
|
|||||||
static fromObject(o: Object): GetUserBasicInfoResp;
|
static fromObject(o: Object): GetUserBasicInfoResp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export declare class UpdateUserAvatarReq {
|
||||||
|
[key: string]: any;
|
||||||
|
/**
|
||||||
|
* 头像URL
|
||||||
|
*/
|
||||||
|
avatar: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param avatar string 头像URL
|
||||||
|
*/
|
||||||
|
constructor(avatar: string,);
|
||||||
|
/**
|
||||||
|
* 从对象创建 UpdateUserAvatarReq
|
||||||
|
*
|
||||||
|
* @param o Object
|
||||||
|
* - avatar: string, // 头像URL
|
||||||
|
*/
|
||||||
|
static fromObject(o: Object): UpdateUserAvatarReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export declare class UpdateUserAvatarResp {
|
||||||
|
[key: string]: any;
|
||||||
|
/**
|
||||||
|
* 是否成功
|
||||||
|
*/
|
||||||
|
ok: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ok boolean 是否成功
|
||||||
|
*/
|
||||||
|
constructor(ok: boolean,);
|
||||||
|
/**
|
||||||
|
* 从对象创建 UpdateUserAvatarResp
|
||||||
|
*
|
||||||
|
* @param o Object
|
||||||
|
* - ok: boolean, // 是否成功
|
||||||
|
*/
|
||||||
|
static fromObject(o: Object): UpdateUserAvatarResp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传图片参数
|
* 上传图片参数
|
||||||
*/
|
*/
|
||||||
@@ -5118,6 +5164,7 @@ export default class HaveADrink {
|
|||||||
/**
|
/**
|
||||||
* 上传照片
|
* 上传照片
|
||||||
*
|
*
|
||||||
|
* @deprecated 请使用 upload.api 上传照片
|
||||||
* @param req UploadPhotoReq 上传照片请求
|
* @param req UploadPhotoReq 上传照片请求
|
||||||
* @return UploadPhotoResp 上传照片响应
|
* @return UploadPhotoResp 上传照片响应
|
||||||
*/
|
*/
|
||||||
@@ -5126,6 +5173,7 @@ export default class HaveADrink {
|
|||||||
/**
|
/**
|
||||||
* 批量上传照片
|
* 批量上传照片
|
||||||
*
|
*
|
||||||
|
* @deprecated 请使用 upload.api 批量上传照片
|
||||||
* @param req UploadPhotosReq 批量上传照片请求
|
* @param req UploadPhotosReq 批量上传照片请求
|
||||||
* @return UploadPhotosResp 批量上传照片响应
|
* @return UploadPhotosResp 批量上传照片响应
|
||||||
*/
|
*/
|
||||||
@@ -5219,6 +5267,14 @@ export default class HaveADrink {
|
|||||||
*/
|
*/
|
||||||
public GetUserBasicInfo(req: GetUserBasicInfoReq) : Promise<GetUserBasicInfoResp>;
|
public GetUserBasicInfo(req: GetUserBasicInfoReq) : Promise<GetUserBasicInfoResp>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户头像
|
||||||
|
*
|
||||||
|
* @param req UpdateUserAvatarReq
|
||||||
|
* @return UpdateUserAvatarResp
|
||||||
|
*/
|
||||||
|
public UpdateUserAvatar(req: UpdateUserAvatarReq) : Promise<UpdateUserAvatarResp>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传文件
|
* 上传文件
|
||||||
*
|
*
|
||||||
|
|||||||
+64
@@ -1864,6 +1864,38 @@ export class GetUserBasicInfoResp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class UpdateUserAvatarReq {
|
||||||
|
/**
|
||||||
|
* @param avatar: string 头像URL
|
||||||
|
*/
|
||||||
|
constructor(avatar,) {
|
||||||
|
this.avatar = avatar;
|
||||||
|
|
||||||
|
}
|
||||||
|
static fromObject(o) {
|
||||||
|
return new UpdateUserAvatarReq(o.avatar,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class UpdateUserAvatarResp {
|
||||||
|
/**
|
||||||
|
* @param ok: boolean 是否成功
|
||||||
|
*/
|
||||||
|
constructor(ok,) {
|
||||||
|
this.ok = ok;
|
||||||
|
|
||||||
|
}
|
||||||
|
static fromObject(o) {
|
||||||
|
return new UpdateUserAvatarResp(o.ok,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传图片参数
|
* 上传图片参数
|
||||||
*/
|
*/
|
||||||
@@ -4152,6 +4184,38 @@ export default class HaveADrink {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户头像
|
||||||
|
*/
|
||||||
|
UpdateUserAvatar(req) {
|
||||||
|
return new Promise((reslove, reject)=>{
|
||||||
|
let data = req;
|
||||||
|
let url = `${this.host}/api/have_a_drink/v1/user/user/avatar`;
|
||||||
|
|
||||||
|
|
||||||
|
this.http_request(url, {
|
||||||
|
uri: '/api/have_a_drink/v1/user/user/avatar',
|
||||||
|
method: 'PUT',
|
||||||
|
data: data,
|
||||||
|
responseType: 'json',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}).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",
|
"name": "HaveADrink",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "v1.0.15",
|
"version": "v1.0.16",
|
||||||
"description": "喝酒了么 API 服务",
|
"description": "喝酒了么 API 服务",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Generated
+4
-4
@@ -5,13 +5,13 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HaveADrink": "^1.0.15"
|
"HaveADrink": "^1.0.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/HaveADrink": {
|
"node_modules/HaveADrink": {
|
||||||
"version": "1.0.15",
|
"version": "1.0.16",
|
||||||
"resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.15.tgz",
|
"resolved": "https://npm.wash-painting.cn/HaveADrink/-/HaveADrink-1.0.16.tgz",
|
||||||
"integrity": "sha512-ZKgrIKuwXXuKRY2D34FRNUwlr1Y4bAUtXwrjpoHwLPa30KNGVvuXhfYvxAWf5cDvTJw3b/lm8JSSYwCqcIZ3Bw==",
|
"integrity": "sha512-NKaV3AZHHVYT23Eo4WGTzZ7H+FVfQoHa8EE3k5SnHCi9yxGAxnExBqRYde5AEFI1pJNe7OzqzbxAykxN757LXQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"HaveADrink": "^1.0.15"
|
"HaveADrink": "^1.0.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
>
|
>
|
||||||
<!-- 头像 -->
|
<!-- 头像 -->
|
||||||
<view class="conv-avatar-wrap">
|
<view class="conv-avatar-wrap">
|
||||||
<image v-if="conv.avatar" class="conv-avatar" :src="conv.avatar" mode="aspectFill"></image>
|
<image v-if="isValidAvatar(conv.avatar)" class="conv-avatar" :src="conv.avatar" mode="aspectFill"></image>
|
||||||
<view v-else class="conv-avatar conv-avatar-ph">
|
<view v-else class="conv-avatar conv-avatar-ph">
|
||||||
<text class="conv-avatar-text">{{ conv.nickname ? conv.nickname[0] : '酒' }}</text>
|
<text class="conv-avatar-text">{{ conv.nickname ? conv.nickname[0] : '酒' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -69,6 +69,7 @@ import EmptyState from '../../components/EmptyState.vue'
|
|||||||
import { GetConversations } from '../../common/api'
|
import { GetConversations } from '../../common/api'
|
||||||
import wsManager from '../../common/websocket'
|
import wsManager from '../../common/websocket'
|
||||||
import themeMixin from '../../common/theme-mixin'
|
import themeMixin from '../../common/theme-mixin'
|
||||||
|
import { isValidAvatar } from '../../common/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [themeMixin],
|
mixins: [themeMixin],
|
||||||
@@ -93,6 +94,7 @@ export default {
|
|||||||
this.unbindWsEvents()
|
this.unbindWsEvents()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isValidAvatar,
|
||||||
async loadConversations() {
|
async loadConversations() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
+49
-18
@@ -90,7 +90,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import themeMixin from '../../common/theme-mixin'
|
import themeMixin from '../../common/theme-mixin'
|
||||||
import client, { saveAuthTokens } from '../../common/api'
|
import client, { saveAuthTokens, UploadImage, UpdateUserAvatar } from '../../common/api'
|
||||||
import { handlePendingInvite } from '../../common/invite'
|
import { handlePendingInvite } from '../../common/invite'
|
||||||
import wsManager from '../../common/websocket'
|
import wsManager from '../../common/websocket'
|
||||||
|
|
||||||
@@ -102,6 +102,7 @@ export default {
|
|||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
agreed: false,
|
agreed: false,
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
|
avatarTempPath: '', // 待上传的 wxfile:// 临时路径(选头像时暂存,登录后带 token 上传)
|
||||||
nickname: ''
|
nickname: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,10 +124,14 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ===== 微信新规范:头像选择 =====
|
// ===== 微信新规范:头像选择 =====
|
||||||
|
// chooseAvatar 返回的是 wxfile://tmp_ 本地临时路径(微信随时会清理、仅本机有效),
|
||||||
|
// 上传接口需要登录 token,而选头像时还未登录,因此这里只暂存临时路径,
|
||||||
|
// 等 handleLogin 拿到 token 后再上传
|
||||||
onChooseAvatar(e) {
|
onChooseAvatar(e) {
|
||||||
if (e.detail && e.detail.avatarUrl) {
|
const tempUrl = e.detail && e.detail.avatarUrl
|
||||||
this.avatarUrl = e.detail.avatarUrl
|
if (!tempUrl) return
|
||||||
}
|
this.avatarUrl = tempUrl
|
||||||
|
this.avatarTempPath = tempUrl
|
||||||
},
|
},
|
||||||
|
|
||||||
// ===== 微信新规范:昵称输入 =====
|
// ===== 微信新规范:昵称输入 =====
|
||||||
@@ -157,30 +162,56 @@ export default {
|
|||||||
if (!loginRes || !loginRes.code) {
|
if (!loginRes || !loginRes.code) {
|
||||||
throw new Error('获取微信code失败')
|
throw new Error('获取微信code失败')
|
||||||
}
|
}
|
||||||
// 2. 调用后端微信登录接口
|
// 2. 调用后端微信登录接口换取 token(上传头像接口需要鉴权)
|
||||||
const loginParams = {
|
const firstResp = await client.WechatLogin({
|
||||||
code: loginRes.code,
|
code: loginRes.code,
|
||||||
nickName: this.nickname.trim() || '',
|
nickName: this.nickname.trim() || '',
|
||||||
avatarUrl: this.avatarUrl || ''
|
avatarUrl: ''
|
||||||
}
|
})
|
||||||
const resp = await client.WechatLogin(loginParams)
|
|
||||||
// errcode 兜底:后端业务错误码非 0 时视为登录失败
|
// errcode 兜底:后端业务错误码非 0 时视为登录失败
|
||||||
if (resp && resp.errcode && resp.errcode !== 0) {
|
if (firstResp && firstResp.errcode && firstResp.errcode !== 0) {
|
||||||
throw { msg: resp.errmsg || '登录失败' }
|
throw { msg: firstResp.errmsg || '登录失败' }
|
||||||
}
|
}
|
||||||
if (!resp || !resp.token) {
|
if (!firstResp || !firstResp.token) {
|
||||||
throw { msg: '登录响应缺少 token' }
|
throw { msg: '登录响应缺少 token' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 保存 token 和用户信息
|
// 3. 保存 token 和用户信息(此时已有鉴权能力)
|
||||||
saveAuthTokens(resp)
|
saveAuthTokens(firstResp)
|
||||||
if (resp.user) {
|
if (firstResp.user) {
|
||||||
uni.setStorageSync('user_info', JSON.stringify(resp.user))
|
uni.setStorageSync('user_info', JSON.stringify(firstResp.user))
|
||||||
}
|
}
|
||||||
uni.setStorageSync('is_logged_in', 'true')
|
uni.setStorageSync('is_logged_in', 'true')
|
||||||
uni.setStorageSync('is_guest', 'false')
|
uni.setStorageSync('is_guest', 'false')
|
||||||
uni.setStorageSync('is_first_launch', 'false')
|
uni.setStorageSync('is_first_launch', 'false')
|
||||||
loginOk = true
|
loginOk = true
|
||||||
|
|
||||||
|
// 4. 有头像时:带 token 上传临时文件,再调 UpdateUserAvatar 写入后端
|
||||||
|
// (头像环节失败不阻断登录)
|
||||||
|
if (this.avatarTempPath) {
|
||||||
|
try {
|
||||||
|
const upRes = await UploadImage({ filePath: this.avatarTempPath })
|
||||||
|
const remoteUrl = upRes && upRes.data && upRes.data.url
|
||||||
|
if (remoteUrl && /^https?:\/\//.test(remoteUrl)) {
|
||||||
|
const avatarResp = await UpdateUserAvatar({ avatar: remoteUrl })
|
||||||
|
if (avatarResp && avatarResp.data && avatarResp.data.ok) {
|
||||||
|
// 同步更新本地缓存的用户头像
|
||||||
|
try {
|
||||||
|
const cached = JSON.parse(uni.getStorageSync('user_info') || '{}')
|
||||||
|
cached.avatar = remoteUrl
|
||||||
|
uni.setStorageSync('user_info', JSON.stringify(cached))
|
||||||
|
} catch (cacheErr) { /* ignore */ }
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '头像保存失败,可稍后在个人主页修改', icon: 'none' })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '头像上传失败,可稍后在个人主页修改', icon: 'none' })
|
||||||
|
}
|
||||||
|
} catch (avatarErr) {
|
||||||
|
console.warn('头像上传/保存失败,不影响登录:', avatarErr)
|
||||||
|
uni.showToast({ title: '头像上传失败,可稍后在个人主页修改', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 打印完整错误,方便定位具体失败环节
|
// 打印完整错误,方便定位具体失败环节
|
||||||
console.warn('后端登录失败,降级为本地模式:', e && e.stack ? e.stack : e)
|
console.warn('后端登录失败,降级为本地模式:', e && e.stack ? e.stack : e)
|
||||||
@@ -194,7 +225,7 @@ export default {
|
|||||||
const userInfo = {
|
const userInfo = {
|
||||||
id: `user_${Date.now()}`,
|
id: `user_${Date.now()}`,
|
||||||
nickname: this.nickname.trim() || '酒友',
|
nickname: this.nickname.trim() || '酒友',
|
||||||
avatar: this.avatarUrl || '',
|
avatar: '',
|
||||||
joinedAt: new Date().toISOString().slice(0, 10),
|
joinedAt: new Date().toISOString().slice(0, 10),
|
||||||
preferences: null
|
preferences: null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<view class="hero-content">
|
<view class="hero-content">
|
||||||
<view class="avatar-ring">
|
<view class="avatar-ring">
|
||||||
<image v-if="user.avatar" class="hero-avatar" :src="user.avatar" mode="aspectFill"></image>
|
<image v-if="isValidAvatar(user.avatar)" class="hero-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||||||
<view v-else class="hero-avatar hero-avatar-placeholder">
|
<view v-else class="hero-avatar hero-avatar-placeholder">
|
||||||
<text class="hero-avatar-icon">👤</text>
|
<text class="hero-avatar-icon">👤</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import AchievementBadge from '../../components/AchievementBadge.vue'
|
import AchievementBadge from '../../components/AchievementBadge.vue'
|
||||||
import client, { clearAuth } from '../../common/api'
|
import client, { clearAuth } from '../../common/api'
|
||||||
import { checkAchievements, getCatIcon, isIconPath, calcLocalStreak } from '../../common/utils'
|
import { checkAchievements, getCatIcon, isIconPath, calcLocalStreak, isValidAvatar } from '../../common/utils'
|
||||||
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
|
import { ACHIEVEMENTS, DRINK_CATEGORIES } from '../../common/constants'
|
||||||
import themeMixin from '../../common/theme-mixin'
|
import themeMixin from '../../common/theme-mixin'
|
||||||
|
|
||||||
@@ -271,11 +271,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isValidAvatar,
|
||||||
isIconPath,
|
isIconPath,
|
||||||
// 构建分享名片的 query 参数(携带公开档案数据)
|
// 构建分享名片的 query 参数(携带公开档案数据)
|
||||||
buildShareQuery() {
|
buildShareQuery() {
|
||||||
const parts = [`nick=${encodeURIComponent(this.user.nickname || '酒友')}`]
|
const parts = [`nick=${encodeURIComponent(this.user.nickname || '酒友')}`]
|
||||||
if (this.user.avatar) parts.push(`avatar=${encodeURIComponent(this.user.avatar)}`)
|
if (isValidAvatar(this.user.avatar)) parts.push(`avatar=${encodeURIComponent(this.user.avatar)}`)
|
||||||
parts.push(`days=${this.stats.totalDays || 0}`)
|
parts.push(`days=${this.stats.totalDays || 0}`)
|
||||||
parts.push(`records=${this.stats.totalRecords || 0}`)
|
parts.push(`records=${this.stats.totalRecords || 0}`)
|
||||||
parts.push(`cups=${this.stats.totalCups || 0}`)
|
parts.push(`cups=${this.stats.totalCups || 0}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user