fix:bug修改
This commit is contained in:
1 parent
3184608d99
commit
25a3dba931
64 files changed
+1980
-875
No files matched your search
@@ -1,7 +1,10 @@
|
||||
# 打包目标
|
||||
# H5 - 全部都是使用H5支付
|
||||
# APP - 全部都是使用APP支付
|
||||
SHOPRO_BUILD_TARGET=H5
|
||||
# 支付目标
|
||||
# H5 - 强制使用 H5/WAP 支付渠道
|
||||
# APP - 非微信小程序端跳转到微信小程序支付
|
||||
# SHOPRO_BUILD_TARGET 为兼容旧配置的默认值,下面两项可分别覆盖支付宝和微信支付
|
||||
SHOPRO_BUILD_TARGET=APP
|
||||
SHOPRO_ALIPAY_BUILD_TARGET=H5
|
||||
SHOPRO_WECHAT_BUILD_TARGET=APP
|
||||
|
||||
# 版本号
|
||||
SHOPRO_VERSION=v2.4.1
|
||||
@@ -12,6 +15,7 @@ SHOPRO_API_PATH=/app-api
|
||||
# 后端接口 - 测试环境(通过 process.env.NODE_ENV == development)
|
||||
# SHOPRO_DEV_BASE_URL = https://api.htysapp.com
|
||||
SHOPRO_DEV_BASE_URL=https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
# SHOPRO_DEV_BASE_URL=http://localhost:48083
|
||||
|
||||
# 后端接口 - 正式环境(通过 process.env.NODE_ENV != development)
|
||||
SHOPRO_BASE_URL=https://api.htysapp.com
|
||||
@@ -26,7 +30,7 @@ SHOPRO_WEBSOCKET_PATH=/infra/ws
|
||||
SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn
|
||||
### SHOPRO_STATIC_URL = https://file.sheepjs.com
|
||||
|
||||
# 前端 H5 访问域名
|
||||
# 前端 H5 访问域名(App 调起 H5 支付时作为支付完成后的回跳地址)
|
||||
SHOPRO_H5_URL=http://127.0.0.1:3000
|
||||
|
||||
# 是否开启直播 1 开启直播 | 0 关闭直播
|
||||
|
||||
+38
-12
@@ -7,9 +7,24 @@ import * as enums from './common/enums';
|
||||
import * as wsApi from './common/wssocket';
|
||||
import chatTokenManager from './common/chat-token-manager';
|
||||
import CHAT_ENV from '@/chat-env.js';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
|
||||
import useChatStore from '@/chat-store/chatStore.js';
|
||||
|
||||
const reissueChatToken = async () => {
|
||||
const response = await AuthUtil.reissueChatToken();
|
||||
const chatToken = response?.data?.chatToken;
|
||||
if (response?.code !== 0 || !chatToken) {
|
||||
const error = new Error(response?.msg || '重新签发聊天 token 失败');
|
||||
error.code = response?.code;
|
||||
throw error;
|
||||
}
|
||||
return chatToken;
|
||||
};
|
||||
|
||||
// 页面可能早于异步 onLaunch 完成加载,需在模块初始化时就注册重签能力。
|
||||
chatTokenManager.setReissueToken(reissueChatToken);
|
||||
|
||||
export default {
|
||||
/** ↓↓↓ 以下为app启动聊天相关模块初始化,WebSocket链接,加载聊天消息,加载离线消息,加载群组消息,初始化聊天相关的全局状态仓库 */
|
||||
data() {
|
||||
@@ -75,7 +90,11 @@ export default {
|
||||
this.refreshUnreadBadge()
|
||||
} catch (error) {
|
||||
console.log("初始化聊天:", error);
|
||||
this.exit();
|
||||
this.configStore?.setAppInit(false);
|
||||
uni.showToast({
|
||||
title: '聊天服务暂时不可用,请稍后重试',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
initWebSocket() {
|
||||
@@ -344,7 +363,6 @@ export default {
|
||||
handleSystemMessage(msg) {
|
||||
if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) {
|
||||
// 用户被封禁
|
||||
wsApi.close(3099);
|
||||
uni.showModal({
|
||||
content: '您的账号已被管理员封禁,原因:' + msg.content,
|
||||
showCancel: false,
|
||||
@@ -433,7 +451,7 @@ export default {
|
||||
console.log('exit');
|
||||
this.isExit = true;
|
||||
chatTokenManager.stop();
|
||||
wsApi.close(3099);
|
||||
wsApi.destroy(3099);
|
||||
uni.removeStorageSync('loginInfo');
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login',
|
||||
@@ -447,19 +465,19 @@ export default {
|
||||
// this.audioTip.play();
|
||||
},
|
||||
startChatTokenCheck() {
|
||||
// 轮询只处理聊天 token;仅凭证明确失效时退出,网络错误等待下次检查。
|
||||
// IM 双 token 都过期时,使用仍有效的主站登录态重新签发。
|
||||
chatTokenManager.start({
|
||||
reissueToken: reissueChatToken,
|
||||
onRefreshError: (error) => {
|
||||
const code = error?.code ?? error?.data?.code;
|
||||
if (code == 400 || code == 401) {
|
||||
this.exit();
|
||||
}
|
||||
console.warn('聊天 token 自动续期失败,等待下次重试', error);
|
||||
},
|
||||
});
|
||||
},
|
||||
reconnectWs() {
|
||||
// 已退出则不再重连
|
||||
if (this.isExit) {
|
||||
let loginInfo = uni.getStorageSync('loginInfo');
|
||||
// 退出时可能已有异步重连排队,需要同时校验主站和 IM 登录态。
|
||||
if (this.isExit || !uni.getStorageSync('token') || !loginInfo?.accessToken) {
|
||||
this.reconnecting = false;
|
||||
return;
|
||||
}
|
||||
// 记录标志
|
||||
@@ -468,11 +486,19 @@ export default {
|
||||
this.userStore
|
||||
.loadUser()
|
||||
.then((userInfo) => {
|
||||
loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (this.isExit || !uni.getStorageSync('token') || !loginInfo?.accessToken) {
|
||||
this.reconnecting = false;
|
||||
return;
|
||||
}
|
||||
// 重新连接
|
||||
let loginInfo = uni.getStorageSync('loginInfo');
|
||||
wsApi.reconnect(CHAT_ENV.WS_URL, loginInfo.accessToken);
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.isExit || !uni.getStorageSync('token')) {
|
||||
this.reconnecting = false;
|
||||
return;
|
||||
}
|
||||
// 5s后重试
|
||||
setTimeout(() => {
|
||||
this.reconnectWs();
|
||||
@@ -528,7 +554,7 @@ export default {
|
||||
// #ifndef MP-WEIXIN
|
||||
this.closeSplashscreen(1000); // 延迟1s,避免用户看到页面跳转
|
||||
let loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (!loginInfo || !loginInfo.refreshToken) {
|
||||
if (!uni.getStorageSync('token')) {
|
||||
this.closeSplashscreen(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ class ChatTokenManager {
|
||||
// 所有调用共享同一个刷新 Promise,避免并发刷新后 token 相互覆盖。
|
||||
this.refreshPromise = null;
|
||||
this.onRefreshError = null;
|
||||
// IM 的 refreshToken 也失效时,由主站登录态重新签发一套聊天 token。
|
||||
this.reissueToken = null;
|
||||
// 用于忽略定时器停止后才返回的旧检查结果。
|
||||
this.monitorVersion = 0;
|
||||
}
|
||||
@@ -17,9 +19,13 @@ class ChatTokenManager {
|
||||
// 启动时立即检查一次,之后每 30 秒检查一次;重复调用不会创建多个定时器。
|
||||
start(options = {}) {
|
||||
this.onRefreshError = options.onRefreshError || null;
|
||||
if (options.reissueToken) {
|
||||
this.reissueToken = options.reissueToken;
|
||||
}
|
||||
|
||||
const loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (!loginInfo?.refreshToken) {
|
||||
const canReissueFromMainLogin = uni.getStorageSync('token') && this.reissueToken;
|
||||
if (!loginInfo?.refreshToken && !canReissueFromMainLogin) {
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
@@ -44,6 +50,10 @@ class ChatTokenManager {
|
||||
this.onRefreshError = null;
|
||||
}
|
||||
|
||||
setReissueToken(reissueToken) {
|
||||
this.reissueToken = reissueToken;
|
||||
}
|
||||
|
||||
runCheck(monitorVersion = this.monitorVersion) {
|
||||
this.checkAccessToken().catch((error) => {
|
||||
if (monitorVersion !== this.monitorVersion) {
|
||||
@@ -57,6 +67,9 @@ class ChatTokenManager {
|
||||
checkAccessToken() {
|
||||
const loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (!loginInfo?.refreshToken) {
|
||||
if (uni.getStorageSync('token') && typeof this.reissueToken === 'function') {
|
||||
return this.refreshAccessToken(loginInfo);
|
||||
}
|
||||
this.stop();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
@@ -74,6 +87,17 @@ class ChatTokenManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
const loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (
|
||||
loginInfo?.accessToken === accessToken &&
|
||||
Number.isFinite(Number(loginInfo.accessTokenExpiresAt))
|
||||
) {
|
||||
return (
|
||||
Number(loginInfo.accessTokenExpiresAt) - Date.now() <=
|
||||
TOKEN_REFRESH_THRESHOLD_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// JWT 第二段是 payload,H5 和 App 均通过 uni API 完成 Base64 解码。
|
||||
const payload = accessToken.split('.')[1];
|
||||
@@ -103,33 +127,50 @@ class ChatTokenManager {
|
||||
}
|
||||
|
||||
refreshAccessToken(loginInfo = uni.getStorageSync('loginInfo')) {
|
||||
if (!loginInfo?.refreshToken) {
|
||||
return Promise.reject(new Error('缺少聊天 refreshToken'));
|
||||
}
|
||||
|
||||
if (this.refreshPromise) {
|
||||
return this.refreshPromise;
|
||||
}
|
||||
|
||||
const refreshToken = loginInfo.refreshToken;
|
||||
this.refreshPromise = this.requestRefreshToken(refreshToken)
|
||||
.then((res) => {
|
||||
if (!res || res.data?.code != 200) {
|
||||
throw res?.data || new Error('刷新聊天 token 失败');
|
||||
const refreshVersion = this.monitorVersion;
|
||||
const accessToken = loginInfo?.accessToken;
|
||||
const refreshToken = loginInfo?.refreshToken;
|
||||
const refreshRequest = refreshToken
|
||||
? this.requestRefreshToken(refreshToken).then((res) => {
|
||||
if (!res || res.data?.code != 200 || !res.data?.data) {
|
||||
throw res?.data || new Error('刷新聊天 token 失败');
|
||||
}
|
||||
return res.data.data;
|
||||
})
|
||||
: Promise.reject(new Error('缺少聊天 refreshToken'));
|
||||
|
||||
this.refreshPromise = refreshRequest
|
||||
.catch((error) => {
|
||||
if (!this.canReissueToken(error, refreshToken)) {
|
||||
throw error;
|
||||
}
|
||||
return this.reissueToken();
|
||||
})
|
||||
.then((newLoginInfo) => {
|
||||
if (refreshVersion !== this.monitorVersion) {
|
||||
throw new Error('聊天登录状态已停止,忽略本次 token 刷新结果');
|
||||
}
|
||||
if (!newLoginInfo?.accessToken || !newLoginInfo?.refreshToken) {
|
||||
throw new Error('重新签发的聊天 token 不完整');
|
||||
}
|
||||
|
||||
// 刷新期间用户可能退出或重新登录,旧请求结果不能覆盖新的登录状态。
|
||||
const currentLoginInfo = uni.getStorageSync('loginInfo');
|
||||
if (currentLoginInfo?.refreshToken !== refreshToken) {
|
||||
const loginChanged = refreshToken
|
||||
? currentLoginInfo?.refreshToken !== refreshToken
|
||||
: accessToken && currentLoginInfo?.accessToken !== accessToken;
|
||||
if (loginChanged) {
|
||||
if (currentLoginInfo?.accessToken) {
|
||||
return currentLoginInfo;
|
||||
}
|
||||
throw new Error('聊天登录状态已变化,忽略本次 token 刷新结果');
|
||||
}
|
||||
|
||||
const newLoginInfo = res.data.data;
|
||||
uni.setStorageSync('loginInfo', newLoginInfo);
|
||||
return newLoginInfo;
|
||||
return this.saveTokenInfo(newLoginInfo);
|
||||
})
|
||||
.finally(() => {
|
||||
this.refreshPromise = null;
|
||||
@@ -138,6 +179,43 @@ class ChatTokenManager {
|
||||
return this.refreshPromise;
|
||||
}
|
||||
|
||||
canReissueToken(error, refreshToken) {
|
||||
if (!uni.getStorageSync('token') || typeof this.reissueToken !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (!refreshToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const code = error?.code ?? error?.data?.code ?? error?.statusCode;
|
||||
const message = error?.message ?? error?.data?.message ?? error?.errMsg ?? '';
|
||||
return (
|
||||
code == 400 ||
|
||||
code == 401 ||
|
||||
message.includes('登录信息已过期') ||
|
||||
message.includes('refreshToken已过期')
|
||||
);
|
||||
}
|
||||
|
||||
saveTokenInfo(tokenInfo) {
|
||||
if (!tokenInfo) {
|
||||
return tokenInfo;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const storedTokenInfo = { ...tokenInfo };
|
||||
const accessTokenExpiresIn = Number(tokenInfo.accessTokenExpiresIn);
|
||||
const refreshTokenExpiresIn = Number(tokenInfo.refreshTokenExpiresIn);
|
||||
if (Number.isFinite(accessTokenExpiresIn) && accessTokenExpiresIn > 0) {
|
||||
storedTokenInfo.accessTokenExpiresAt = now + accessTokenExpiresIn * 1000;
|
||||
}
|
||||
if (Number.isFinite(refreshTokenExpiresIn) && refreshTokenExpiresIn > 0) {
|
||||
storedTokenInfo.refreshTokenExpiresAt = now + refreshTokenExpiresIn * 1000;
|
||||
}
|
||||
uni.setStorageSync('loginInfo', storedTokenInfo);
|
||||
return storedTokenInfo;
|
||||
}
|
||||
|
||||
requestRefreshToken(refreshToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
|
||||
+10
-9
@@ -1,7 +1,9 @@
|
||||
import CHAT_ENV from '@/chat-env.js';
|
||||
import chatTokenManager from './chat-token-manager';
|
||||
|
||||
const request = (options) => {
|
||||
const MAX_AUTH_RETRY_COUNT = 2;
|
||||
|
||||
const request = (options, authRetryCount = 0) => {
|
||||
const header = options.header || {};
|
||||
const loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (loginInfo) {
|
||||
@@ -23,33 +25,32 @@ const request = (options) => {
|
||||
await chatTokenManager.refreshAccessToken(currentLoginInfo);
|
||||
} catch (error) {
|
||||
console.log('刷新token失败', error);
|
||||
getApp().$vm.exit();
|
||||
return reject(error);
|
||||
}
|
||||
}
|
||||
return resolve(res.data.data);
|
||||
} else if (res.data.code == 400) {
|
||||
console.log('code 400');
|
||||
getApp().$vm.exit();
|
||||
} else if (res.data.code == 401) {
|
||||
} else if (res.data.code == 400 || res.data.code == 401) {
|
||||
console.log('token失效,尝试重新获取');
|
||||
const currentLoginInfo = uni.getStorageSync('loginInfo');
|
||||
|
||||
if (authRetryCount >= MAX_AUTH_RETRY_COUNT) {
|
||||
return reject(res.data);
|
||||
}
|
||||
|
||||
// 当前请求使用的是旧 token,直接用已刷新的 token 重试。
|
||||
if (
|
||||
currentLoginInfo?.accessToken &&
|
||||
currentLoginInfo.accessToken !== header.accessToken
|
||||
) {
|
||||
return resolve(request(options));
|
||||
return resolve(request(options, authRetryCount + 1));
|
||||
}
|
||||
|
||||
try {
|
||||
await chatTokenManager.refreshAccessToken(currentLoginInfo);
|
||||
// 重新发送刚才的请求
|
||||
return resolve(request(options));
|
||||
return resolve(request(options, authRetryCount + 1));
|
||||
} catch (error) {
|
||||
console.log('刷新token失败', error);
|
||||
getApp().$vm.exit();
|
||||
return reject(error);
|
||||
}
|
||||
} else {
|
||||
|
||||
+213
-77
@@ -1,129 +1,266 @@
|
||||
let accessToken = '';
|
||||
let wsUrl = '';
|
||||
let messageCallBack = null;
|
||||
let closeCallBack = null;
|
||||
let connectCallBack = null;
|
||||
let isConnect = false; //连接标识 避免重复连接
|
||||
let isConnect = false;
|
||||
let enabled = false;
|
||||
let rec = null;
|
||||
let lastConnectTime = new Date(); // 最后一次连接时间
|
||||
let connectRetryTimer = null;
|
||||
let lastConnectTime = 0;
|
||||
let socketTask = null;
|
||||
|
||||
let connect = (wsurl, token) => {
|
||||
accessToken = token;
|
||||
if (isConnect) {
|
||||
const clearReconnectTimers = () => {
|
||||
if (rec) {
|
||||
clearTimeout(rec);
|
||||
rec = null;
|
||||
}
|
||||
if (connectRetryTimer) {
|
||||
clearTimeout(connectRetryTimer);
|
||||
connectRetryTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const closeSocketTask = (task, code = 1000) => {
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
lastConnectTime = new Date();
|
||||
socketTask = uni.connectSocket({
|
||||
url: wsurl,
|
||||
success: (res) => {},
|
||||
fail: (e) => {
|
||||
console.log(e);
|
||||
console.log('WebSocket连接失败,10s后重连');
|
||||
setTimeout(() => {
|
||||
connect();
|
||||
}, 10000);
|
||||
},
|
||||
});
|
||||
try {
|
||||
task.close({
|
||||
code,
|
||||
fail: (error) => {
|
||||
console.log('关闭websocket连接失败', error);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('关闭websocket连接失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
socketTask.onOpen((res) => {
|
||||
const finishConnection = (task, event) => {
|
||||
// 旧连接可能晚于新连接触发事件,不能让它污染当前连接状态。
|
||||
if (task !== socketTask) {
|
||||
return;
|
||||
}
|
||||
socketTask = null;
|
||||
isConnect = false;
|
||||
heartCheck.stop();
|
||||
closeCallBack && closeCallBack(event);
|
||||
};
|
||||
|
||||
const scheduleConnectRetry = (url, token) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
if (connectRetryTimer) {
|
||||
clearTimeout(connectRetryTimer);
|
||||
}
|
||||
connectRetryTimer = setTimeout(() => {
|
||||
connectRetryTimer = null;
|
||||
if (!enabled || socketTask) {
|
||||
return;
|
||||
}
|
||||
connect(url, token);
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
let connect = (url, token) => {
|
||||
if (!url || !token) {
|
||||
console.warn('缺少WebSocket地址或登录凭证,已跳过连接');
|
||||
return;
|
||||
}
|
||||
|
||||
wsUrl = url;
|
||||
accessToken = token;
|
||||
enabled = true;
|
||||
clearReconnectTimers();
|
||||
if (socketTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastConnectTime = Date.now();
|
||||
let task = null;
|
||||
try {
|
||||
task = uni.connectSocket({
|
||||
url,
|
||||
success: () => {},
|
||||
fail: (error) => {
|
||||
if (!enabled || (task && task !== socketTask)) {
|
||||
return;
|
||||
}
|
||||
console.log(error);
|
||||
console.log('WebSocket连接失败,10s后重连');
|
||||
socketTask = null;
|
||||
isConnect = false;
|
||||
scheduleConnectRetry(url, token);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('WebSocket连接失败,10s后重连', error);
|
||||
scheduleConnectRetry(url, token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
scheduleConnectRetry(url, token);
|
||||
return;
|
||||
}
|
||||
socketTask = task;
|
||||
|
||||
task.onOpen(() => {
|
||||
if (task !== socketTask || !enabled) {
|
||||
closeSocketTask(task);
|
||||
return;
|
||||
}
|
||||
console.log('WebSocket连接成功');
|
||||
isConnect = true;
|
||||
// 发送登录命令
|
||||
let loginInfo = {
|
||||
cmd: 0,
|
||||
data: {
|
||||
accessToken: accessToken,
|
||||
},
|
||||
};
|
||||
socketTask.send({
|
||||
data: JSON.stringify(loginInfo),
|
||||
if (connectRetryTimer) {
|
||||
clearTimeout(connectRetryTimer);
|
||||
connectRetryTimer = null;
|
||||
}
|
||||
task.send({
|
||||
data: JSON.stringify({
|
||||
cmd: 0,
|
||||
data: {
|
||||
accessToken: token,
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
socketTask.onMessage((res) => {
|
||||
let sendInfo = JSON.parse(res.data);
|
||||
task.onMessage((res) => {
|
||||
if (task !== socketTask || !enabled) {
|
||||
return;
|
||||
}
|
||||
let sendInfo;
|
||||
try {
|
||||
sendInfo = JSON.parse(res.data);
|
||||
} catch (error) {
|
||||
console.warn('WebSocket消息解析失败', error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sendInfo.cmd == 0) {
|
||||
heartCheck.start();
|
||||
connectCallBack && connectCallBack();
|
||||
console.log('WebSocket登录成功');
|
||||
} else if (sendInfo.cmd == 1) {
|
||||
// 重新开启心跳定时
|
||||
heartCheck.reset();
|
||||
} else {
|
||||
// 其他消息转发出去
|
||||
console.log('接收到消息', sendInfo);
|
||||
messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data);
|
||||
}
|
||||
});
|
||||
|
||||
socketTask.onClose((res) => {
|
||||
task.onClose((res) => {
|
||||
if (task !== socketTask) {
|
||||
return;
|
||||
}
|
||||
console.log('WebSocket连接关闭');
|
||||
isConnect = false;
|
||||
closeCallBack && closeCallBack(res);
|
||||
finishConnection(task, res);
|
||||
});
|
||||
|
||||
socketTask.onError((e) => {
|
||||
console.log('ws错误:', e);
|
||||
close(1006);
|
||||
isConnect = false;
|
||||
closeCallBack && closeCallBack(e);
|
||||
task.onError((error) => {
|
||||
if (task !== socketTask || !enabled) {
|
||||
return;
|
||||
}
|
||||
console.log('ws错误:', error);
|
||||
closeSocketTask(task);
|
||||
finishConnection(task, error);
|
||||
});
|
||||
};
|
||||
|
||||
//定义重连函数
|
||||
let reconnect = (wsurl, accessToken) => {
|
||||
console.log('尝试重新连接');
|
||||
if (isConnect) {
|
||||
// 网络异常断开后重连;主动销毁后不会再接受旧任务发起的重连。
|
||||
let reconnect = (url, token) => {
|
||||
if (!enabled || socketTask) {
|
||||
return;
|
||||
}
|
||||
// 延迟10秒重连 避免过多次过频繁请求重连
|
||||
let timeDiff = new Date().getTime() - lastConnectTime.getTime();
|
||||
let delay = timeDiff < 10000 ? 10000 - timeDiff : 0;
|
||||
rec && clearTimeout(rec);
|
||||
rec = setTimeout(function () {
|
||||
connect(wsurl, accessToken);
|
||||
const reconnectUrl = url || wsUrl;
|
||||
const reconnectToken = token || accessToken;
|
||||
if (!reconnectUrl || !reconnectToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('尝试重新连接');
|
||||
const timeDiff = Date.now() - lastConnectTime;
|
||||
const delay = timeDiff < 10000 ? 10000 - timeDiff : 0;
|
||||
if (rec) {
|
||||
clearTimeout(rec);
|
||||
}
|
||||
rec = setTimeout(() => {
|
||||
rec = null;
|
||||
if (!enabled || socketTask) {
|
||||
return;
|
||||
}
|
||||
connect(reconnectUrl, reconnectToken);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
//设置关闭连接
|
||||
let close = (code) => {
|
||||
if (!isConnect) {
|
||||
// 临时关闭当前连接,关闭事件仍交给上层决定是否需要重连。
|
||||
let close = (code = 1000) => {
|
||||
clearReconnectTimers();
|
||||
heartCheck.stop();
|
||||
isConnect = false;
|
||||
if (!socketTask) {
|
||||
return;
|
||||
}
|
||||
socketTask.close({
|
||||
code: code,
|
||||
complete: (res) => {
|
||||
isConnect = false;
|
||||
console.log('关闭websocket连接');
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log('关闭websocket连接失败', e);
|
||||
},
|
||||
});
|
||||
closeSocketTask(socketTask, code);
|
||||
};
|
||||
|
||||
// 退出登录时永久销毁当前连接,并抑制旧连接的关闭回调和重连任务。
|
||||
let destroy = (code = 1000) => {
|
||||
enabled = false;
|
||||
clearReconnectTimers();
|
||||
heartCheck.stop();
|
||||
|
||||
const task = socketTask;
|
||||
socketTask = null;
|
||||
isConnect = false;
|
||||
accessToken = '';
|
||||
wsUrl = '';
|
||||
closeSocketTask(task, code);
|
||||
};
|
||||
|
||||
// 心跳设置
|
||||
let heartCheck = {
|
||||
timeout: 20000, //每段时间发送一次心跳包 这里设置为20s
|
||||
timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象)
|
||||
start: function () {
|
||||
if (isConnect) {
|
||||
timeout: 20000,
|
||||
timeoutObj: null,
|
||||
start() {
|
||||
if (enabled && isConnect) {
|
||||
console.log('发送WebSocket心跳');
|
||||
let heartBeat = {
|
||||
cmd: 1,
|
||||
data: {},
|
||||
};
|
||||
sendMessage(JSON.stringify(heartBeat));
|
||||
sendMessage(
|
||||
JSON.stringify({
|
||||
cmd: 1,
|
||||
data: {},
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
reset: function () {
|
||||
clearTimeout(this.timeoutObj);
|
||||
reset() {
|
||||
this.stop();
|
||||
if (!enabled || !isConnect) {
|
||||
return;
|
||||
}
|
||||
this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout);
|
||||
},
|
||||
stop() {
|
||||
if (this.timeoutObj) {
|
||||
clearTimeout(this.timeoutObj);
|
||||
this.timeoutObj = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let sendMessage = (message) => {
|
||||
socketTask.send({ data: message });
|
||||
if (!enabled || !isConnect || !socketTask) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
socketTask.send({ data: message });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('发送WebSocket消息失败', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let onConnect = (callback) => {
|
||||
@@ -138,5 +275,4 @@ let onClose = (callback) => {
|
||||
closeCallBack = callback;
|
||||
};
|
||||
|
||||
// 将方法暴露出去
|
||||
export { connect, reconnect, close, sendMessage, onConnect, onMessage, onClose };
|
||||
export { connect, reconnect, close, destroy, sendMessage, onConnect, onMessage, onClose };
|
||||
+8
-2
@@ -2,6 +2,7 @@
|
||||
import App from './App.vue';
|
||||
import { createSSRApp } from 'vue';
|
||||
import { setupPinia } from './sheep/store';
|
||||
import { initStatusBarHeight, statusBarPageStyle } from '@/sheep/platform/status-bar';
|
||||
import 'virtual:uno.css';
|
||||
|
||||
/** 聊天common模块 */
|
||||
@@ -18,14 +19,12 @@ import * as recorder from './common/recorder-h5';
|
||||
import ImageResize from 'quill-image-resize-mp';
|
||||
import Quill from 'quill';
|
||||
import { patchH5Navigation } from '@/sheep/router/h5-navigation';
|
||||
import { initH5HostShell } from '@/sheep/platform/h5-shell';
|
||||
// 以下组件用于兼容部分手机聊天边框无法输入的问题
|
||||
window.Quill = Quill;
|
||||
window.ImageResize = { default: ImageResize };
|
||||
// 调试器
|
||||
// import VConsole from 'vconsole';
|
||||
// new VConsole();
|
||||
initH5HostShell();
|
||||
patchH5Navigation();
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
@@ -39,6 +38,9 @@ import useGroupStore from '@/chat-store/groupStore.js';
|
||||
import useConfigStore from '@/chat-store/configStore.js';
|
||||
import useUserStore from '@/chat-store/userStore.js';
|
||||
|
||||
// H5 会同步应用宿主高度;App 和小程序会在异步系统信息返回后更新页面根变量。
|
||||
void initStatusBarHeight();
|
||||
|
||||
/** 聊天页面的导航组件 */
|
||||
import barGroup from '@/components/bar/bar-group';
|
||||
import arrowBar from '@/components/bar/arrow-bar';
|
||||
@@ -48,6 +50,10 @@ import switchBar from '@/components/bar/switch-bar';
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
|
||||
Object.defineProperty(app.config.globalProperties, '$statusBarPageStyle', {
|
||||
get: () => statusBarPageStyle.value,
|
||||
});
|
||||
|
||||
const pinia = setupPinia(app);
|
||||
|
||||
// 聊天common模块挂载为全局属性
|
||||
|
||||
+126
-75
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider class="flex-col h-full" theme="dark">
|
||||
<wd-navbar title="城市选择" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -15,106 +16,156 @@
|
||||
</view>
|
||||
<view class="p-x-30rpx text-sm m-t-30rpx">热门城市</view>
|
||||
<view class="flex flex-wrap gap-3 py-3 px-4">
|
||||
<text v-for="(item, index) in hotCity" :key="index" class="bg-color-light text-color-theme px-2 rounded-1"
|
||||
@click.stop="selectHot(item)">
|
||||
<text
|
||||
v-for="(item, index) in hotCity"
|
||||
:key="index"
|
||||
class="bg-color-light text-color-theme px-2 rounded-1"
|
||||
@click.stop="selectHot(item)"
|
||||
>
|
||||
{{ item.t }}
|
||||
</text>
|
||||
</view>
|
||||
<wd-index-bar class="flex-1 overflow-hidden">
|
||||
<view v-for="(item, index) in useCityIndex()" :key="index">
|
||||
<wd-index-anchor :index="item.letter" />
|
||||
<wd-cell border clickable v-for="city in item.data" :key="city.v" :title="city.t" @click="select(city)" />
|
||||
<wd-cell
|
||||
border
|
||||
clickable
|
||||
v-for="city in item.data"
|
||||
:key="city.v"
|
||||
:title="city.t"
|
||||
@click="select(city)"
|
||||
/>
|
||||
</view>
|
||||
</wd-index-bar>
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const hotCity = [
|
||||
{ t: '北京', v: 1 },
|
||||
{ t: '上海', v: 2 },
|
||||
{ t: '嘉兴', v: 3 },
|
||||
{ t: '杭州', v: 4 },
|
||||
];
|
||||
const hotCity = [
|
||||
{ t: '北京', v: 1 },
|
||||
{ t: '上海', v: 2 },
|
||||
{ t: '嘉兴', v: 3 },
|
||||
{ t: '杭州', v: 4 },
|
||||
];
|
||||
|
||||
const userCity = computed(() => {
|
||||
const selectCity = sheep.$store('user').userCity
|
||||
if (selectCity && selectCity.t) return selectCity
|
||||
const currentLocation = sheep.$store('user')?.userInfo?.currentLocation
|
||||
if (!currentLocation) return null
|
||||
const city = {
|
||||
v: null,
|
||||
t: currentLocation.city,
|
||||
lng: String(currentLocation.lng),
|
||||
lat: String(currentLocation.lat),
|
||||
}
|
||||
sheep.$store('user').setUserCity(city);
|
||||
return city
|
||||
});
|
||||
const userCity = computed(() => {
|
||||
const selectCity = sheep.$store('user').userCity;
|
||||
if (selectCity && selectCity.t) return selectCity;
|
||||
const currentLocation = sheep.$store('user')?.userInfo?.currentLocation;
|
||||
if (!currentLocation) return null;
|
||||
const city = {
|
||||
v: null,
|
||||
t: currentLocation.city,
|
||||
lng: String(currentLocation.lng),
|
||||
lat: String(currentLocation.lat),
|
||||
};
|
||||
sheep.$store('user').setUserCity(city);
|
||||
return city;
|
||||
});
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import useCityIndex, { type cityItem } from '@/sheep/hooks/use-city-index';
|
||||
// @ts-ignore
|
||||
import sheep from '@/sheep';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import useCityIndex, { type cityItem } from '@/sheep/hooks/use-city-index';
|
||||
// @ts-ignore
|
||||
import sheep from '@/sheep';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import {
|
||||
type CurrentLocation,
|
||||
getCurrentLocation,
|
||||
getLocationErrorMessage,
|
||||
} from '@/sheep/util/location';
|
||||
|
||||
const instance: any = getCurrentInstance()?.proxy;
|
||||
const eventChannel = instance.getOpenerEventChannel();
|
||||
const instance: any = getCurrentInstance()?.proxy;
|
||||
const eventChannel = instance.getOpenerEventChannel();
|
||||
|
||||
// 选择热门城市
|
||||
const selectHot = (v: cityItem) => {
|
||||
sheep.$store('user').setUserCity(v);
|
||||
eventChannel.emit('changeCity');
|
||||
back();
|
||||
};
|
||||
// 选择热门城市
|
||||
const selectHot = (v: cityItem) => {
|
||||
sheep.$store('user').setUserCity(v);
|
||||
eventChannel.emit('changeCity');
|
||||
back();
|
||||
};
|
||||
|
||||
const openSystemGPS = () => {
|
||||
const openSystemGPS = async () => {
|
||||
uni.showLoading({ title: '定位中' });
|
||||
let location: CurrentLocation;
|
||||
try {
|
||||
location = await getCurrentLocation({ geocode: true });
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: getLocationErrorMessage(error), icon: 'none', duration: 5000 });
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
await RecentApi.updateUserLocation({
|
||||
lat: String(location.latitude),
|
||||
lon: String(location.longitude),
|
||||
province: location.address?.province ?? '',
|
||||
city: location.address?.city ?? '',
|
||||
district: location.address?.district ?? '',
|
||||
address: location.address?.street ?? '',
|
||||
});
|
||||
|
||||
const select = (v: cityItem) => {
|
||||
sheep.$store('user').setUserCity(v);
|
||||
uni.getLocation({
|
||||
geocode: true,
|
||||
success: (res) => {
|
||||
const params = {
|
||||
lat: String(res.latitude),
|
||||
lon: String(res.longitude),
|
||||
province: res.address?.province ?? '',
|
||||
const userInfo = await sheep.$store('user').getInfo();
|
||||
const currentLocation = userInfo?.currentLocation;
|
||||
const cityName = location.address?.city || currentLocation?.city;
|
||||
if (cityName) {
|
||||
sheep.$store('user').setUserCity({
|
||||
v: null,
|
||||
t: cityName,
|
||||
lng: String(location.longitude),
|
||||
lat: String(location.latitude),
|
||||
});
|
||||
eventChannel.emit('changeCity');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('同步用户位置失败:', error);
|
||||
uni.showToast({ title: '位置已获取,但城市信息同步失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
const select = async (v: cityItem) => {
|
||||
sheep.$store('user').setUserCity(v);
|
||||
let location: CurrentLocation | null;
|
||||
try {
|
||||
location = await getCurrentLocation({ geocode: true });
|
||||
} catch (error) {
|
||||
location = null;
|
||||
}
|
||||
|
||||
try {
|
||||
await RecentApi.updateUserLocation({
|
||||
lat: location ? String(location.latitude) : '0',
|
||||
lon: location ? String(location.longitude) : '0',
|
||||
province: location?.address?.province ?? '',
|
||||
city: v?.t ?? '',
|
||||
district: res.address?.district ?? '',
|
||||
address: res.address?.street ?? '',
|
||||
};
|
||||
RecentApi.updateUserLocation(params);
|
||||
},
|
||||
fail: (err) => {
|
||||
const params = {
|
||||
lat: '0',
|
||||
lon: '0',
|
||||
city: v?.t ?? '',
|
||||
};
|
||||
RecentApi.updateUserLocation(params);
|
||||
},
|
||||
});
|
||||
eventChannel.emit('changeCity');
|
||||
back();
|
||||
};
|
||||
district: location?.address?.district ?? '',
|
||||
address: location?.address?.street ?? '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('同步所选城市失败:', error);
|
||||
}
|
||||
eventChannel.emit('changeCity');
|
||||
back();
|
||||
};
|
||||
|
||||
function back() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
function back() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.wd-index-bar__sidebar {
|
||||
.wd-index-bar__index {
|
||||
width: 40rpx;
|
||||
padding: 0 !important;
|
||||
text-align: center;
|
||||
.wd-index-bar__sidebar {
|
||||
.wd-index-bar__index {
|
||||
width: 40rpx;
|
||||
padding: 0 !important;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="举报" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<view class="chat-box" id="chatBox">
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar fixed safeAreaInsetTop :bordered="false">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<view class="page friend">
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="友友" fixed safeAreaInsetTop :bordered="false">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<view class="page">
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="用户信息" fixed safeAreaInsetTop :bordered="false">
|
||||
|
||||
+257
-111
@@ -1,13 +1,17 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="发布动态" safeAreaInsetTop :bordered="false">
|
||||
<wd-navbar :title="isEditMode ? '修改动态' : '发布动态'" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
<uni-icons type="back" size="30" color="#D29E52" @click="back"></uni-icons>
|
||||
</template>
|
||||
<template #right>
|
||||
<view class="bg-color-theme-dark px-3 py-1 rounded-full c-black h-1/2 line-height-normal active:bg-color-theme"
|
||||
@click="publishMoments">
|
||||
发布
|
||||
<view
|
||||
class="bg-color-theme-dark px-3 py-1 rounded-full c-black h-1/2 line-height-normal active:bg-color-theme"
|
||||
:class="{ 'opacity-50': publishing || loadingMoment }"
|
||||
@click="publishMoments"
|
||||
>
|
||||
{{ publishing ? '提交中' : isEditMode ? '重新发布' : '发布' }}
|
||||
</view>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
@@ -17,20 +21,34 @@
|
||||
<wd-tabs v-model="activeTab">
|
||||
<wd-tab title="发图文">
|
||||
<view class="px-4 py-8 flex-row-center">
|
||||
<s-uploader v-model:url="params.coverPic" fileMediatype="image" limit="6" mode="grid" :imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
border: { width: '4rpx', color: '#D29E52', radius: '40rpx' },
|
||||
}" />
|
||||
<s-uploader
|
||||
v-model="imageFiles"
|
||||
v-model:url="params.coverPic"
|
||||
fileMediatype="image"
|
||||
limit="6"
|
||||
mode="grid"
|
||||
:imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
border: { width: '4rpx', color: '#D29E52', radius: '40rpx' },
|
||||
}"
|
||||
/>
|
||||
</view>
|
||||
</wd-tab>
|
||||
<wd-tab title="发视频">
|
||||
<view class="px-4 py-8 flex-row-center">
|
||||
<s-uploader v-model:url="params.coverVideo" fileMediatype="video" limit="6" mode="grid" :imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
border: { width: '4rpx', color: '#D29E52', radius: '40rpx' },
|
||||
}">
|
||||
<s-uploader
|
||||
v-model="videoFiles"
|
||||
v-model:url="params.coverVideo"
|
||||
fileMediatype="video"
|
||||
limit="6"
|
||||
mode="grid"
|
||||
:imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
border: { width: '4rpx', color: '#D29E52', radius: '40rpx' },
|
||||
}"
|
||||
>
|
||||
<view class="flex-row-center size-full">
|
||||
<image src="/static/video-icon.png" class="size-100rpx"></image>
|
||||
</view>
|
||||
@@ -39,8 +57,16 @@
|
||||
</wd-tab>
|
||||
<wd-tab v-if="false" title="发视频">
|
||||
<view class="px-4 py-8">
|
||||
<wd-upload v-model:file-list="params.coverVideo" :action="baseUrl + apiPath + '/infra/file/upload'"
|
||||
:header="header" image-mode="aspectFill" accept="video" multiple :limit="6" :max-size="2000000" />
|
||||
<wd-upload
|
||||
v-model:file-list="params.coverVideo"
|
||||
:action="baseUrl + apiPath + '/infra/file/upload'"
|
||||
:header="header"
|
||||
image-mode="aspectFill"
|
||||
accept="video"
|
||||
multiple
|
||||
:limit="6"
|
||||
:max-size="2000000"
|
||||
/>
|
||||
</view>
|
||||
</wd-tab>
|
||||
</wd-tabs>
|
||||
@@ -50,127 +76,247 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { useColPickerData } from '@/sheep/hooks/use-col-picker-data';
|
||||
import { type ColPickerColumnChange } from '@/uni_modules/wot-design-uni/components/wd-col-picker/types';
|
||||
// @ts-ignore
|
||||
import { baseUrl, apiPath, tenantId } from '@/sheep/config';
|
||||
// @ts-ignore
|
||||
import { getAccessToken } from '@/sheep/request';
|
||||
import { useToast } from '@/uni_modules/wot-design-uni';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { useColPickerData } from '@/sheep/hooks/use-col-picker-data';
|
||||
import { type ColPickerColumnChange } from '@/uni_modules/wot-design-uni/components/wd-col-picker/types';
|
||||
// @ts-ignore
|
||||
import { baseUrl, apiPath, tenantId } from '@/sheep/config';
|
||||
// @ts-ignore
|
||||
import { getAccessToken } from '@/sheep/request';
|
||||
import { useToast } from '@/uni_modules/wot-design-uni';
|
||||
import {
|
||||
getCurrentLocation,
|
||||
getLocationErrorMessage,
|
||||
isLocationError,
|
||||
} from '@/sheep/util/location';
|
||||
|
||||
const toast = useToast();
|
||||
const { colPickerData, findChildrenByCode } = useColPickerData();
|
||||
const toast = useToast();
|
||||
const { colPickerData, findChildrenByCode } = useColPickerData();
|
||||
|
||||
const activeTab = ref<0 | 1>(0);
|
||||
// const addressValue = ref<string[]>([]);
|
||||
const params = reactive({
|
||||
coverPic: [],
|
||||
coverVideo: [],
|
||||
description: '',
|
||||
issueLong: '',
|
||||
issueLat: '',
|
||||
issueProvince: '',
|
||||
issueCity: '',
|
||||
issueDistrict: '',
|
||||
});
|
||||
const coverPic = computed(() => params.coverPic?.join(',') || '');
|
||||
const coverVideo = computed(() => params.coverVideo?.join(',') || '');
|
||||
const area = ref<any[]>([
|
||||
colPickerData.map((item) => {
|
||||
return {
|
||||
value: item.value,
|
||||
label: item.text,
|
||||
};
|
||||
}),
|
||||
]);
|
||||
const activeTab = ref<0 | 1>(0);
|
||||
const publishing = ref(false);
|
||||
const loadingMoment = ref(false);
|
||||
const recentId = ref<string>();
|
||||
const imageFiles = ref<any[]>([]);
|
||||
const videoFiles = ref<any[]>([]);
|
||||
const originalContent = ref({
|
||||
description: '',
|
||||
coverPic: '',
|
||||
coverVideo: '',
|
||||
});
|
||||
const isEditMode = computed(() => Boolean(recentId.value));
|
||||
// const addressValue = ref<string[]>([]);
|
||||
const params = reactive({
|
||||
coverPic: [],
|
||||
coverVideo: [],
|
||||
description: '',
|
||||
issueLong: '',
|
||||
issueLat: '',
|
||||
issueProvince: '',
|
||||
issueCity: '',
|
||||
issueDistrict: '',
|
||||
issueAddress: '',
|
||||
});
|
||||
const coverPic = computed(() => params.coverPic?.join(',') || '');
|
||||
const coverVideo = computed(() => params.coverVideo?.join(',') || '');
|
||||
const area = ref<any[]>([
|
||||
colPickerData.map((item) => {
|
||||
return {
|
||||
value: item.value,
|
||||
label: item.text,
|
||||
};
|
||||
}),
|
||||
]);
|
||||
|
||||
const columnChange: ColPickerColumnChange = ({ selectedItem, resolve, finish }) => {
|
||||
const areaData = findChildrenByCode(colPickerData, selectedItem.value);
|
||||
if (areaData && areaData.length) {
|
||||
resolve(
|
||||
areaData.map((item) => {
|
||||
return {
|
||||
value: item.value,
|
||||
label: item.text,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
const columnChange: ColPickerColumnChange = ({ selectedItem, resolve, finish }) => {
|
||||
const areaData = findChildrenByCode(colPickerData, selectedItem.value);
|
||||
if (areaData && areaData.length) {
|
||||
resolve(
|
||||
areaData.map((item) => {
|
||||
return {
|
||||
value: item.value,
|
||||
label: item.text,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
// 地址选择器确认
|
||||
const areaPickerConfirm = ({
|
||||
value,
|
||||
selectedItems,
|
||||
}: {
|
||||
value: string[];
|
||||
selectedItems: { value: string; label: string }[];
|
||||
}) => {
|
||||
params.issueProvince = selectedItems[0].label;
|
||||
params.issueCity = selectedItems[1].label;
|
||||
params.issueDistrict = selectedItems[2].label;
|
||||
};
|
||||
// 地址选择器确认
|
||||
const areaPickerConfirm = ({
|
||||
value,
|
||||
selectedItems,
|
||||
}: {
|
||||
value: string[];
|
||||
selectedItems: { value: string; label: string }[];
|
||||
}) => {
|
||||
params.issueProvince = selectedItems[0].label;
|
||||
params.issueCity = selectedItems[1].label;
|
||||
params.issueDistrict = selectedItems[2].label;
|
||||
};
|
||||
|
||||
const header = {
|
||||
Accept: '*/*',
|
||||
'tenant-id': tenantId,
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
};
|
||||
const header = {
|
||||
Accept: '*/*',
|
||||
'tenant-id': tenantId,
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
};
|
||||
|
||||
// 发布朋友圈
|
||||
const publishMoments = async () => {
|
||||
if (!params.description) return uni.showToast({ title: '请填写内容', icon: 'error' });
|
||||
if (activeTab.value === 0 && (!params.coverPic || !params.coverPic.length))
|
||||
return uni.showToast({ title: '请上传图片', icon: 'error' });
|
||||
if (activeTab.value === 1 && (!params.coverVideo || !params.coverVideo.length))
|
||||
return uni.showToast({ title: '请上传视频', icon: 'error' });
|
||||
const splitUrls = (value?: string) =>
|
||||
String(value || '')
|
||||
.split(',')
|
||||
.map((url) => url.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// 获取经纬度位置信息
|
||||
uni.getLocation({
|
||||
success: async (res) => {
|
||||
params.issueLat = String(res.latitude);
|
||||
params.issueLong = String(res.longitude);
|
||||
const toUploadFiles = (urls: string[]) =>
|
||||
urls.map((url) => ({
|
||||
url,
|
||||
path: url,
|
||||
fileID: url,
|
||||
status: 'success',
|
||||
}));
|
||||
|
||||
const { code, msg } = await RecentApi.createMoment({
|
||||
...params,
|
||||
coverPic: activeTab.value === 0 ? coverPic.value : '',
|
||||
coverVideo: activeTab.value === 1 ? coverVideo.value : '',
|
||||
const getContentSnapshot = (
|
||||
description: string,
|
||||
coverPicValue: string,
|
||||
coverVideoValue: string,
|
||||
) => ({
|
||||
description: description.trim(),
|
||||
coverPic: splitUrls(coverPicValue).join(','),
|
||||
coverVideo: splitUrls(coverVideoValue).join(','),
|
||||
});
|
||||
|
||||
const loadMoment = async (id: string) => {
|
||||
loadingMoment.value = true;
|
||||
try {
|
||||
const { code, data, msg } = await RecentApi.getMomentForEdit(id);
|
||||
if (code !== 0 || !data) {
|
||||
toast.error({ msg: msg || '动态加载失败', duration: 3000 });
|
||||
setTimeout(back, 1000);
|
||||
return;
|
||||
}
|
||||
if (data.auditStatus !== 2) {
|
||||
toast.error({ msg: '仅审核不通过的动态可以修改后重新发布', duration: 3000 });
|
||||
setTimeout(back, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
const picUrls = splitUrls(data.coverPic);
|
||||
const videoUrls = splitUrls(data.coverVideo);
|
||||
Object.assign(params, {
|
||||
coverPic: picUrls,
|
||||
coverVideo: videoUrls,
|
||||
description: data.description || '',
|
||||
issueLong: data.issueLong ?? '',
|
||||
issueLat: data.issueLat ?? '',
|
||||
issueProvince: data.issueProvince || '',
|
||||
issueCity: data.issueCity || '',
|
||||
issueDistrict: data.issueDistrict || '',
|
||||
issueAddress: data.issueAddress || '',
|
||||
});
|
||||
if (code !== 0)
|
||||
return toast.error({
|
||||
imageFiles.value = toUploadFiles(picUrls);
|
||||
videoFiles.value = toUploadFiles(videoUrls);
|
||||
activeTab.value = videoUrls.length ? 1 : 0;
|
||||
originalContent.value = getContentSnapshot(
|
||||
params.description,
|
||||
picUrls.join(','),
|
||||
videoUrls.join(','),
|
||||
);
|
||||
} catch (error: any) {
|
||||
toast.error({ msg: error?.message || '动态加载失败', duration: 3000 });
|
||||
setTimeout(back, 1000);
|
||||
} finally {
|
||||
loadingMoment.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onLoad((options: Record<string, string> = {}) => {
|
||||
if (!options.id) return;
|
||||
recentId.value = options.id;
|
||||
loadMoment(options.id);
|
||||
});
|
||||
|
||||
// 发布朋友圈
|
||||
const publishMoments = async () => {
|
||||
if (publishing.value || loadingMoment.value) return;
|
||||
if (!params.description.trim()) return uni.showToast({ title: '请填写内容', icon: 'error' });
|
||||
if (activeTab.value === 0 && (!params.coverPic || !params.coverPic.length))
|
||||
return uni.showToast({ title: '请上传图片', icon: 'error' });
|
||||
if (activeTab.value === 1 && (!params.coverVideo || !params.coverVideo.length))
|
||||
return uni.showToast({ title: '请上传视频', icon: 'error' });
|
||||
|
||||
publishing.value = true;
|
||||
try {
|
||||
const submitCoverPic = activeTab.value === 0 ? coverPic.value : '';
|
||||
const submitCoverVideo = activeTab.value === 1 ? coverVideo.value : '';
|
||||
if (isEditMode.value) {
|
||||
const currentContent = getContentSnapshot(
|
||||
params.description,
|
||||
submitCoverPic,
|
||||
submitCoverVideo,
|
||||
);
|
||||
if (
|
||||
currentContent.description === originalContent.value.description &&
|
||||
currentContent.coverPic === originalContent.value.coverPic &&
|
||||
currentContent.coverVideo === originalContent.value.coverVideo
|
||||
) {
|
||||
uni.showToast({ title: '请先修改动态内容', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEditMode.value || !params.issueLat || !params.issueLong) {
|
||||
const location = await getCurrentLocation();
|
||||
params.issueLat = String(location.latitude);
|
||||
params.issueLong = String(location.longitude);
|
||||
}
|
||||
|
||||
const submitData = {
|
||||
...params,
|
||||
coverPic: submitCoverPic,
|
||||
coverVideo: submitCoverVideo,
|
||||
};
|
||||
const { code, msg } = isEditMode.value
|
||||
? await RecentApi.updateAndResubmitMoment({ ...submitData, id: recentId.value })
|
||||
: await RecentApi.createMoment(submitData);
|
||||
if (code !== 0) {
|
||||
toast.error({
|
||||
msg,
|
||||
duration: 5000,
|
||||
direction: 'vertical',
|
||||
position: 'middle',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success({
|
||||
msg: '发布成功',
|
||||
msg: isEditMode.value ? '已重新提交审核' : '已提交审核,审核通过后将展示',
|
||||
duration: 5000,
|
||||
direction: 'vertical',
|
||||
position: 'middle',
|
||||
});
|
||||
setTimeout(() => {
|
||||
back();
|
||||
}, 800);
|
||||
},
|
||||
fail: (err) => {
|
||||
}, 1200);
|
||||
} catch (error: any) {
|
||||
toast.error({
|
||||
msg: '发布失败,位置信息获取失败,请打开手机系统定位',
|
||||
msg: isLocationError(error)
|
||||
? `发布失败,${getLocationErrorMessage(error)}`
|
||||
: error?.message || '提交失败,请稍后重试',
|
||||
duration: 5000,
|
||||
direction: 'vertical',
|
||||
position: 'middle',
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
} finally {
|
||||
publishing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 返回
|
||||
function back() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
// 返回
|
||||
function back() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="关于" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="历史记录" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -53,4 +54,4 @@ const { listData, loadMoreStatus, refreshList, listLoadMore } = useScroll(
|
||||
onShow(() => {
|
||||
refreshList();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="开通VIP" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -140,6 +141,7 @@ import PayOrderApi from '@/sheep/api/pay/order';
|
||||
// @ts-ignore
|
||||
import sheep from '@/sheep';
|
||||
import { jumpToWechatMiniProgram, wechatNavBack } from '@/sheep/util/wechat-tool';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
import appConfig from '@/sheep/config';
|
||||
|
||||
const messageBox = useMessage();
|
||||
@@ -181,6 +183,7 @@ const nodes = ref(''); // 会员协议文本
|
||||
const payMethod = ref<'alipay' | 'wechat'>('alipay'); // 支付方式:alipay / wechat
|
||||
const openTypeList = ref<OpenTypeItem[]>([]); // 会员类型列表
|
||||
const curOpenTypeId = ref<number>();
|
||||
const isAutoLoggingIn = ref(false);
|
||||
// 当前选择的会员类型
|
||||
const currentOpenType = computed<OpenTypeItem | undefined>(() =>
|
||||
openTypeList.value?.find((item) => item.id == curOpenTypeId.value),
|
||||
@@ -189,8 +192,36 @@ const isLogin = computed(() => sheep.$store('user').isLogin);
|
||||
|
||||
onLoad(async (params) => {
|
||||
console.log('onLoad:', params);
|
||||
// #ifdef MP-WEIXIN
|
||||
if (params?.loginCode) {
|
||||
isAutoLoggingIn.value = true;
|
||||
let loginCompleted = false;
|
||||
try {
|
||||
const { code } = await AuthUtil.miniProgramLogin(params.loginCode);
|
||||
if (code !== 0) {
|
||||
throw new Error('小程序自动登录失败');
|
||||
}
|
||||
loginCompleted = true;
|
||||
uni.removeStorageSync('openid');
|
||||
const bindSuccess = await sheep.$platform.useProvider('wechat').bind();
|
||||
if (!bindSuccess) {
|
||||
throw new Error('微信自动绑定失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('|小程序自动登录及绑定|意外的错误error:', error);
|
||||
if (loginCompleted) {
|
||||
uni.showToast({ title: '微信自动绑定失败,请重试', icon: 'none' });
|
||||
} else {
|
||||
authDialog();
|
||||
}
|
||||
return;
|
||||
} finally {
|
||||
isAutoLoggingIn.value = false;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const { data = null } = await RecentApi.getMemberOpenTypeList();
|
||||
openTypeList.value = data ?? [];
|
||||
@@ -199,16 +230,19 @@ onLoad(async (params) => {
|
||||
}
|
||||
if (params) {
|
||||
if (params.openTypeId && params.payMethod) {
|
||||
payMethod.value = params.payMethod;
|
||||
curOpenTypeId.value = params.openTypeId;
|
||||
payMethod.value = params.payMethod as 'alipay' | 'wechat';
|
||||
curOpenTypeId.value = Number(params.openTypeId);
|
||||
handleOpenVip();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
if (isAutoLoggingIn.value) {
|
||||
return;
|
||||
}
|
||||
if (!isLogin.value) {
|
||||
authDialog()
|
||||
authDialog();
|
||||
} else {
|
||||
getChatConfig();
|
||||
}
|
||||
@@ -278,30 +312,37 @@ const handleOpenVip = () => {
|
||||
msg: `确认使用${payMethodItem.title}${fen2yuan(currentOpenType.value?.preferentialPrice)}元,开通${currentOpenType.value?.title}会员吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
if (appConfig.buildTarget === 'H5') {
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(curOpenTypeId.value);
|
||||
} else {
|
||||
disposeAlipayPay(curOpenTypeId.value);
|
||||
}
|
||||
} else {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(curOpenTypeId.value);
|
||||
} else {
|
||||
disposeAlipayPay(curOpenTypeId.value);
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
jumpToWechatPage();
|
||||
// #endif
|
||||
if (appConfig.paymentBuildTargets[payMethod.value] === 'H5') {
|
||||
disposePay(curOpenTypeId.value, true);
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
disposePay(curOpenTypeId.value);
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
jumpToWechatPage();
|
||||
// #endif
|
||||
})
|
||||
.catch(() => { });
|
||||
};
|
||||
|
||||
const disposePay = (openTypeId: any, forceH5Pay = false) => {
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(openTypeId, forceH5Pay);
|
||||
} else {
|
||||
disposeAlipayPay(openTypeId, forceH5Pay);
|
||||
}
|
||||
};
|
||||
|
||||
const executePay = (payOrderId: any, forceH5Pay: boolean) => {
|
||||
if (forceH5Pay) {
|
||||
return sheep.$platform.payH5(payMethod.value, 'recharge', payOrderId);
|
||||
}
|
||||
return sheep.$platform.pay(payMethod.value, 'recharge', payOrderId);
|
||||
};
|
||||
|
||||
// 处理支付宝支付
|
||||
const disposeAlipayPay = async (openTypeId: any) => {
|
||||
const disposeAlipayPay = async (openTypeId: any, forceH5Pay = false) => {
|
||||
uni.showLoading({ title: '支付中...', mask: true });
|
||||
try {
|
||||
const { code, data } = await RecentApi.memberOpenTypePay({
|
||||
@@ -312,7 +353,7 @@ const disposeAlipayPay = async (openTypeId: any) => {
|
||||
uni.showToast({ title: '创建订单失败', icon: 'none' });
|
||||
throw new Error('创建订单失败');
|
||||
}
|
||||
await sheep.$platform.pay(payMethod.value, 'recharge', data);
|
||||
await executePay(data, forceH5Pay);
|
||||
// 更新用户信息
|
||||
$store('user').getInfo();
|
||||
uni.showToast({ title: '创建订单成功', icon: 'success' });
|
||||
@@ -322,7 +363,7 @@ const disposeAlipayPay = async (openTypeId: any) => {
|
||||
}
|
||||
|
||||
// 处理微信支付
|
||||
const disposeWechatPay = async (openTypeId: any) => {
|
||||
const disposeWechatPay = async (openTypeId: any, forceH5Pay = false) => {
|
||||
uni.showLoading({ title: '支付中...', mask: true });
|
||||
try {
|
||||
const { code, data } = await RecentApi.memberOpenTypePay({
|
||||
@@ -333,7 +374,7 @@ const disposeWechatPay = async (openTypeId: any) => {
|
||||
uni.showToast({ title: '创建订单失败', icon: 'none' });
|
||||
throw new Error('创建订单失败');
|
||||
}
|
||||
await sheep.$platform.pay(payMethod.value, 'recharge', data);
|
||||
await executePay(data, forceH5Pay);
|
||||
// 更新用户信息
|
||||
$store('user').getInfo();
|
||||
uni.showToast({ title: '支付成功', icon: 'success' });
|
||||
@@ -345,9 +386,15 @@ const disposeWechatPay = async (openTypeId: any) => {
|
||||
// 跳转到微信小程序
|
||||
const jumpToWechatPage = async () => {
|
||||
try {
|
||||
const { code, data: loginCode } = await AuthUtil.createMiniProgramLoginCode();
|
||||
if (code !== 0 || !loginCode) {
|
||||
uni.showToast({ title: '获取小程序登录凭证失败', icon: 'none' });
|
||||
throw new Error('获取小程序登录凭证失败');
|
||||
}
|
||||
const url = await jumpToWechatMiniProgram('openVip', {
|
||||
openTypeId: curOpenTypeId.value,
|
||||
payMethod: payMethod.value,
|
||||
loginCode,
|
||||
});
|
||||
if (!url) {
|
||||
uni.showToast({ title: '获取小程序短链失败', icon: 'none' });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="黑名单" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="精英男士认证" safeAreaInsetTop fixed placeholder :bordered="false" style="z-index: 999">
|
||||
<wd-navbar
|
||||
title="精英男士认证"
|
||||
safeAreaInsetTop
|
||||
fixed
|
||||
placeholder
|
||||
:bordered="false"
|
||||
style="z-index: 999"
|
||||
>
|
||||
<template #left>
|
||||
<navigator open-type="navigateBack" hover-class="navigator-hover">
|
||||
<uni-icons type="back" size="30" color="#D29E52" />
|
||||
@@ -13,16 +21,24 @@
|
||||
<view class="text-sm my-2 mx-8 c-#949494">上传本人头像,女生更放心与你见面~</view>
|
||||
<view class="h-60rpx"></view>
|
||||
<view class="flex-col-center">
|
||||
<s-uploader v-model:url="userinfo.avatar" fileMediatype="image" limit="1" mode="grid" :imageStyles="{
|
||||
width: '268rpx',
|
||||
height: '268rpx',
|
||||
border: { width: '6rpx', color: '#D29E52', radius: '50rpx' },
|
||||
}" />
|
||||
<s-uploader
|
||||
v-model:url="userinfo.avatar"
|
||||
fileMediatype="image"
|
||||
limit="1"
|
||||
mode="grid"
|
||||
:imageStyles="{
|
||||
width: '268rpx',
|
||||
height: '268rpx',
|
||||
border: { width: '6rpx', color: '#D29E52', radius: '50rpx' },
|
||||
}"
|
||||
/>
|
||||
<image class="w-260rpx" src="@/static/face-auth-prompt.png" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="mx-auto w-200rpx rounded-full p-3 text-center bg-color-theme-dark active:bg-color-theme"
|
||||
@click="createFaceVerify">
|
||||
<view
|
||||
class="mx-auto w-200rpx rounded-full p-3 text-center bg-color-theme-dark active:bg-color-theme"
|
||||
@click="createFaceVerify"
|
||||
>
|
||||
开始认证
|
||||
</view>
|
||||
|
||||
@@ -34,68 +50,68 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
// @ts-ignore
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
import { reactive } from 'vue';
|
||||
import verify from './ali-face-verify';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
// @ts-ignore
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
import { reactive } from 'vue';
|
||||
import verify from './ali-face-verify';
|
||||
|
||||
// 用户信息
|
||||
const userinfo = reactive<any>({
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
birthday: '',
|
||||
height: '',
|
||||
weight: '',
|
||||
occ: '',
|
||||
description: '',
|
||||
photo: [],
|
||||
});
|
||||
|
||||
onLoad(async () => {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
const { data } = await UserApi.getUserInfo();
|
||||
Object.assign(userinfo, {
|
||||
avatar: data.avatar,
|
||||
birthday: data.birthday ?? '',
|
||||
description: data.description,
|
||||
height: data.height ?? '',
|
||||
weight: data.weight ?? '',
|
||||
mobile: data.mobile,
|
||||
nickname: data.nickname,
|
||||
occ: data.occ ?? '',
|
||||
sex: data.sex,
|
||||
// 用户信息
|
||||
const userinfo = reactive<any>({
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
birthday: '',
|
||||
height: '',
|
||||
weight: '',
|
||||
occ: '',
|
||||
description: '',
|
||||
photo: [],
|
||||
});
|
||||
uni.hideLoading();
|
||||
});
|
||||
|
||||
// 创建人脸认证
|
||||
const createFaceVerify = async () => {
|
||||
// #ifndef APP-PLUS
|
||||
uni.showToast({ title: '当前设备不支持', icon: 'error' });
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
if (!userinfo.avatar) return uni.showToast({ title: '请先上传人脸照片', icon: 'error' });
|
||||
|
||||
const aliyunVerify = uni.requireNativePlugin('AP-FaceDetectModule');
|
||||
const metaInfo = JSON.stringify(aliyunVerify.getMetaInfo());
|
||||
|
||||
// 创建男生人脸认证
|
||||
const { code, data } = await RecentApi.createBoyFaceVerify({
|
||||
authPic: userinfo.avatar,
|
||||
metaInfoJson: metaInfo,
|
||||
onLoad(async () => {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
const { data } = await UserApi.getUserInfo();
|
||||
Object.assign(userinfo, {
|
||||
avatar: data.avatar,
|
||||
birthday: data.birthday ?? '',
|
||||
description: data.description,
|
||||
height: data.height ?? '',
|
||||
weight: data.weight ?? '',
|
||||
mobile: data.mobile,
|
||||
nickname: data.nickname,
|
||||
occ: data.occ ?? '',
|
||||
sex: data.sex,
|
||||
});
|
||||
uni.hideLoading();
|
||||
});
|
||||
console.log('男 创建人脸认证接口responseData:', code, data);
|
||||
|
||||
if (code !== 0)
|
||||
return uni.showToast({ title: '请上传包含人脸的头像', icon: 'none', duration: 6000 });
|
||||
// 创建人脸认证
|
||||
const createFaceVerify = async () => {
|
||||
// #ifndef APP-PLUS
|
||||
return uni.showToast({ title: '人脸认证仅支持 App', icon: 'none' });
|
||||
// #endif
|
||||
|
||||
const { code: userResCode } = await UserApi.updateUser(userinfo);
|
||||
if (userResCode !== 0) return;
|
||||
// #ifdef APP-PLUS
|
||||
if (!userinfo.avatar) return uni.showToast({ title: '请先上传人脸照片', icon: 'error' });
|
||||
|
||||
verify(data.bizId, data.id);
|
||||
// #endif
|
||||
};
|
||||
const aliyunVerify = uni.requireNativePlugin('AP-FaceDetectModule');
|
||||
const metaInfo = JSON.stringify(aliyunVerify.getMetaInfo());
|
||||
|
||||
// 创建男生人脸认证
|
||||
const { code, data } = await RecentApi.createBoyFaceVerify({
|
||||
authPic: userinfo.avatar,
|
||||
metaInfoJson: metaInfo,
|
||||
});
|
||||
console.log('男 创建人脸认证接口responseData:', code, data);
|
||||
|
||||
if (code !== 0)
|
||||
return uni.showToast({ title: '请上传包含人脸的头像', icon: 'none', duration: 6000 });
|
||||
|
||||
const { code: userResCode } = await UserApi.updateUser(userinfo);
|
||||
if (userResCode !== 0) return;
|
||||
|
||||
verify(data.bizId, data.id);
|
||||
// #endif
|
||||
};
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="魅力女生认证" safeAreaInsetTop placeholder :bordered="false" style="z-index: 999">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="魅力女生认证" safeAreaInsetTop placeholder :bordered="false" style="z-index: 999">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="魅力女生认证" safeAreaInsetTop placeholder :bordered="false" style="z-index: 999">
|
||||
<template #left>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="魅力女生认证" safeAreaInsetTop placeholder :bordered="false" style="z-index: 999">
|
||||
<wd-navbar
|
||||
title="魅力女生认证"
|
||||
safeAreaInsetTop
|
||||
placeholder
|
||||
:bordered="false"
|
||||
style="z-index: 999"
|
||||
>
|
||||
<template #left>
|
||||
<navigator open-type="navigateBack" hover-class="navigator-hover">
|
||||
<uni-icons type="back" size="30" color="#D29E52" />
|
||||
@@ -18,29 +25,54 @@
|
||||
<view class="text-sm my-2 mx-8 c-#949494">上传清晰的五官头像,让更多用户发现你~</view>
|
||||
<view class="h-60rpx"></view>
|
||||
<view class="mx-8 flex items-center">
|
||||
<s-uploader class="flex-1" v-model:url="userinfo.avatar" fileMediatype="image" limit="1" mode="grid"
|
||||
<s-uploader
|
||||
class="flex-1"
|
||||
v-model:url="userinfo.avatar"
|
||||
fileMediatype="image"
|
||||
limit="1"
|
||||
mode="grid"
|
||||
:imageStyles="{
|
||||
width: '268rpx',
|
||||
height: '268rpx',
|
||||
border: { width: '6rpx', color: '#D29E52', radius: '50rpx' },
|
||||
}" />
|
||||
}"
|
||||
/>
|
||||
<view class="flex-1 flex-col-center">
|
||||
<image class="w-160rpx h-180rpx" src="@/static/face-auth-prompt.png" mode="aspectFit" />
|
||||
<view class="text-xs text-color-theme">确保本人头像</view>
|
||||
<view class="text-xs text-color-theme">才能通过审核哦</view>
|
||||
</view>
|
||||
</view>
|
||||
<wd-datetime-picker v-model="userinfo.birthday" label="生日" title="生日" type="date" align-right
|
||||
:default-value="Date.now()" :min-date="new Date(1980, 1, 1).getTime()" :max-date="Date.now()">
|
||||
<wd-datetime-picker
|
||||
v-model="userinfo.birthday"
|
||||
label="生日"
|
||||
title="生日"
|
||||
type="date"
|
||||
align-right
|
||||
:default-value="Date.now()"
|
||||
:min-date="new Date(1980, 1, 1).getTime()"
|
||||
:max-date="Date.now()"
|
||||
>
|
||||
<capsule-cell title="生日" :value="formatDate(userinfo.birthday, 'YYYY-MM-DD')" />
|
||||
</wd-datetime-picker>
|
||||
<input class="h-100rpx mx-8 my-4 rounded-50rpx bg-color-light px-40rpx text-32rpx text-color-theme"
|
||||
v-model="userinfo.nickname" type="text" placeholder="请输入昵称" placeholder-style="color: rgba(255, 255, 255, 0.6)"
|
||||
:maxlength="10" />
|
||||
<capsule-cell title="位置权限" :value="locationAuth ? '已允许' : '未允许'" @click="openLocationAuth" />
|
||||
<input
|
||||
class="h-100rpx mx-8 my-4 rounded-50rpx bg-color-light px-40rpx text-32rpx text-color-theme"
|
||||
v-model="userinfo.nickname"
|
||||
type="text"
|
||||
placeholder="请输入昵称"
|
||||
placeholder-style="color: rgba(255, 255, 255, 0.6)"
|
||||
:maxlength="10"
|
||||
/>
|
||||
<capsule-cell
|
||||
title="位置权限"
|
||||
:value="locationAuth ? '已允许' : '未允许'"
|
||||
@click="openLocationAuth"
|
||||
/>
|
||||
|
||||
<view class="mx-auto mt-6 w-200rpx rounded-full p-3 text-center bg-color-theme-dark active:bg-color-theme"
|
||||
@click="createFaceVerify">
|
||||
<view
|
||||
class="mx-auto mt-6 w-200rpx rounded-full p-3 text-center bg-color-theme-dark active:bg-color-theme"
|
||||
@click="createFaceVerify"
|
||||
>
|
||||
开始认证
|
||||
</view>
|
||||
</view>
|
||||
@@ -48,112 +80,125 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import CapsuleCell from '../components/capsule-cell.vue';
|
||||
// @ts-ignore
|
||||
import { formatDate } from '@/sheep/helper/utils';
|
||||
import { reactive, ref } from 'vue';
|
||||
// @ts-ignore
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import verify from './ali-face-verify';
|
||||
// @ts-ignore
|
||||
import checkOpenGPSService from './openSettings.js';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import CapsuleCell from '../components/capsule-cell.vue';
|
||||
// @ts-ignore
|
||||
import { formatDate } from '@/sheep/helper/utils';
|
||||
import { reactive, ref } from 'vue';
|
||||
// @ts-ignore
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import verify from './ali-face-verify';
|
||||
import {
|
||||
type CurrentLocation,
|
||||
getCurrentLocation,
|
||||
getLocationErrorMessage,
|
||||
} from '@/sheep/util/location';
|
||||
|
||||
// 用户信息
|
||||
const userinfo = reactive<any>({
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
birthday: '',
|
||||
height: '',
|
||||
weight: '',
|
||||
occ: '',
|
||||
description: '',
|
||||
photo: [],
|
||||
});
|
||||
const locationAuth = ref(false);
|
||||
|
||||
onLoad(async () => {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
const { data } = await UserApi.getUserInfo();
|
||||
Object.assign(userinfo, {
|
||||
avatar: data.avatar,
|
||||
birthday: data.birthday ?? '',
|
||||
description: data.description,
|
||||
height: data.height ?? '',
|
||||
weight: data.weight ?? '',
|
||||
mobile: data.mobile,
|
||||
nickname: data.nickname,
|
||||
wechatNum: data.wechatNum ?? '',
|
||||
occ: data.occ ?? '',
|
||||
sex: data.sex,
|
||||
// 用户信息
|
||||
const userinfo = reactive<any>({
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
birthday: '',
|
||||
height: '',
|
||||
weight: '',
|
||||
occ: '',
|
||||
description: '',
|
||||
photo: [],
|
||||
});
|
||||
uni.hideLoading();
|
||||
const locationAuth = ref(false);
|
||||
|
||||
checkOpenGPSService();
|
||||
});
|
||||
onLoad(async () => {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
const { data } = await UserApi.getUserInfo();
|
||||
Object.assign(userinfo, {
|
||||
avatar: data.avatar,
|
||||
birthday: data.birthday ?? '',
|
||||
description: data.description,
|
||||
height: data.height ?? '',
|
||||
weight: data.weight ?? '',
|
||||
mobile: data.mobile,
|
||||
nickname: data.nickname,
|
||||
wechatNum: data.wechatNum ?? '',
|
||||
occ: data.occ ?? '',
|
||||
sex: data.sex,
|
||||
});
|
||||
uni.hideLoading();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
uni.getLocation({
|
||||
type: 'wgs84',
|
||||
success: async (res) => {
|
||||
locationAuth.value = true;
|
||||
const params = {
|
||||
lat: String(res.latitude),
|
||||
lon: String(res.longitude),
|
||||
province: res.address?.province ?? '',
|
||||
city: res.address?.city ?? '',
|
||||
district: res.address?.district ?? '',
|
||||
address: res.address?.street ?? '',
|
||||
};
|
||||
await RecentApi.updateUserLocation(params);
|
||||
},
|
||||
fail: () => {
|
||||
onShow(async () => {
|
||||
await syncCurrentLocation(false);
|
||||
});
|
||||
|
||||
const syncCurrentLocation = async (showError: boolean) => {
|
||||
let location: CurrentLocation;
|
||||
try {
|
||||
location = await getCurrentLocation({ type: 'wgs84', geocode: true });
|
||||
} catch (error) {
|
||||
locationAuth.value = false;
|
||||
},
|
||||
});
|
||||
});
|
||||
if (showError) {
|
||||
uni.showToast({ title: getLocationErrorMessage(error), icon: 'none', duration: 5000 });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const openLocationAuth = () => {
|
||||
if (locationAuth.value) return;
|
||||
checkOpenGPSService();
|
||||
};
|
||||
locationAuth.value = true;
|
||||
try {
|
||||
await RecentApi.updateUserLocation({
|
||||
lat: String(location.latitude),
|
||||
lon: String(location.longitude),
|
||||
province: location.address?.province ?? '',
|
||||
city: location.address?.city ?? '',
|
||||
district: location.address?.district ?? '',
|
||||
address: location.address?.street ?? '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('同步用户位置失败:', error);
|
||||
if (showError) {
|
||||
uni.showToast({ title: '位置已获取,但同步失败,请稍后重试', icon: 'none' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const createFaceVerify = async () => {
|
||||
// #ifndef APP-PLUS
|
||||
uni.showToast({ title: '当前设备不支持', icon: 'error' });
|
||||
// #endif
|
||||
const openLocationAuth = async () => {
|
||||
if (locationAuth.value) return;
|
||||
await syncCurrentLocation(true);
|
||||
};
|
||||
|
||||
if (!userinfo.birthday || !userinfo.nickname)
|
||||
return uni.showToast({ title: '请完善信息', icon: 'none' });
|
||||
if (!locationAuth.value) return uni.showToast({ title: '请先授权位置权限', icon: 'none' });
|
||||
const createFaceVerify = async () => {
|
||||
// #ifndef APP-PLUS
|
||||
return uni.showToast({ title: '人脸认证仅支持 App', icon: 'none' });
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
if (!userinfo.avatar) return uni.showToast({ title: '请先上传人脸照片', icon: 'error' });
|
||||
if (!userinfo.birthday || !userinfo.nickname)
|
||||
return uni.showToast({ title: '请完善信息', icon: 'none' });
|
||||
if (!locationAuth.value) return uni.showToast({ title: '请先授权位置权限', icon: 'none' });
|
||||
|
||||
console.log("userinfo:", userinfo.wechatNum);
|
||||
// #ifdef APP-PLUS
|
||||
if (!userinfo.avatar) return uni.showToast({ title: '请先上传人脸照片', icon: 'error' });
|
||||
|
||||
console.log('userinfo:', userinfo.wechatNum);
|
||||
|
||||
const aliyunVerify = uni.requireNativePlugin('AP-FaceDetectModule');
|
||||
const metaInfo = JSON.stringify(aliyunVerify.getMetaInfo());
|
||||
const aliyunVerify = uni.requireNativePlugin('AP-FaceDetectModule');
|
||||
const metaInfo = JSON.stringify(aliyunVerify.getMetaInfo());
|
||||
|
||||
// 创建女生人脸认证
|
||||
const { code, data, msg } = await RecentApi.createGirlFaceVerify({
|
||||
memberHeight: userinfo.height,
|
||||
memberWeight: userinfo.weight,
|
||||
memberWechatNumber: userinfo.wechatNum,
|
||||
memberOcc: userinfo.occ,
|
||||
memberDesc: userinfo.description,
|
||||
authPic: userinfo.avatar,
|
||||
metaInfoJson: metaInfo,
|
||||
});
|
||||
console.log('女 创建人脸认证接口responseData:', code, JSON.stringify(data));
|
||||
// 创建女生人脸认证
|
||||
const { code, data, msg } = await RecentApi.createGirlFaceVerify({
|
||||
memberHeight: userinfo.height,
|
||||
memberWeight: userinfo.weight,
|
||||
memberWechatNumber: userinfo.wechatNum,
|
||||
memberOcc: userinfo.occ,
|
||||
memberDesc: userinfo.description,
|
||||
authPic: userinfo.avatar,
|
||||
metaInfoJson: metaInfo,
|
||||
});
|
||||
console.log('女 创建人脸认证接口responseData:', code, JSON.stringify(data));
|
||||
|
||||
if (code !== 0) {
|
||||
return uni.showToast({ title: msg ?? '请上传包含人脸的头像', icon: 'none', duration: 6000 });
|
||||
}
|
||||
if (code !== 0) {
|
||||
return uni.showToast({ title: msg ?? '请上传包含人脸的头像', icon: 'none', duration: 6000 });
|
||||
}
|
||||
|
||||
verify(data.bizId, data.id);
|
||||
// #endif
|
||||
};
|
||||
verify(data.bizId, data.id);
|
||||
// #endif
|
||||
};
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark" :theme-vars="themeVars">
|
||||
<wd-navbar title="问题反馈" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark" :theme-vars="{ darkBackground2: '#1b1b1b' }">
|
||||
<wd-navbar :title="curStep === 0 ? '选择性别' : '完善资料'" fixed safeAreaInsetTop :bordered="false"></wd-navbar>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider class="h-full flex-col" theme="dark">
|
||||
<wd-navbar title="" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="我的动态" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -15,9 +16,14 @@
|
||||
</template>
|
||||
</wd-navbar>
|
||||
|
||||
<scroll-list style="height: calc(100dvh - 44px - var(--status-bar-height, 0px))" :loadMoreStatus="loadMoreStatus" @refresh="refreshList"
|
||||
@loadMore="listLoadMore">
|
||||
<moment-card v-for="item in listData" :data="item" :showChat="false" :showLike="false" />
|
||||
<wd-tabs v-model="activeTab" color="#D29E52" inactive-color="#A8A8A8" :animated="false" @change="handleTabChange">
|
||||
<wd-tab v-for="tab in auditTabs" :key="tab.label" :title="tab.label" />
|
||||
</wd-tabs>
|
||||
|
||||
<scroll-list style="height: calc(100dvh - 44px - 50px - var(--status-bar-height, 0px))"
|
||||
:loadMoreStatus="loadMoreStatus" @refresh="refreshList" @loadMore="listLoadMore">
|
||||
<moment-card v-for="item in listData" :key="item.id" :data="item" :showChat="false" :showLike="false"
|
||||
:showAuditStatus="true" :showResubmit="true" />
|
||||
</scroll-list>
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
@@ -27,11 +33,31 @@ import MomentCard from '@/sheep/components/moment-card/moment-card.vue';
|
||||
import ScrollList from '@/sheep/components/scroll-list/scroll-list.vue';
|
||||
import useScroll from '@/sheep/hooks/use-scroll';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const { listData, loadMoreStatus, refreshList, listLoadMore } = useScroll(RecentApi.myMomentList);
|
||||
const activeTab = ref(0);
|
||||
const auditTabs = [
|
||||
{ label: '全部', auditStatus: undefined },
|
||||
{ label: '审核中', auditStatus: 0 },
|
||||
{ label: '已通过', auditStatus: 1 },
|
||||
{ label: '已拒绝', auditStatus: 2 },
|
||||
];
|
||||
const queryParams = reactive<{ auditStatus?: number }>({
|
||||
auditStatus: undefined,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const { listData, loadMoreStatus, refreshList, listLoadMore } = useScroll(
|
||||
RecentApi.myMomentList,
|
||||
queryParams,
|
||||
);
|
||||
|
||||
const handleTabChange = ({ index }: { index: number }) => {
|
||||
queryParams.auditStatus = auditTabs[index]?.auditStatus;
|
||||
refreshList();
|
||||
};
|
||||
|
||||
onShow(() => {
|
||||
refreshList();
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
<!-- 支付结果页面 -->
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="支付结果" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="历史记录" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="充值" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -108,6 +109,7 @@ import sheep from '@/sheep';
|
||||
// @ts-ignore
|
||||
import PayOrderApi from '@/sheep/api/pay/order';
|
||||
import { jumpToWechatMiniProgram, wechatNavBack } from '@/sheep/util/wechat-tool';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
import appConfig from '@/sheep/config';
|
||||
|
||||
const messageBox = useMessage();
|
||||
@@ -133,17 +135,46 @@ const rechargePackag = ref<{ id: number; name: string; payPrice: number; bonusPr
|
||||
|
||||
const userWallet = computed(() => sheep.$store('user').userWallet);
|
||||
const isLogin = computed(() => sheep.$store('user').isLogin);
|
||||
const isAutoLoggingIn = ref(false);
|
||||
|
||||
onLoad(async (params) => {
|
||||
console.log('onLoad:', params);
|
||||
// #ifdef MP-WEIXIN
|
||||
if (params?.loginCode) {
|
||||
isAutoLoggingIn.value = true;
|
||||
let loginCompleted = false;
|
||||
try {
|
||||
const { code } = await AuthUtil.miniProgramLogin(params.loginCode);
|
||||
if (code !== 0) {
|
||||
throw new Error('小程序自动登录失败');
|
||||
}
|
||||
loginCompleted = true;
|
||||
uni.removeStorageSync('openid');
|
||||
const bindSuccess = await sheep.$platform.useProvider('wechat').bind();
|
||||
if (!bindSuccess) {
|
||||
throw new Error('微信自动绑定失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('|小程序自动登录及绑定|意外的错误error:', error);
|
||||
if (loginCompleted) {
|
||||
uni.showToast({ title: '微信自动绑定失败,请重试', icon: 'none' });
|
||||
} else {
|
||||
authDialog();
|
||||
}
|
||||
return;
|
||||
} finally {
|
||||
isAutoLoggingIn.value = false;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const { data } = await RecentApi.getWalletRechargePackage();
|
||||
rechargePackag.value = data ?? [];
|
||||
if (params) {
|
||||
if (params.packageId && params.payMethod) {
|
||||
payMethod.value = params.payMethod;
|
||||
payMethod.value = params.payMethod as 'alipay' | 'wechat';
|
||||
const item = rechargePackag.value.find((item) => item.id === Number(params.packageId));
|
||||
if (!item) {
|
||||
uni.showToast({ title: '请选择会员类型', icon: 'none' });
|
||||
@@ -155,8 +186,11 @@ onLoad(async (params) => {
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
if (isAutoLoggingIn.value) {
|
||||
return;
|
||||
}
|
||||
if (!isLogin.value) {
|
||||
authDialog()
|
||||
authDialog();
|
||||
} else {
|
||||
// 查询钱包余额
|
||||
getWallet();
|
||||
@@ -198,32 +232,39 @@ const handleRecharge = (item: any) => {
|
||||
.confirm({
|
||||
title: '提示',
|
||||
msg: `确认使用${payMethodItem.title}${fen2yuanSimple(item.payPrice)}元获取${fen2yuanSimple(item.bonusPrice + item.payPrice)}砖石?`,
|
||||
})
|
||||
.then(async () => {
|
||||
if (appConfig.buildTarget === 'H5') {
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(item.id);
|
||||
} else {
|
||||
disposeAlipayPay(item.id);
|
||||
}
|
||||
} else {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(item.id);
|
||||
} else {
|
||||
disposeAlipayPay(item.id);
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
jumpToWechatPage(item.id);
|
||||
// #endif
|
||||
})
|
||||
.then(async () => {
|
||||
if (appConfig.paymentBuildTargets[payMethod.value] === 'H5') {
|
||||
disposePay(item.id, true);
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
disposePay(item.id);
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
jumpToWechatPage(item.id);
|
||||
// #endif
|
||||
})
|
||||
.catch(() => { });
|
||||
};
|
||||
|
||||
// 处理支付宝支付
|
||||
const disposeAlipayPay = async (packageId: any) => {
|
||||
const disposePay = (packageId: any, forceH5Pay = false) => {
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(packageId, forceH5Pay);
|
||||
} else {
|
||||
disposeAlipayPay(packageId, forceH5Pay);
|
||||
}
|
||||
};
|
||||
|
||||
const executePay = (payOrderId: any, forceH5Pay: boolean) => {
|
||||
if (forceH5Pay) {
|
||||
return sheep.$platform.payH5(payMethod.value, 'recharge', payOrderId);
|
||||
}
|
||||
return sheep.$platform.pay(payMethod.value, 'recharge', payOrderId);
|
||||
};
|
||||
|
||||
// 处理支付宝支付
|
||||
const disposeAlipayPay = async (packageId: any, forceH5Pay = false) => {
|
||||
uni.showLoading({ title: '支付中...', mask: true });
|
||||
try {
|
||||
const { code, data } = await RecentApi.walletRecharge({ packageId: packageId });
|
||||
@@ -232,7 +273,7 @@ const disposeAlipayPay = async (packageId: any) => {
|
||||
uni.showToast({ title: '创建订单失败', icon: 'none' });
|
||||
throw new Error();
|
||||
}
|
||||
await sheep.$platform.pay(payMethod.value, 'recharge', data.payOrderId);
|
||||
await executePay(data.payOrderId, forceH5Pay);
|
||||
// 更新用户钱包
|
||||
getWallet();
|
||||
uni.showToast({ title: '创建订单成功', icon: 'success' });
|
||||
@@ -244,7 +285,7 @@ const disposeAlipayPay = async (packageId: any) => {
|
||||
}
|
||||
|
||||
// 处理微信支付
|
||||
const disposeWechatPay = async (packageId: any) => {
|
||||
const disposeWechatPay = async (packageId: any, forceH5Pay = false) => {
|
||||
uni.showLoading({ title: '支付中...', mask: true });
|
||||
try {
|
||||
const { code, data } = await RecentApi.walletRecharge({ packageId: packageId });
|
||||
@@ -253,7 +294,7 @@ const disposeWechatPay = async (packageId: any) => {
|
||||
uni.showToast({ title: '创建订单失败', icon: 'none' });
|
||||
throw new Error();
|
||||
}
|
||||
await sheep.$platform.pay(payMethod.value, 'recharge', data.payOrderId);
|
||||
await executePay(data.payOrderId, forceH5Pay);
|
||||
// 更新用户钱包
|
||||
getWallet();
|
||||
uni.showToast({ title: '支付成功', icon: 'success' });
|
||||
@@ -266,20 +307,30 @@ const disposeWechatPay = async (packageId: any) => {
|
||||
|
||||
// 跳转到微信小程序
|
||||
const jumpToWechatPage = async (packageId: any) => {
|
||||
console.log("跳转到微信小程序");
|
||||
const url = await jumpToWechatMiniProgram('recharge', {
|
||||
packageId,
|
||||
payMethod: payMethod.value,
|
||||
});
|
||||
if (!url) {
|
||||
uni.showToast({ title: '获取小程序短链失败', icon: 'none' });
|
||||
throw new Error('获取小程序短链失败');
|
||||
try {
|
||||
console.log('跳转到微信小程序');
|
||||
const { code, data: loginCode } = await AuthUtil.createMiniProgramLoginCode();
|
||||
if (code !== 0 || !loginCode) {
|
||||
uni.showToast({ title: '获取小程序登录凭证失败', icon: 'none' });
|
||||
throw new Error('获取小程序登录凭证失败');
|
||||
}
|
||||
const url = await jumpToWechatMiniProgram('recharge', {
|
||||
packageId,
|
||||
payMethod: payMethod.value,
|
||||
loginCode,
|
||||
});
|
||||
if (!url) {
|
||||
uni.showToast({ title: '获取小程序短链失败', icon: 'none' });
|
||||
throw new Error('获取小程序短链失败');
|
||||
}
|
||||
// #ifdef H5
|
||||
window.location.href = url
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openURL(url)
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.log('|跳转微信小程序|,意外的错误error:', error);
|
||||
}
|
||||
// #ifdef H5
|
||||
window.location.href = url
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openURL(url)
|
||||
// #endif
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark" :theme-vars="themeVars">
|
||||
<wd-navbar title="设置微信" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="设置" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="上传相册" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="我的账号" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -44,4 +45,4 @@ const writeoff = () => {
|
||||
uni.showToast({ title: '注销成功', icon: 'success' });
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark" :theme-vars="{ darkBackground2: '#1b1b1b' }">
|
||||
<wd-navbar
|
||||
title="修改资料"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="人脸认证" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="访客记录" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
+101
-91
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -13,8 +14,12 @@
|
||||
</view>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<swiper :current="current" style="height: calc(100dvh - 44px - 50px - var(--status-bar-height, 0px))"
|
||||
@change="(e) => (current = e.detail.current)" :duration="200">
|
||||
<swiper
|
||||
:current="current"
|
||||
style="height: calc(100dvh - 44px - 50px - var(--status-bar-height, 0px))"
|
||||
@change="(e) => (current = e.detail.current)"
|
||||
:duration="200"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in segmentedItems" :key="index">
|
||||
<scroll-user :scene="item['search-action']" />
|
||||
</swiper-item>
|
||||
@@ -22,22 +27,30 @@
|
||||
</wd-config-provider>
|
||||
|
||||
<navigator url="/pages/wechat-tabbar/index" open-type="navigate" hover-class="navigator-hover">
|
||||
<image class="size-100rpx fixed right-0 bottom-260" src="@/static/invitation-activity.png" mode="scaleToFill" />
|
||||
<image
|
||||
class="size-100rpx fixed right-0 bottom-260"
|
||||
src="@/static/invitation-activity.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
</navigator>
|
||||
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="!isOpenGPS"
|
||||
<view
|
||||
v-if="!isOpenGPS"
|
||||
class="w-full h-100rpx bg-color-theme-dark fixed bottom-100 left-0 right-0 c-#222 flex items-center px-2"
|
||||
@click="openSystemGPS">
|
||||
@click="openSystemGPS"
|
||||
>
|
||||
<uni-icons type="location-filled" color="black" size="24" />
|
||||
<text class="text-28rpx">点击开启位置权限,匹配您附近的那个Ta~</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef H5 -->
|
||||
<view v-if="!isOpenGPS"
|
||||
<view
|
||||
v-if="!isOpenGPS"
|
||||
class="w-full h-100rpx bg-color-theme-dark fixed bottom-0 left-0 right-0 c-#222 flex items-center px-2"
|
||||
@click="openSystemGPS">
|
||||
@click="openSystemGPS"
|
||||
>
|
||||
<uni-icons type="location-filled" color="black" size="24" />
|
||||
<text class="text-28rpx">点击开启位置权限,匹配您附近的那个Ta~</text>
|
||||
</view>
|
||||
@@ -47,100 +60,97 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import segmented from '@/sheep/components/segmented/segmented.vue';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import ScrollUser from './components/scroll-user.vue';
|
||||
import { requestPermission, gotoAppPermissionSetting } from "@/sheep/util/permission"
|
||||
// @ts-ignore
|
||||
import sheep from '@/sheep';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import { computed, ref } from 'vue';
|
||||
import segmented from '@/sheep/components/segmented/segmented.vue';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import ScrollUser from './components/scroll-user.vue';
|
||||
import {
|
||||
type CurrentLocation,
|
||||
getCurrentLocation,
|
||||
getLocationErrorMessage,
|
||||
} from '@/sheep/util/location';
|
||||
// @ts-ignore
|
||||
import sheep from '@/sheep';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
|
||||
const current = ref(0);
|
||||
const segmentedItems = ref([]);
|
||||
const isOpenGPS = ref(true);
|
||||
const userCity = computed(() => {
|
||||
const selectCity = sheep.$store('user').userCity
|
||||
if (selectCity && selectCity.t) return selectCity
|
||||
const currentLocation = sheep.$store('user')?.userInfo?.currentLocation
|
||||
if (!currentLocation) return null
|
||||
const city = {
|
||||
v: null,
|
||||
t: currentLocation.city,
|
||||
lng: String(currentLocation.lng),
|
||||
lat: String(currentLocation.lat),
|
||||
}
|
||||
sheep.$store('user').setUserCity(city);
|
||||
return city
|
||||
});
|
||||
const current = ref(0);
|
||||
const segmentedItems = ref([]);
|
||||
const isOpenGPS = ref(true);
|
||||
const userCity = computed(() => {
|
||||
const selectCity = sheep.$store('user').userCity;
|
||||
if (selectCity && selectCity.t) return selectCity;
|
||||
const currentLocation = sheep.$store('user')?.userInfo?.currentLocation;
|
||||
if (!currentLocation) return null;
|
||||
const city = {
|
||||
v: null,
|
||||
t: currentLocation.city,
|
||||
lng: String(currentLocation.lng),
|
||||
lat: String(currentLocation.lat),
|
||||
};
|
||||
sheep.$store('user').setUserCity(city);
|
||||
return city;
|
||||
});
|
||||
|
||||
onLoad(async () => {
|
||||
// 获取顶部导航栏tab菜单
|
||||
const { data } = await RecentApi.getSquareTabMenu();
|
||||
if (data && data.length > 0) {
|
||||
segmentedItems.value = data;
|
||||
}
|
||||
getLocation();
|
||||
});
|
||||
onLoad(async () => {
|
||||
// 获取顶部导航栏tab菜单
|
||||
const { data } = await RecentApi.getSquareTabMenu();
|
||||
if (data && data.length > 0) {
|
||||
segmentedItems.value = data;
|
||||
}
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
getLocation();
|
||||
});
|
||||
onShow(async () => {
|
||||
await syncLocation(false);
|
||||
});
|
||||
|
||||
const getLocation = () => {
|
||||
uni.getLocation({
|
||||
geocode: true,
|
||||
success: (res) => {
|
||||
console.log("位置授权:", res);
|
||||
const params = {
|
||||
lat: String(res.latitude),
|
||||
lon: String(res.longitude),
|
||||
province: res.address?.province ?? '',
|
||||
city: res.address?.city ?? '',
|
||||
district: res.address?.district ?? '',
|
||||
address: res.address?.street ?? '',
|
||||
};
|
||||
RecentApi.updateUserLocation(params);
|
||||
isOpenGPS.value = true;
|
||||
},
|
||||
fail: (err) => {
|
||||
const syncLocation = async (showError: boolean) => {
|
||||
let location: CurrentLocation;
|
||||
try {
|
||||
location = await getCurrentLocation({ geocode: true });
|
||||
} catch (error) {
|
||||
isOpenGPS.value = false;
|
||||
},
|
||||
});
|
||||
};
|
||||
if (showError) {
|
||||
uni.showToast({ title: getLocationErrorMessage(error), icon: 'none', duration: 5000 });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const openSystemGPS = async () => {
|
||||
// #ifdef APP-PLUS || MP-WEIXIN
|
||||
const permissionStatus = await requestPermission("location")
|
||||
console.log("APP授权:", permissionStatus);
|
||||
// 已拒绝|受限
|
||||
if (permissionStatus === 'denied') {
|
||||
gotoAppPermissionSetting()
|
||||
}
|
||||
// 已授权
|
||||
if (permissionStatus === 'granted') {
|
||||
isOpenGPS.value = true;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
uni.showToast({ title: '不支持', icon: 'none' });
|
||||
// #endif
|
||||
};
|
||||
try {
|
||||
await RecentApi.updateUserLocation({
|
||||
lat: String(location.latitude),
|
||||
lon: String(location.longitude),
|
||||
province: location.address?.province ?? '',
|
||||
city: location.address?.city ?? '',
|
||||
district: location.address?.district ?? '',
|
||||
address: location.address?.street ?? '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('同步用户位置失败:', error);
|
||||
if (showError) {
|
||||
uni.showToast({ title: '位置已获取,但同步失败,请稍后重试', icon: 'none' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goSelectAddress = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/home/city',
|
||||
events: {
|
||||
changeCity: async () => { },
|
||||
},
|
||||
});
|
||||
};
|
||||
const openSystemGPS = async () => {
|
||||
await syncLocation(true);
|
||||
};
|
||||
|
||||
const goSelectAddress = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/home/city',
|
||||
events: {
|
||||
changeCity: async () => {},
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.wd-navbar {
|
||||
.wd-navbar__title {
|
||||
max-width: 70% !important;
|
||||
.wd-navbar {
|
||||
.wd-navbar__title {
|
||||
max-width: 70% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<view class="tab-page">
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="消息" fixed safeAreaInsetTop :bordered="false">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="" safeAreaInsetTop style="z-index: 999999 !important" :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<!-- 顶部导航 -->
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="我的" safeAreaInsetTop :bordered="false">
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import { getCurrentLocation, getLocationErrorMessage } from '@/sheep/util/location';
|
||||
|
||||
const LONGITUDE = 'user_longitude';
|
||||
const LATITUDE = 'user_latitude';
|
||||
@@ -77,39 +78,33 @@
|
||||
phoneNumber: phone,
|
||||
});
|
||||
};
|
||||
const selfLocation = () => {
|
||||
const saveLocation = (latitude, longitude) => {
|
||||
state.user_latitude = latitude;
|
||||
state.user_longitude = longitude;
|
||||
uni.setStorageSync(LATITUDE, latitude);
|
||||
uni.setStorageSync(LONGITUDE, longitude);
|
||||
};
|
||||
|
||||
const selfLocation = async () => {
|
||||
// #ifdef H5
|
||||
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
|
||||
if (jsWxSdk.isWechat()) {
|
||||
jsWxSdk.getLocation((res) => {
|
||||
state.user_latitude = res.latitude;
|
||||
state.user_longitude = res.longitude;
|
||||
uni.setStorageSync(LATITUDE, res.latitude);
|
||||
uni.setStorageSync(LONGITUDE, res.longitude);
|
||||
saveLocation(res.latitude, res.longitude);
|
||||
getList();
|
||||
});
|
||||
} else {
|
||||
// #endif
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
try {
|
||||
state.user_latitude = res.latitude;
|
||||
state.user_longitude = res.longitude;
|
||||
uni.setStorageSync(LATITUDE, res.latitude);
|
||||
uni.setStorageSync(LONGITUDE, res.longitude);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
getList();
|
||||
},
|
||||
complete: () => {
|
||||
getList();
|
||||
},
|
||||
});
|
||||
// #ifdef H5
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
|
||||
try {
|
||||
const location = await getCurrentLocation({ type: 'gcj02' });
|
||||
saveLocation(location.latitude, location.longitude);
|
||||
} catch (error) {
|
||||
uni.showToast({ title: getLocationErrorMessage(error), icon: 'none', duration: 5000 });
|
||||
} finally {
|
||||
getList();
|
||||
}
|
||||
};
|
||||
const showMaoLocation = (e) => {
|
||||
// #ifdef H5
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<!-- 顶部导航 -->
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="福利中心" safeAreaInsetTop :bordered="false">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="我的" safeAreaInsetTop :bordered="false" />
|
||||
<scroll-view class="h-[calc(100dvh-144rpx)]" scroll-y>
|
||||
@@ -77,4 +78,4 @@ const doLogout = () => {
|
||||
console.log("error:", error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="礼物提现历史记录" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -31,4 +32,4 @@ const { listData, loadMoreStatus, refreshList, listLoadMore } = useScroll(
|
||||
onShow(() => {
|
||||
refreshList();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="礼物账单" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -108,4 +109,4 @@ const tabsChange = ({ index }) => {
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="邀请福利" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -157,4 +158,4 @@ const getTxConfig = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
<!-- 页面 TODO 芋艿:该页面的实现代码需要优化,包括 js 和 css,以及相关的样式设计 -->
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="我的团队" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="邀请海报" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -94,4 +95,4 @@ const savePoster = () => {
|
||||
)
|
||||
};
|
||||
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar :title="pageType == 'yj' ? '分销提现' : '礼物提现'" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
@@ -278,4 +279,4 @@ async function alert() {
|
||||
zIndex: 200,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<page-meta :page-style="$statusBarPageStyle" />
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar title="佣金提现历史记录" safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
|
||||
@@ -77,6 +77,39 @@ const AuthUtil = {
|
||||
},
|
||||
});
|
||||
},
|
||||
// 创建 H5 跳转微信小程序时使用的一次性登录码
|
||||
createMiniProgramLoginCode: () => {
|
||||
return request({
|
||||
url: '/member/auth/mini-program-login-code',
|
||||
method: 'POST',
|
||||
custom: {
|
||||
showLoading: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 微信小程序使用一次性登录码恢复 H5 用户登录态
|
||||
miniProgramLogin: (code) => {
|
||||
return request({
|
||||
url: '/member/auth/mini-program-login',
|
||||
method: 'POST',
|
||||
params: { code },
|
||||
custom: {
|
||||
isToken: false,
|
||||
showLoading: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
// IM 的 accessToken 和 refreshToken 都失效后,使用主站登录态重新签发
|
||||
reissueChatToken: () => {
|
||||
return request({
|
||||
url: '/member/auth/chat-token',
|
||||
method: 'POST',
|
||||
custom: {
|
||||
showLoading: false,
|
||||
showError: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 社交授权的跳转
|
||||
socialAuthRedirect: (type, redirectUri) => {
|
||||
return request({
|
||||
|
||||
@@ -58,6 +58,33 @@ const RecentApi = {
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取本人动态,用于修改后重新发布
|
||||
getMomentForEdit: (recentId: string | number) => {
|
||||
return request({
|
||||
url: '/member/recent/get-recent',
|
||||
method: 'GET',
|
||||
params: { recentId },
|
||||
custom: {
|
||||
showLoading: true,
|
||||
loadingMsg: '加载中',
|
||||
showError: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 修改审核不通过的动态并重新提交审核
|
||||
updateAndResubmitMoment: (data: any) => {
|
||||
return request({
|
||||
url: '/member/recent/update-and-resubmit',
|
||||
method: 'POST',
|
||||
data,
|
||||
custom: {
|
||||
showLoading: true,
|
||||
loadingMsg: '重新发布中',
|
||||
showSuccess: false,
|
||||
showError: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取用户详情页
|
||||
getRecentDetail: (params: { id: string | number }) => {
|
||||
return request({
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<template>
|
||||
<view class="border-1 border-b-solid border-#444444 px-4 py-4">
|
||||
<view class="mb-1 flex items-center" @click="detail">
|
||||
<image :src="member.memberAvatar" class="mr-3 size-120rpx rounded-full bg-#333333" mode="aspectFill" />
|
||||
<image
|
||||
:src="member.memberAvatar"
|
||||
class="mr-3 size-120rpx rounded-full bg-#333333"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="flex justify-between items-center">
|
||||
<view class="flex items-center">
|
||||
<text class="text-base truncate">{{ member?.memberNickname }}</text>
|
||||
<view class="flex my-1 ml-15rpx">
|
||||
<view v-for="(item, index) in data.memberTagList" :key="index">
|
||||
<view v-if="['1'].includes(item.tagBizType)"
|
||||
class="mr-2 flex items-center px-1 text-18rpx text-black rounded-4rpx bg-color-theme-dark">
|
||||
<view
|
||||
v-if="['1'].includes(item.tagBizType)"
|
||||
class="mr-2 flex items-center px-1 text-18rpx text-black rounded-4rpx bg-color-theme-dark"
|
||||
>
|
||||
<wd-icon name="discount-filled" size="10px" color="#000" />
|
||||
<text class="whitespace-nowrap">{{ item.tagTitle }}</text>
|
||||
</view>
|
||||
@@ -29,19 +35,44 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="props.showAuditStatus" class="mb-12rpx pl-140rpx">
|
||||
<view class="audit-status" :style="{ color: auditStatusMeta.color }">
|
||||
<wd-icon :name="auditStatusMeta.icon" size="12px" :color="auditStatusMeta.color" />
|
||||
<text>{{ auditStatusMeta.text }}</text>
|
||||
</view>
|
||||
<view v-if="data.auditStatus === 2 && data.auditReason" class="audit-reason">
|
||||
{{ data.auditReason }}
|
||||
</view>
|
||||
<navigator
|
||||
v-if="data.auditStatus === 2 && props.showResubmit"
|
||||
:url="`/pages/moments/publish?id=${data.id}`"
|
||||
open-type="navigate"
|
||||
hover-class="navigator-hover"
|
||||
class="mt-16rpx inline-flex h-44rpx items-center justify-center rounded-full bg-color-theme px-2 py-1 text-sm c-black"
|
||||
>
|
||||
重新发布
|
||||
</navigator>
|
||||
</view>
|
||||
<view class="mb-30rpx pl-140rpx">
|
||||
<text class="leading-[1.5] text-white">{{ data.description }}</text>
|
||||
<view class="mt-20rpx flex flex-wrap gap-20rpx">
|
||||
<image v-for="(pic, index) in data.coverPic?.split(',')" :key="index" :src="pic"
|
||||
class="size-260rpx rounded-10rpx bg-#333333" mode="aspectFill" @click="previewImage(pic)" />
|
||||
<template v-if="false" v-for="video in data.coverVideo?.split(',')">
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<su-video class="size-260rpx rounded-10rpx bg-#333333" :src="video" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video class="rounded-10rpx" :src="video" />
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
<image
|
||||
v-if="data.coverPic && data.coverPic?.split(',')?.length"
|
||||
v-for="(pic, index) in data.coverPic?.split(',')"
|
||||
:key="index"
|
||||
:src="pic"
|
||||
class="size-260rpx rounded-10rpx bg-#333333"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(pic)"
|
||||
/>
|
||||
<su-video
|
||||
v-for="(video, index) in videoUrls"
|
||||
:key="video"
|
||||
:uid="`${data.id}-${index}`"
|
||||
:height="360"
|
||||
class="w-full rounded-10rpx bg-#333333"
|
||||
:src="video"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mb-4 flex items-center pl-130rpx text-xs text-white/70">
|
||||
@@ -65,112 +96,171 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { formatDate } from '@/sheep/util/index';
|
||||
import { getGender } from '@/sheep/util/index';
|
||||
import useConfigStore from '@/chat-store/configStore.js';
|
||||
import useChatStore from '@/chat-store/chatStore.js';
|
||||
import useFriendStore from '@/chat-store/friendStore.js';
|
||||
import request from '@/common/request';
|
||||
// @ts-nocheck
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { formatDate } from '@/sheep/util/index';
|
||||
import { getGender } from '@/sheep/util/index';
|
||||
import useConfigStore from '@/chat-store/configStore.js';
|
||||
import useChatStore from '@/chat-store/chatStore.js';
|
||||
import useFriendStore from '@/chat-store/friendStore.js';
|
||||
import request from '@/common/request';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: any;
|
||||
showLike?: boolean;
|
||||
showChat?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showLike: true,
|
||||
showChat: true,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<(e: 'detail') => void>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: any;
|
||||
showLike?: boolean;
|
||||
showChat?: boolean;
|
||||
showAuditStatus?: boolean;
|
||||
showResubmit?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showLike: true,
|
||||
showChat: true,
|
||||
showAuditStatus: false,
|
||||
showResubmit: false,
|
||||
},
|
||||
);
|
||||
const emit = defineEmits<(e: 'detail') => void>();
|
||||
|
||||
const member = computed(() => props.data.member || {});
|
||||
|
||||
const isLike = ref(props.data.recentOperationStatus?.isLike);
|
||||
const operationTotal = ref(props.data.operationTotal?.likeTotal || 0);
|
||||
const params = reactive({
|
||||
memberId: member.value.memberId, // 被操作的用户id
|
||||
memberRecentId: props.data.id, // 当前朋友圈的编号,不能为空
|
||||
// 朋友圈操作类型,不能为空
|
||||
action: '',
|
||||
// RECENT_LIKE-朋友圈-点赞
|
||||
// RECENT_SHIELD-朋友圈-屏蔽
|
||||
// RECENT_REPORT-朋友圈-举报
|
||||
});
|
||||
|
||||
// 图片预览
|
||||
const previewImage = (img: string) => {
|
||||
uni.previewImage({
|
||||
urls: [img],
|
||||
});
|
||||
};
|
||||
|
||||
// 喜欢
|
||||
const handleLike = async () => {
|
||||
params.action = 'RECENT_LIKE';
|
||||
const { code } = await RecentApi.recentOperate(params);
|
||||
if (code !== 0) return;
|
||||
|
||||
isLike.value = !isLike.value;
|
||||
isLike.value ? operationTotal.value++ : operationTotal.value--;
|
||||
};
|
||||
|
||||
// 打招呼 私聊
|
||||
const goChat = async () => {
|
||||
if (!member.value.memberId) return uni.showToast({ title: '用户不存在' });
|
||||
const { code } = await RecentApi.chatAddFriend({
|
||||
friendUserId: member.value.memberId,
|
||||
});
|
||||
if (code !== 0) return uni.showToast({ title: '好友添加失败', icon: 'error' });
|
||||
|
||||
const friendStore = useFriendStore();
|
||||
const configStore = useConfigStore();
|
||||
const chatStore = useChatStore();
|
||||
|
||||
request({
|
||||
url: '/user/find/' + member.value.memberId,
|
||||
method: 'GET',
|
||||
}).then((user: any) => {
|
||||
let friendInfo = user;
|
||||
|
||||
const friend = {
|
||||
id: friendInfo.id,
|
||||
nickName: friendInfo.nickName,
|
||||
headImage: friendInfo.headImageThumb,
|
||||
online: friendInfo.online,
|
||||
deleted: false,
|
||||
};
|
||||
friendStore.addFriend(friend);
|
||||
|
||||
const chat: any = {
|
||||
type: 'PRIVATE',
|
||||
targetId: friendInfo.id,
|
||||
showName: friendInfo.nickName,
|
||||
headImage: friendInfo.headImageThumb,
|
||||
isDnd: friendInfo.isDnd,
|
||||
};
|
||||
chatStore.openChat(chat);
|
||||
const chatIdx = chatStore.findChatIdx(chat);
|
||||
|
||||
// 防止初始化期间进入会话导致的消息不刷新
|
||||
if (!configStore.appInit || chatStore.loading) {
|
||||
uni.showToast({
|
||||
title: '正在初始化页面,请稍后...',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
const member = computed(() => props.data.member || {});
|
||||
const videoUrls = computed(
|
||||
() =>
|
||||
props.data.coverVideo
|
||||
?.split(',')
|
||||
.map((url: string) => url.trim())
|
||||
.filter(Boolean) || [],
|
||||
);
|
||||
const auditStatusMeta = computed(() => {
|
||||
const status = props.data.auditStatus ?? 1;
|
||||
if (status === 0) {
|
||||
return {
|
||||
text: '审核中',
|
||||
icon: 'time',
|
||||
color: '#C5A66E',
|
||||
};
|
||||
}
|
||||
|
||||
uni.navigateTo({ url: '/pages/message/chat-box?chatIdx=' + chatIdx });
|
||||
if (status === 2) {
|
||||
return {
|
||||
text: '审核不通过',
|
||||
icon: 'close',
|
||||
color: '#C98C8C',
|
||||
};
|
||||
}
|
||||
return {
|
||||
text: '审核通过',
|
||||
icon: 'check',
|
||||
color: '#8FAE9A',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 详情
|
||||
const detail = () => {
|
||||
emit('detail');
|
||||
};
|
||||
const isLike = ref(props.data.recentOperationStatus?.isLike);
|
||||
const operationTotal = ref(props.data.operationTotal?.likeTotal || 0);
|
||||
const params = reactive({
|
||||
memberId: member.value.memberId, // 被操作的用户id
|
||||
memberRecentId: props.data.id, // 当前朋友圈的编号,不能为空
|
||||
// 朋友圈操作类型,不能为空
|
||||
action: '',
|
||||
// RECENT_LIKE-朋友圈-点赞
|
||||
// RECENT_SHIELD-朋友圈-屏蔽
|
||||
// RECENT_REPORT-朋友圈-举报
|
||||
});
|
||||
|
||||
// 图片预览
|
||||
const previewImage = (img: string) => {
|
||||
uni.previewImage({
|
||||
urls: [img],
|
||||
});
|
||||
};
|
||||
|
||||
// 喜欢
|
||||
const handleLike = async () => {
|
||||
params.action = 'RECENT_LIKE';
|
||||
const { code } = await RecentApi.recentOperate(params);
|
||||
if (code !== 0) return;
|
||||
|
||||
isLike.value = !isLike.value;
|
||||
isLike.value ? operationTotal.value++ : operationTotal.value--;
|
||||
};
|
||||
|
||||
// 打招呼 私聊
|
||||
const goChat = async () => {
|
||||
if (!member.value.memberId) return uni.showToast({ title: '用户不存在' });
|
||||
const { code } = await RecentApi.chatAddFriend({
|
||||
friendUserId: member.value.memberId,
|
||||
});
|
||||
if (code !== 0) return uni.showToast({ title: '好友添加失败', icon: 'error' });
|
||||
|
||||
const friendStore = useFriendStore();
|
||||
const configStore = useConfigStore();
|
||||
const chatStore = useChatStore();
|
||||
|
||||
request({
|
||||
url: '/user/find/' + member.value.memberId,
|
||||
method: 'GET',
|
||||
}).then((user: any) => {
|
||||
let friendInfo = user;
|
||||
|
||||
const friend = {
|
||||
id: friendInfo.id,
|
||||
nickName: friendInfo.nickName,
|
||||
headImage: friendInfo.headImageThumb,
|
||||
online: friendInfo.online,
|
||||
deleted: false,
|
||||
};
|
||||
friendStore.addFriend(friend);
|
||||
|
||||
const chat: any = {
|
||||
type: 'PRIVATE',
|
||||
targetId: friendInfo.id,
|
||||
showName: friendInfo.nickName,
|
||||
headImage: friendInfo.headImageThumb,
|
||||
isDnd: friendInfo.isDnd,
|
||||
};
|
||||
chatStore.openChat(chat);
|
||||
const chatIdx = chatStore.findChatIdx(chat);
|
||||
|
||||
// 防止初始化期间进入会话导致的消息不刷新
|
||||
if (!configStore.appInit || chatStore.loading) {
|
||||
uni.showToast({
|
||||
title: '正在初始化页面,请稍后...',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({ url: '/pages/message/chat-box?chatIdx=' + chatIdx });
|
||||
});
|
||||
};
|
||||
|
||||
// 详情
|
||||
const detail = () => {
|
||||
emit('detail');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.audit-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
min-height: 36rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 6rpx;
|
||||
background: #292929;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.audit-reason {
|
||||
margin-top: 10rpx;
|
||||
padding: 12rpx 16rpx;
|
||||
border-left: 4rpx solid #ff8585;
|
||||
color: #ffb3b3;
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
font-size: 22rpx;
|
||||
line-height: 1.55;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<view class="page-app" :class="['theme-' + sys?.mode, 'main-' + sys?.theme, 'font-' + sys?.fontSize]">
|
||||
<view
|
||||
class="page-app"
|
||||
:class="['theme-' + sys?.mode, 'main-' + sys?.theme, 'font-' + sys?.fontSize]"
|
||||
:style="$statusBarPageStyle"
|
||||
>
|
||||
<view class="page-main" :style="[bgMain]">
|
||||
<!-- 顶部导航栏-情况1:默认通用顶部导航栏 -->
|
||||
<su-navbar v-if="navbar === 'normal'" :title="title" statusBar :color="color" :tools="tools"
|
||||
|
||||
@@ -19,9 +19,18 @@ export const tenantId = import.meta.env.SHOPRO_TENANT_ID;
|
||||
export const websocketPath = import.meta.env.SHOPRO_WEBSOCKET_PATH;
|
||||
export const h5Url = import.meta.env.SHOPRO_H5_URL;
|
||||
export const buildTarget = import.meta.env.SHOPRO_BUILD_TARGET;
|
||||
export const alipayBuildTarget = import.meta.env.SHOPRO_ALIPAY_BUILD_TARGET || buildTarget;
|
||||
export const wechatBuildTarget = import.meta.env.SHOPRO_WECHAT_BUILD_TARGET || buildTarget;
|
||||
export const paymentBuildTargets = {
|
||||
alipay: alipayBuildTarget,
|
||||
wechat: wechatBuildTarget,
|
||||
};
|
||||
|
||||
export default {
|
||||
buildTarget,
|
||||
alipayBuildTarget,
|
||||
wechatBuildTarget,
|
||||
paymentBuildTargets,
|
||||
baseUrl,
|
||||
apiPath,
|
||||
staticUrl,
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { getStatusBarHeight, initStatusBarHeight } from './status-bar';
|
||||
// #ifdef H5
|
||||
import { isWxBrowser } from '@/sheep/helper/utils';
|
||||
import { getH5HostStatusBarHeight } from './h5-shell';
|
||||
// #endif
|
||||
import wechat from './provider/wechat/index.js';
|
||||
import apple from './provider/apple';
|
||||
@@ -20,12 +20,13 @@ import share from './share';
|
||||
import Pay from './pay';
|
||||
|
||||
const device = uni.getWindowInfo();
|
||||
// #ifdef H5
|
||||
const h5StatusBarHeight = getH5HostStatusBarHeight();
|
||||
if (h5StatusBarHeight > 0) {
|
||||
device.statusBarHeight = h5StatusBarHeight;
|
||||
const initialStatusBarHeight = getStatusBarHeight();
|
||||
if (initialStatusBarHeight > 0) {
|
||||
device.statusBarHeight = initialStatusBarHeight;
|
||||
}
|
||||
// #endif
|
||||
initStatusBarHeight().then((statusBarHeight) => {
|
||||
device.statusBarHeight = statusBarHeight;
|
||||
});
|
||||
|
||||
const os = uni.getDeviceInfo().platform;
|
||||
|
||||
@@ -86,6 +87,11 @@ const pay = (payment, orderType, orderSN) => {
|
||||
return new Pay(payment, orderType, orderSN);
|
||||
};
|
||||
|
||||
// 强制使用 H5/WAP 支付渠道,不根据当前运行平台切换到 App 支付
|
||||
const payH5 = (payment, orderType, orderSN) => {
|
||||
return Pay.payH5(payment, orderType, orderSN);
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查更新 (只检查小程序和App)
|
||||
* @param {Boolean} silence - 静默检查
|
||||
@@ -146,9 +152,8 @@ const capsule = getCapsule();
|
||||
|
||||
// 标题栏高度
|
||||
const getNavBar = () => {
|
||||
return device.statusBarHeight + 44;
|
||||
return getStatusBarHeight() + 44;
|
||||
};
|
||||
const navbar = getNavBar();
|
||||
|
||||
function getLandingPage() {
|
||||
let page = '';
|
||||
@@ -171,10 +176,13 @@ const _platform = {
|
||||
checkUpdate,
|
||||
checkNetwork,
|
||||
pay,
|
||||
payH5,
|
||||
share,
|
||||
load,
|
||||
capsule,
|
||||
navbar,
|
||||
get navbar() {
|
||||
return getNavBar();
|
||||
},
|
||||
landingPage,
|
||||
isWechatInstalled,
|
||||
};
|
||||
|
||||
+105
-46
@@ -3,6 +3,7 @@ import sheep from '@/sheep';
|
||||
import $wxsdk from '@/sheep/libs/sdk-h5-weixin';
|
||||
// #endif
|
||||
import { getRootUrl } from '@/sheep/helper';
|
||||
import { h5Url } from '@/sheep/config';
|
||||
import PayOrderApi from '@/sheep/api/pay/order';
|
||||
|
||||
/**
|
||||
@@ -14,11 +15,17 @@ import PayOrderApi from '@/sheep/api/pay/order';
|
||||
*/
|
||||
|
||||
export default class SheepPay {
|
||||
constructor(payment, orderType, id) {
|
||||
constructor(payment, orderType, id, autoPay = true) {
|
||||
this.payment = payment;
|
||||
this.id = id;
|
||||
this.orderType = orderType;
|
||||
this.payAction();
|
||||
if (autoPay) {
|
||||
this.payAction();
|
||||
}
|
||||
}
|
||||
|
||||
static payH5(payment, orderType, id) {
|
||||
return new SheepPay(payment, orderType, id, false).h5PayAction();
|
||||
}
|
||||
|
||||
payAction() {
|
||||
@@ -83,42 +90,55 @@ export default class SheepPay {
|
||||
return payAction[sheep.$platform.name][this.payment]();
|
||||
}
|
||||
|
||||
// 预支付
|
||||
prepay(channel) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let data = {
|
||||
id: this.id,
|
||||
channelCode: channel,
|
||||
channelExtras: {},
|
||||
};
|
||||
// 特殊逻辑:微信公众号、小程序支付时,必须传入 openid
|
||||
if (['wx_pub', 'wx_lite'].includes(channel)) {
|
||||
const openid = await sheep.$platform.useProvider('wechat').getOpenid(true);
|
||||
// 如果获取不到 openid,微信无法发起支付,此时需要引导
|
||||
if (!openid) {
|
||||
this.bindWeixin();
|
||||
return;
|
||||
}
|
||||
data.channelExtras.openid = openid;
|
||||
h5PayAction() {
|
||||
const payAction = {
|
||||
wechat: () => this.wechatWapPay(),
|
||||
alipay: () => this.redirectPay(),
|
||||
};
|
||||
const action = payAction[this.payment];
|
||||
if (!action) {
|
||||
return Promise.reject(new Error(`H5 支付不支持支付方式: ${this.payment}`));
|
||||
}
|
||||
return action().then((started) => {
|
||||
if (!started) {
|
||||
throw new Error('H5 支付调起失败');
|
||||
}
|
||||
// 发起预支付 API 调用
|
||||
PayOrderApi.submitOrder(data).then((res) => {
|
||||
// 成功时
|
||||
res.code === 0 && resolve(res);
|
||||
// 失败时
|
||||
if (res.code !== 0 && res.msg.indexOf('无效的openid') >= 0) {
|
||||
// 特殊逻辑:微信公众号、小程序支付时,必须传入 openid 不正确的情况
|
||||
if (
|
||||
res.msg.indexOf('无效的openid') >= 0 || // 获取的 openid 不正确时,或者随便输入了个 openid
|
||||
res.msg.indexOf('下单账号与支付账号不一致') >= 0
|
||||
) {
|
||||
// https://developers.weixin.qq.com/community/develop/doc/00008c53c347804beec82aed051c00
|
||||
this.bindWeixin();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// 预支付
|
||||
async prepay(channel, returnUrl = '') {
|
||||
const data = {
|
||||
id: this.id,
|
||||
channelCode: channel,
|
||||
channelExtras: {},
|
||||
};
|
||||
if (returnUrl) {
|
||||
data.returnUrl = returnUrl;
|
||||
}
|
||||
// 特殊逻辑:微信公众号、小程序支付时,必须传入 openid
|
||||
if (['wx_pub', 'wx_lite'].includes(channel)) {
|
||||
const openid = await sheep.$platform.useProvider('wechat').getOpenid(true);
|
||||
// 如果获取不到 openid,微信无法发起支付,此时需要引导
|
||||
if (!openid) {
|
||||
this.bindWeixin();
|
||||
return { code: -1, data: null };
|
||||
}
|
||||
data.channelExtras.openid = openid;
|
||||
}
|
||||
const res = await PayOrderApi.submitOrder(data);
|
||||
const message = res.msg || '';
|
||||
if (
|
||||
res.code !== 0 &&
|
||||
(message.includes('无效的openid') || message.includes('下单账号与支付账号不一致'))
|
||||
) {
|
||||
// https://developers.weixin.qq.com/community/develop/doc/00008c53c347804beec82aed051c00
|
||||
this.bindWeixin();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// 微信公众号 JSSDK 支付
|
||||
async wechatOfficialAccountPay() {
|
||||
@@ -145,27 +165,66 @@ export default class SheepPay {
|
||||
},
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 浏览器微信 H5 支付 TODO 芋艿:待接入(注意:H5 支付是给普通浏览器,不是微信公众号的支付,绝大多数人用不到,可以忽略)
|
||||
// 微信 H5 支付
|
||||
async wechatWapPay() {
|
||||
const { error, data } = await this.prepay('wx_wap');
|
||||
if (error === 0) {
|
||||
const redirect_url = `${getRootUrl()}pages/pay/result?id=${this.id}&payment=${this.payment
|
||||
}&orderType=${this.orderType}`;
|
||||
location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`;
|
||||
const returnUrl = this.getH5PayResultUrl();
|
||||
const { code, data } = await this.prepay('wx_wap', returnUrl);
|
||||
if (code !== 0 || !data?.displayContent) {
|
||||
return false;
|
||||
}
|
||||
const separator = data.displayContent.includes('?') ? '&' : '?';
|
||||
const payUrl = returnUrl
|
||||
? `${data.displayContent}${separator}redirect_url=${encodeURIComponent(returnUrl)}`
|
||||
: data.displayContent;
|
||||
this.openH5PayUrl(payUrl, '微信支付');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 支付链接(支付宝 wap 支付)
|
||||
async redirectPay() {
|
||||
let { code, data } = await this.prepay('alipay_wap');
|
||||
if (code !== 0) {
|
||||
return;
|
||||
const { code, data } = await this.prepay('alipay_wap', this.getH5PayResultUrl());
|
||||
if (code !== 0 || !data?.displayContent) {
|
||||
return false;
|
||||
}
|
||||
location.href = data.displayContent;
|
||||
this.openH5PayUrl(data.displayContent, '支付宝支付');
|
||||
return true;
|
||||
}
|
||||
|
||||
// #endif
|
||||
getH5PayResultUrl() {
|
||||
let rootUrl = getRootUrl();
|
||||
if (!rootUrl && h5Url) {
|
||||
rootUrl = `${String(h5Url).replace(/\/+$/, '')}/#/`;
|
||||
}
|
||||
if (!rootUrl) {
|
||||
return '';
|
||||
}
|
||||
return `${rootUrl}pages/profile/pay-result?id=${encodeURIComponent(this.id)}&payment=${encodeURIComponent(
|
||||
this.payment,
|
||||
)}&orderType=${encodeURIComponent(this.orderType)}`;
|
||||
}
|
||||
|
||||
openH5PayUrl(url, title) {
|
||||
// #ifdef H5
|
||||
window.location.href = url;
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openURL(url);
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.showModal({
|
||||
title,
|
||||
content: '请复制链接到手机浏览器完成支付',
|
||||
confirmText: '复制链接',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
sheep.$helper.copyText(url);
|
||||
}
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 微信小程序支付
|
||||
async wechatMiniProgramPay() {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { initH5HostShell } from './h5-shell';
|
||||
|
||||
const MAX_STATUS_BAR_HEIGHT = 200;
|
||||
const statusBarHeight = ref(readInitialStatusBarHeight());
|
||||
let initPromise = null;
|
||||
|
||||
function normalizeStatusBarHeight(value) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null;
|
||||
}
|
||||
const height = Number.parseFloat(value);
|
||||
if (!Number.isFinite(height)) {
|
||||
return null;
|
||||
}
|
||||
return Math.max(0, Math.min(height, MAX_STATUS_BAR_HEIGHT));
|
||||
}
|
||||
|
||||
function resolveSystemStatusBarHeight(systemInfo) {
|
||||
if (!systemInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const safeAreaTop = normalizeStatusBarHeight(systemInfo.safeArea?.top);
|
||||
const screenTop = normalizeStatusBarHeight(systemInfo.screenTop) ?? 0;
|
||||
const candidates = [
|
||||
systemInfo.statusBarHeight,
|
||||
systemInfo.safeAreaInsets?.top,
|
||||
safeAreaTop === null ? null : Math.max(0, safeAreaTop - screenTop),
|
||||
];
|
||||
let zeroHeight = null;
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const height = normalizeStatusBarHeight(candidate);
|
||||
if (height > 0) {
|
||||
return height;
|
||||
}
|
||||
if (height === 0) {
|
||||
zeroHeight = 0;
|
||||
}
|
||||
}
|
||||
return zeroHeight;
|
||||
}
|
||||
|
||||
function readInitialStatusBarHeight() {
|
||||
// #ifdef H5
|
||||
return 0;
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
try {
|
||||
return resolveSystemStatusBarHeight(uni.getWindowInfo?.()) ?? 0;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
function getSystemInfoAsync() {
|
||||
return new Promise((resolve) => {
|
||||
uni.getSystemInfo({
|
||||
success: resolve,
|
||||
fail: () => resolve(null),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const statusBarPageStyle = computed(
|
||||
() => `--status-bar-height: ${statusBarHeight.value}px;`,
|
||||
);
|
||||
|
||||
export function getStatusBarHeight() {
|
||||
return statusBarHeight.value;
|
||||
}
|
||||
|
||||
export function initStatusBarHeight() {
|
||||
if (initPromise) {
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
const h5StatusBarHeight = initH5HostShell();
|
||||
statusBarHeight.value = h5StatusBarHeight;
|
||||
window.addEventListener('app-shell-status-bar-change', (event) => {
|
||||
const nextHeight = normalizeStatusBarHeight(event.detail?.height);
|
||||
if (nextHeight !== null) {
|
||||
statusBarHeight.value = nextHeight;
|
||||
}
|
||||
});
|
||||
initPromise = Promise.resolve(h5StatusBarHeight);
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
initPromise = getSystemInfoAsync().then((systemInfo) => {
|
||||
const nextHeight = resolveSystemStatusBarHeight(systemInfo);
|
||||
if (nextHeight !== null) {
|
||||
statusBarHeight.value = nextHeight;
|
||||
}
|
||||
return statusBarHeight.value;
|
||||
});
|
||||
// #endif
|
||||
|
||||
return initPromise;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import $platform from '@/sheep/platform';
|
||||
// import { showAuthModal } from '@/sheep/hooks/useModal';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
import { getTerminal } from '@/sheep/helper/const';
|
||||
import chatTokenManager from '@/common/chat-token-manager';
|
||||
|
||||
const options = {
|
||||
// 显示操作成功消息 默认不显示
|
||||
@@ -115,7 +116,9 @@ http.interceptors.response.use(
|
||||
// 约定:如果是 /auth/ 下的 URL 地址,并且返回了 accessToken 说明是登录相关的接口,则自动设置登陆令牌
|
||||
if (response.config.url.indexOf('/member/auth/') >= 0 && response.data?.data?.accessToken) {
|
||||
$store('user').setToken(response.data.data.accessToken, response.data.data.refreshToken);
|
||||
uni.setStorageSync('loginInfo', response.data.data.chatToken); // 聊天相关token
|
||||
if (response.data.data.chatToken) {
|
||||
chatTokenManager.saveTokenInfo(response.data.data.chatToken);
|
||||
}
|
||||
}
|
||||
|
||||
// 自定处理【loading 加载中】:如果需要显示 loading,则关闭 loading
|
||||
|
||||
@@ -9,6 +9,7 @@ import PayWalletApi from '@/sheep/api/pay/wallet';
|
||||
import OrderApi from '@/sheep/api/trade/order';
|
||||
import CouponApi from '@/sheep/api/promotion/coupon';
|
||||
import chatTokenManager from '@/common/chat-token-manager';
|
||||
import * as wsApi from '@/common/wssocket';
|
||||
|
||||
// 默认用户信息
|
||||
const defaultUserInfo = {
|
||||
@@ -122,6 +123,9 @@ const user = defineStore({
|
||||
|
||||
// 重置用户默认数据
|
||||
resetUserData() {
|
||||
// 先销毁 IM 长连接,避免旧账号在退出后继续心跳或自动重连
|
||||
wsApi.destroy(1000);
|
||||
chatTokenManager.stop();
|
||||
// 清空 token
|
||||
this.setToken();
|
||||
// 清空用户相关的缓存
|
||||
@@ -129,7 +133,6 @@ const user = defineStore({
|
||||
this.userWallet = clone(defaultUserWallet);
|
||||
this.numData = cloneDeep(defaultNumData);
|
||||
// 清空聊天相关
|
||||
chatTokenManager.stop();
|
||||
uni.removeStorageSync('loginInfo');
|
||||
// 清空购物车的缓存
|
||||
cart().emptyList();
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<template>
|
||||
<view class="ui-video-wrap">
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video
|
||||
:id="`sVideo${uid}`"
|
||||
class="radius"
|
||||
:style="[{ height: height + 'rpx' }]"
|
||||
:src="src"
|
||||
controls
|
||||
:controls="true"
|
||||
:show-progress="true"
|
||||
:show-fullscreen-btn="true"
|
||||
:show-play-btn="true"
|
||||
:show-center-play-btn="true"
|
||||
:show-bottom-progress="true"
|
||||
object-fit="contain"
|
||||
:enable-progress-gesture="state.enableProgressGesture"
|
||||
:initial-time="initialTime"
|
||||
@@ -21,27 +25,6 @@
|
||||
:poster="poster"
|
||||
>
|
||||
</video>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<dom-video
|
||||
ref="domVideo"
|
||||
:id="`sVideo${uid}`"
|
||||
class="radius"
|
||||
:style="[{ height: height + 'rpx' }]"
|
||||
object-fit="contain"
|
||||
:controls="true"
|
||||
:show-progress="false"
|
||||
:show-fullscreen-btn="false"
|
||||
:show-play-btn="false"
|
||||
:show-bottom-progress="false"
|
||||
:autoplay="false"
|
||||
:src="src"
|
||||
@play="play"
|
||||
@pause="pause"
|
||||
@ended="end"
|
||||
:poster="poster"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- <view
|
||||
v-show="!state.isplay"
|
||||
class="poster-wrap radius"
|
||||
@@ -72,8 +55,6 @@
|
||||
*/
|
||||
|
||||
import { reactive, nextTick, getCurrentInstance } from 'vue';
|
||||
import DomVideo from './components/dom-video.vue';
|
||||
import sheep from '@/sheep';
|
||||
const vm = getCurrentInstance();
|
||||
|
||||
// 数据
|
||||
@@ -148,14 +129,14 @@
|
||||
// 开始播放
|
||||
const startPlay = () => {
|
||||
nextTick(() => {
|
||||
const video = uni.createVideoContext(`sVideo${props.index}`, vm);
|
||||
const video = uni.createVideoContext(`sVideo${props.uid}`, vm);
|
||||
video.play();
|
||||
});
|
||||
};
|
||||
|
||||
//暂停播放
|
||||
const pausePlay = () => {
|
||||
const video = uni.createVideoContext(`sVideo${props.index}`, vm);
|
||||
const video = uni.createVideoContext(`sVideo${props.uid}`, vm);
|
||||
video.pause();
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
export type LocationErrorCode =
|
||||
| 'permission-denied'
|
||||
| 'position-unavailable'
|
||||
| 'timeout'
|
||||
| 'insecure-context'
|
||||
| 'unsupported'
|
||||
| 'unknown';
|
||||
|
||||
export interface CurrentLocation {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
accuracy?: number;
|
||||
address?: {
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
street?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CurrentLocationOptions {
|
||||
type?: 'wgs84' | 'gcj02';
|
||||
geocode?: boolean;
|
||||
isHighAccuracy?: boolean;
|
||||
highAccuracyExpireTime?: number;
|
||||
}
|
||||
|
||||
export class LocationError extends Error {
|
||||
code: LocationErrorCode;
|
||||
|
||||
constructor(code: LocationErrorCode, message?: string) {
|
||||
super(message || code);
|
||||
this.name = 'LocationError';
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeUniLocationError = (error: any) => {
|
||||
const message = String(error?.errMsg || error?.message || '');
|
||||
if (/https|secure context|secure origin/i.test(message)) {
|
||||
return new LocationError('insecure-context', message);
|
||||
}
|
||||
if (/deny|denied|permission|authorize|auth/i.test(message)) {
|
||||
return new LocationError('permission-denied', message);
|
||||
}
|
||||
if (/timeout/i.test(message)) {
|
||||
return new LocationError('timeout', message);
|
||||
}
|
||||
if (/unavailable|定位服务|location service/i.test(message)) {
|
||||
return new LocationError('position-unavailable', message);
|
||||
}
|
||||
if (/not support|unsupported/i.test(message)) {
|
||||
return new LocationError('unsupported', message);
|
||||
}
|
||||
return new LocationError('unknown', message);
|
||||
};
|
||||
|
||||
export const getCurrentLocation = (
|
||||
options: CurrentLocationOptions = {},
|
||||
): Promise<CurrentLocation> =>
|
||||
new Promise((resolve, reject) => {
|
||||
uni.getLocation({
|
||||
type: options.type || 'wgs84',
|
||||
geocode: options.geocode !== false,
|
||||
isHighAccuracy: options.isHighAccuracy !== false,
|
||||
highAccuracyExpireTime: options.highAccuracyExpireTime ?? 15000,
|
||||
success: (result: any) => {
|
||||
resolve({
|
||||
latitude: Number(result.latitude),
|
||||
longitude: Number(result.longitude),
|
||||
accuracy: result.accuracy == null ? undefined : Number(result.accuracy),
|
||||
address: result.address,
|
||||
});
|
||||
},
|
||||
fail: (error: any) => reject(normalizeUniLocationError(error)),
|
||||
});
|
||||
});
|
||||
|
||||
export const isLocationError = (error: unknown): error is LocationError =>
|
||||
error instanceof LocationError;
|
||||
|
||||
export const getLocationErrorMessage = (error: unknown) => {
|
||||
const code = isLocationError(error) ? error.code : 'unknown';
|
||||
const isH5 =
|
||||
// #ifdef H5
|
||||
true;
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
false;
|
||||
// #endif
|
||||
|
||||
switch (code) {
|
||||
case 'permission-denied':
|
||||
return isH5
|
||||
? '浏览器已拒绝定位,请在地址栏的网站权限中允许定位'
|
||||
: '位置权限未开启,请在系统设置中允许访问位置';
|
||||
case 'position-unavailable':
|
||||
return '暂时无法获取当前位置,请确认设备定位服务已开启';
|
||||
case 'timeout':
|
||||
return '获取位置超时,请稍后重试';
|
||||
case 'insecure-context':
|
||||
return '浏览器定位需要使用 HTTPS 环境';
|
||||
case 'unsupported':
|
||||
return '当前浏览器不支持定位功能';
|
||||
default:
|
||||
return '位置信息获取失败,请稍后重试';
|
||||
}
|
||||
};
|
||||
@@ -27,7 +27,7 @@ const paramsToQuery = (params) => {
|
||||
}
|
||||
let query = [];
|
||||
for (let key in params) {
|
||||
query.push(key + '=' + params[key]);
|
||||
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
|
||||
}
|
||||
return query.join('&');
|
||||
};
|
||||
|
||||
Vendored
+4
-1
@@ -1,6 +1,9 @@
|
||||
export {};
|
||||
|
||||
declare module "vue" {
|
||||
declare module 'vue' {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
interface ComponentCustomOptions extends Hooks {}
|
||||
interface ComponentCustomProperties {
|
||||
$statusBarPageStyle: string;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user