fix:bug修改

This commit is contained in:
dzc committed 2026-07-30 20:20:24 +08:00
1 parent 3184608d99
commit 25a3dba931
64 files changed
+1980 -875

No files matched your search

+9 -5
View File
@@ -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
View File
@@ -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;
}
+92 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="" safeAreaInsetTop :bordered="false">
<template #left>
+1
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="举报" safeAreaInsetTop :bordered="false">
<template #left>
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="关于" safeAreaInsetTop :bordered="false">
<template #left>
+2 -1
View File
@@ -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>
+72 -25
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="黑名单" safeAreaInsetTop :bordered="false">
<template #left>
+81 -65
View File
@@ -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>
+149 -104
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+32 -6
View File
@@ -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
View File
@@ -1,5 +1,6 @@
<!-- 支付结果页面 -->
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="支付结果" safeAreaInsetTop :bordered="false">
<template #left>
+1
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="历史记录" safeAreaInsetTop :bordered="false">
<template #left>
+94 -43
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="设置" safeAreaInsetTop :bordered="false">
<template #left>
+1
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="上传相册" safeAreaInsetTop :bordered="false">
<template #left>
+2 -1
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark" :theme-vars="{ darkBackground2: '#1b1b1b' }">
<wd-navbar
title="修改资料"
+1
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<wd-config-provider theme="dark">
<wd-navbar title="人脸认证" safeAreaInsetTop :bordered="false">
<template #left>
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<!-- 顶部导航 -->
<wd-config-provider theme="dark">
<wd-navbar title="我的" safeAreaInsetTop :bordered="false">
+20 -25
View File
@@ -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
View File
@@ -1,4 +1,5 @@
<template>
<page-meta :page-style="$statusBarPageStyle" />
<!-- 顶部导航 -->
<wd-config-provider theme="dark">
<wd-navbar title="福利中心" safeAreaInsetTop :bordered="false">
+2 -1
View File
@@ -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>
+2 -1
View File
@@ -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>
+2 -1
View File
@@ -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>
+2 -1
View File
@@ -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
View File
@@ -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>
+2 -1
View File
@@ -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>
+2 -1
View File
@@ -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>
+33
View File
@@ -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({
+27
View File
@@ -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({
+206 -116
View File
@@ -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>
+5 -1
View File
@@ -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"
+9
View File
@@ -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,
+17 -9
View File
@@ -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
View File
@@ -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() {
+104
View File
@@ -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;
}
+4 -1
View File
@@ -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
+4 -1
View File
@@ -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();
+8 -27
View File
@@ -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();
};
+108
View File
@@ -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 '位置信息获取失败,请稍后重试';
}
};
+1 -1
View File
@@ -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('&');
};
+4 -1
View File
@@ -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;
}
}