新增聊天系统
This commit is contained in:
8
App.vue
8
App.vue
@@ -3,6 +3,8 @@
|
|||||||
// 多语言引入并初始化
|
// 多语言引入并初始化
|
||||||
import i18n from './locale/index';
|
import i18n from './locale/index';
|
||||||
const env = process.env.NODE_ENV;
|
const env = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
import socket from '@/utils/socket';
|
||||||
export default {
|
export default {
|
||||||
globalData: {
|
globalData: {
|
||||||
data: {
|
data: {
|
||||||
@@ -3107,6 +3109,12 @@
|
|||||||
onLaunch(params) {
|
onLaunch(params) {
|
||||||
//隐藏系统tabbar
|
//隐藏系统tabbar
|
||||||
this.globalData.system_hide_tabbar();
|
this.globalData.system_hide_tabbar();
|
||||||
|
|
||||||
|
const userInfo = uni.getStorageSync('cache_shop_user_info_key');
|
||||||
|
if (userInfo.id) {
|
||||||
|
// 应用启动时连接socket
|
||||||
|
socket.connectSocket();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 启动,或从后台进入前台显示
|
// 启动,或从后台进入前台显示
|
||||||
|
|||||||
@@ -95,6 +95,7 @@
|
|||||||
// 角标链接定义
|
// 角标链接定义
|
||||||
let badge_arr = {
|
let badge_arr = {
|
||||||
'/pages/cart/cart': 'cart',
|
'/pages/cart/cart': 'cart',
|
||||||
|
'/pages/user/user': 'user',
|
||||||
'/pages/cart-page/cart-page': 'cart',
|
'/pages/cart-page/cart-page': 'cart',
|
||||||
};
|
};
|
||||||
for (var i in nav_content) {
|
for (var i in nav_content) {
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
"enablePullDownRefresh": true,
|
"enablePullDownRefresh": true,
|
||||||
"navigationBarTitleText": ""
|
"navigationBarTitleText": ""
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/contact/contact",
|
||||||
|
"style": {
|
||||||
|
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-KUAISHOU || H5 || APP
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
// #endif
|
||||||
|
"enablePullDownRefresh": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/goods-category/goods-category",
|
"path": "pages/goods-category/goods-category",
|
||||||
|
|||||||
441
pages/contact/contact.vue
Normal file
441
pages/contact/contact.vue
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<view class="header-wrap">
|
||||||
|
<view class="header">
|
||||||
|
<view class="item" @click="back()">
|
||||||
|
<uni-icons type="left" color="#111" size="22"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="title">
|
||||||
|
<text class="t">咨询</text>
|
||||||
|
</view>
|
||||||
|
<view class="item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<view class="message-wrap" id="message-wrap">
|
||||||
|
<view class="more" v-if="list.length >= 10">
|
||||||
|
<text class="t" v-if="!finish">下拉加载更多历史消息</text>
|
||||||
|
<text class="t" v-else>没有更多消息了~</text>
|
||||||
|
</view>
|
||||||
|
<view class="more" v-if="status">
|
||||||
|
<text class="t">{{ contactInfo }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item" :class="[`item${item.user_type}`]" v-for="item in list" :key="item.id">
|
||||||
|
<image class="avatar" src="/static/icon_contact_avatar.svg" mode="aspectFill" v-if="item.user_type == 2"></image>
|
||||||
|
<view class="msg-wrap">
|
||||||
|
<view class="msg" v-if="item.type == 1">
|
||||||
|
<text class="t">{{ item.content }}</text>
|
||||||
|
</view>
|
||||||
|
<image class="img" :src="item.content" mode="heightFix" @click="previewImageHandle([item.content])" v-else></image>
|
||||||
|
<view class="time-wrap">
|
||||||
|
<text class="t">{{ item.add_time | formatTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="focus-wrap" v-if="iosFocus"></view>
|
||||||
|
<view class="footer-wrap" :class="{ focus: iosFocus }">
|
||||||
|
<view class="footer">
|
||||||
|
<view class="item" @click="selectImg">
|
||||||
|
<uni-icons type="image-filled" size="30" color="#666"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="input-wrap">
|
||||||
|
<input :focus="focus" adjust-position class="input" type="text" placeholder="输入消息..." v-model.trim="messageValue" @confirm="sendMessageHandle" @focus="inputFocus" @blur="inputBlur" />
|
||||||
|
</view>
|
||||||
|
<view class="item" style="transform: rotate(280deg)" @click="sendMessageHandle">
|
||||||
|
<uni-icons type="paperplane-filled" size="30" color="#5C6BC0"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const app = getApp();
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import socket from '@/utils/socket';
|
||||||
|
import { uploadImage, previewImage } from '@/utils/uploadFile.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [socket.createSocketMixin()],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
iosFocus: false,
|
||||||
|
focus: false,
|
||||||
|
messageValue: '',
|
||||||
|
userInfo: '',
|
||||||
|
status: 0,
|
||||||
|
contactInfo: '',
|
||||||
|
finish: false,
|
||||||
|
page: 1,
|
||||||
|
list: [],
|
||||||
|
platform: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
formatTime(timeStr) {
|
||||||
|
const now = dayjs(); // 当前时间
|
||||||
|
const targetTime = dayjs(timeStr); // 目标时间
|
||||||
|
|
||||||
|
// 获取日期部分(忽略时分秒)
|
||||||
|
const nowDate = now.startOf('day');
|
||||||
|
const targetDate = targetTime.startOf('day');
|
||||||
|
|
||||||
|
// 计算日期差(按天计算)
|
||||||
|
const diffDays = nowDate.diff(targetDate, 'day');
|
||||||
|
|
||||||
|
// 星期几的中文映射
|
||||||
|
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||||
|
const targetWeekDay = weekDays[targetTime.day()];
|
||||||
|
|
||||||
|
// 获取当前周的第一天(周日)
|
||||||
|
const firstDayOfWeek = now.startOf('week');
|
||||||
|
|
||||||
|
// 根据天数差判断格式化规则
|
||||||
|
if (diffDays === 0) {
|
||||||
|
// 今天
|
||||||
|
return targetTime.format('HH:mm');
|
||||||
|
} else if (diffDays === 1) {
|
||||||
|
// 昨天
|
||||||
|
return '昨天 ' + targetTime.format('HH:mm');
|
||||||
|
} else if (targetDate.isSame(firstDayOfWeek, 'week')) {
|
||||||
|
// 本周内(但不是今天和昨天)
|
||||||
|
return targetWeekDay + ' ' + targetTime.format('HH:mm');
|
||||||
|
} else {
|
||||||
|
// 更早之前
|
||||||
|
return targetTime.format('YYYY年M月D日 HH:mm');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.userInfo = uni.getStorageSync('cache_shop_user_info_key');
|
||||||
|
|
||||||
|
if (this.userInfo.id) {
|
||||||
|
// 自动管理生命周期的消息监听
|
||||||
|
this.socketOnMessage((data) => {
|
||||||
|
this.messageListener(data);
|
||||||
|
});
|
||||||
|
socket.sendMessage({
|
||||||
|
operate_type: 'chat_list',
|
||||||
|
type: 'user_msg',
|
||||||
|
user_id: this.userInfo.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取系统信息
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
this.platform = systemInfo.platform;
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.page++;
|
||||||
|
this.getHistoryMessage();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 发消息
|
||||||
|
sendMessageHandle() {
|
||||||
|
if (this.messageValue) {
|
||||||
|
this.sendMessageSocket(this.messageValue);
|
||||||
|
this.messageValue = '';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.focus = true;
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 发送消息 1文本 2图文
|
||||||
|
sendMessageSocket(content = '', type = 1) {
|
||||||
|
socket.sendMessage({
|
||||||
|
operate_type: 'send_msg',
|
||||||
|
type: 'user_msg',
|
||||||
|
user_id: this.userInfo.id,
|
||||||
|
content: content,
|
||||||
|
content_type: type,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 监听消息
|
||||||
|
messageListener(data) {
|
||||||
|
// console.log('监听消息===', data);
|
||||||
|
if (data.operate_type == 'chat_list' && this.page == 1) {
|
||||||
|
this.list = data.data.chat_list.reverse();
|
||||||
|
this.status = 1;
|
||||||
|
this.contactInfo = data.service;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.operate_type == 'chat_list' && this.page != 1) {
|
||||||
|
if (data.data.chat_list.length) {
|
||||||
|
this.list.unshift(...data.data.chat_list.reverse());
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '获取成功',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.finish = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// this.$nextTick(() => {
|
||||||
|
// uni.pageScrollTo({
|
||||||
|
// scrollTop: 0, // 滚动到页面顶部
|
||||||
|
// duration: 100, // 滚动动画时长(毫秒)
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
if (data.operate_type == 'service_msg') {
|
||||||
|
this.list.push(data.data);
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.operate_type == 'user_msg') {
|
||||||
|
this.list.push(data.data[0]);
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.operate_type == 'error') {
|
||||||
|
uni.showModal({
|
||||||
|
title: '注意',
|
||||||
|
content: data.msg,
|
||||||
|
showCancel: false,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取历史消息
|
||||||
|
getHistoryMessage() {
|
||||||
|
socket.sendMessage({
|
||||||
|
operate_type: 'chat_list',
|
||||||
|
type: 'user_msg',
|
||||||
|
user_id: this.userInfo.id,
|
||||||
|
page: this.page,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 上传图片
|
||||||
|
async selectImg() {
|
||||||
|
try {
|
||||||
|
const [url] = await uploadImage(1, true);
|
||||||
|
console.log('selectImg===', url);
|
||||||
|
this.sendMessageSocket(url, 2);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 预览图片
|
||||||
|
previewImageHandle(urls) {
|
||||||
|
previewImage(urls);
|
||||||
|
},
|
||||||
|
// 返回上一页
|
||||||
|
back() {
|
||||||
|
uni.navigateBack();
|
||||||
|
},
|
||||||
|
inputBlur() {
|
||||||
|
this.focus = false;
|
||||||
|
this.iosFocus = false;
|
||||||
|
},
|
||||||
|
inputFocus() {
|
||||||
|
if (this.platform == 'ios') {
|
||||||
|
this.iosFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollToBottom();
|
||||||
|
},
|
||||||
|
// 滚动到页面底部
|
||||||
|
scrollToBottom() {
|
||||||
|
// 创建节点查询器
|
||||||
|
const query = uni.createSelectorQuery();
|
||||||
|
|
||||||
|
// 获取底部元素的位置(相对于页面顶部的距离)
|
||||||
|
query.select('#message-wrap').boundingClientRect();
|
||||||
|
query.exec((res) => {
|
||||||
|
console.log('scrollToBottom===', res);
|
||||||
|
if (res && res[0]) {
|
||||||
|
const bottomHeight = res[0].height; // 元素顶部距离页面顶部的距离
|
||||||
|
|
||||||
|
// 滚动到指定位置(注意:不同平台参数可能不同)
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: bottomHeight + 1000, // 滚动距离(单位:px)
|
||||||
|
duration: 100, // 滚动动画时长(可选)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.container {
|
||||||
|
--header-height: 44px;
|
||||||
|
--footer-height: 60px;
|
||||||
|
$height: 40px;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
padding: calc(var(--header-height) + var(--status-bar-height)) 0 calc(var(--footer-height) + env(safe-area-inset-bottom)) 0;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef MP-ALIPAY */
|
||||||
|
padding: 28upx 0 calc(var(--footer-height) + env(safe-area-inset-bottom)) 0;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.header-wrap {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: var(--status-bar-height);
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 99;
|
||||||
|
background-color: #fff;
|
||||||
|
.header {
|
||||||
|
height: var(--header-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: var(--header-height);
|
||||||
|
height: var(--header-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.t {
|
||||||
|
font-size: 32upx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.message-wrap {
|
||||||
|
padding: 28upx;
|
||||||
|
.more {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 28upx;
|
||||||
|
.t {
|
||||||
|
font-size: 24upx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
gap: 20upx;
|
||||||
|
padding-bottom: 36upx;
|
||||||
|
&.item1 {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
.msg-wrap {
|
||||||
|
.msg {
|
||||||
|
background-color: #8e99f3;
|
||||||
|
.t {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.item2 {
|
||||||
|
.msg-wrap {
|
||||||
|
.time-wrap {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
$size: 80upx;
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
margin-top: 7upx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.msg-wrap {
|
||||||
|
.msg {
|
||||||
|
padding: 28upx;
|
||||||
|
border-radius: 28upx;
|
||||||
|
background-color: #fff;
|
||||||
|
.t {
|
||||||
|
display: block;
|
||||||
|
max-width: 450upx;
|
||||||
|
font-size: 28upx;
|
||||||
|
color: #333;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
display: block;
|
||||||
|
max-width: 500upx;
|
||||||
|
max-height: 200upx;
|
||||||
|
border-radius: 12upx;
|
||||||
|
background-color: #fefefe;
|
||||||
|
}
|
||||||
|
.time-wrap {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 12upx;
|
||||||
|
.t {
|
||||||
|
font-size: 24upx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.focus-wrap {
|
||||||
|
height: calc(var(--footer-height) + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.footer-wrap {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
background-color: #fcfcfc;
|
||||||
|
transform: translateY(0);
|
||||||
|
&.focus {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
height: var(--footer-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.item {
|
||||||
|
width: $height + 20px;
|
||||||
|
height: $height;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.input-wrap {
|
||||||
|
flex: 1;
|
||||||
|
.input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: $height;
|
||||||
|
border-radius: $height;
|
||||||
|
background-color: #efefef;
|
||||||
|
padding-left: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -452,6 +452,10 @@
|
|||||||
</block>
|
</block>
|
||||||
</block>
|
</block>
|
||||||
</block>
|
</block>
|
||||||
|
<view class="cp" hover-class="none" data-value="/pages/contact/contact" @tap="url_event">
|
||||||
|
<image src="/static/icon_contact.svg" mode="scaleToFill" class="image" style="filter: invert(80%)"></image>
|
||||||
|
<text class="dis-block text-size-xs cr-grey">客服</text>
|
||||||
|
</view>
|
||||||
<!-- 购物车 -->
|
<!-- 购物车 -->
|
||||||
<view v-if="is_opt_cart == 1" class="cp pr" data-value="/pages/cart-page/cart-page" @tap="url_event">
|
<view v-if="is_opt_cart == 1" class="cp pr" data-value="/pages/cart-page/cart-page" @tap="url_event">
|
||||||
<view class="badge-icon">
|
<view class="badge-icon">
|
||||||
@@ -1460,7 +1464,24 @@
|
|||||||
|
|
||||||
// url事件
|
// url事件
|
||||||
url_event(e) {
|
url_event(e) {
|
||||||
app.globalData.url_event(e);
|
var login = e.currentTarget.dataset.login;
|
||||||
|
if (login === undefined || login == 1) {
|
||||||
|
if (this.is_login()) {
|
||||||
|
app.globalData.url_event(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.globalData.url_event(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 是否登录
|
||||||
|
is_login() {
|
||||||
|
const user = app.globalData.get_user_cache_info() || null;
|
||||||
|
if ((user || null) == null) {
|
||||||
|
app.globalData.url_open('/pages/login/login?event_callback=init');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 底部导航操作返回事件
|
// 底部导航操作返回事件
|
||||||
|
|||||||
@@ -15,9 +15,7 @@
|
|||||||
<iconfont name="icon-login-down-arrow" size="16rpx"></iconfont>
|
<iconfont name="icon-login-down-arrow" size="16rpx"></iconfont>
|
||||||
</view>
|
</view>
|
||||||
</view> -->
|
</view> -->
|
||||||
<view style="height: 100upx;">
|
<view style="height: 100upx"> </view>
|
||||||
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 绑定手机 -->
|
<!-- 绑定手机 -->
|
||||||
@@ -415,6 +413,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
|
import socket from '@/utils/socket';
|
||||||
import base64 from '@/common/js/lib/base64.js';
|
import base64 from '@/common/js/lib/base64.js';
|
||||||
import componentCommon from '@/components/common/common';
|
import componentCommon from '@/components/common/common';
|
||||||
import componentPopup from '@/components/popup/popup';
|
import componentPopup from '@/components/popup/popup';
|
||||||
@@ -1369,6 +1368,7 @@
|
|||||||
app.globalData.showToast(msg, 'success');
|
app.globalData.showToast(msg, 'success');
|
||||||
var event_callback = this.params.event_callback || null;
|
var event_callback = this.params.event_callback || null;
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
|
socket.connectSocket();
|
||||||
var pages = getCurrentPages();
|
var pages = getCurrentPages();
|
||||||
if (pages.length > 1) {
|
if (pages.length > 1) {
|
||||||
// 触发回调函数
|
// 触发回调函数
|
||||||
|
|||||||
@@ -44,7 +44,13 @@
|
|||||||
|
|
||||||
<!-- 商品列表 -->
|
<!-- 商品列表 -->
|
||||||
<view class="goods bg-white padding-main" style="border-radius: 8px 8px 0 0">
|
<view class="goods bg-white padding-main" style="border-radius: 8px 8px 0 0">
|
||||||
<view class="br-b padding-bottom-main fw-b text-size">{{ $t('user-order-detail.user-order-detail.7f8p26') }}</view>
|
<view class="br-b padding-bottom-main fw-b text-size" style="display: flex; justify-content: space-between; align-items: center">
|
||||||
|
<text>{{ $t('user-order-detail.user-order-detail.7f8p26') }}</text>
|
||||||
|
<view style="color: #555; font-size: 28upx;display: flex;align-items: center;gap: 4upx;" data-value="/pages/contact/contact" @click="url_event">
|
||||||
|
<uni-icons type="chat" size="18" color="#666"></uni-icons>
|
||||||
|
联系客服
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view v-for="(item, index) in detail.items" :key="index" class="goods-item br-b-dashed oh padding-main">
|
<view v-for="(item, index) in detail.items" :key="index" class="goods-item br-b-dashed oh padding-main">
|
||||||
<view :data-value="item.goods_url" @tap="url_event" class="cp">
|
<view :data-value="item.goods_url" @tap="url_event" class="cp">
|
||||||
<image class="goods-image fl radius" :src="item.images" mode="aspectFill"></image>
|
<image class="goods-image fl radius" :src="item.images" mode="aspectFill"></image>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="item.operate_data.is_cancel + item.operate_data.is_pay + item.operate_data.is_collect + item.operate_data.is_comments + item.operate_data.is_delete + (item.plugins_is_order_allot_button || 0) + (item.plugins_is_order_batch_button || 0) + (item.plugins_is_order_frequencycard_button || 0) + (item.plugins_delivery_data || 0) + (item.plugins_ordergoodsform_data || 0) + (item.plugins_orderresources_data || 0) > 0 || (item.status == 2 && item.order_model != 2) || ((item.plugins_express_data || 0) == 1 && (item.express_data || null) != null) || ((item.plugins_intellectstools_data || null) != null && (item.plugins_intellectstools_data.continue_buy_data || null) != null && item.plugins_intellectstools_data.continue_buy_data.length > 0)" class="item-operation tr br-t padding-top-main">
|
<view v-if="item.operate_data.is_cancel + item.operate_data.is_pay + item.operate_data.is_collect + item.operate_data.is_comments + item.operate_data.is_delete + (item.plugins_is_order_allot_button || 0) + (item.plugins_is_order_batch_button || 0) + (item.plugins_is_order_frequencycard_button || 0) + (item.plugins_delivery_data || 0) + (item.plugins_ordergoodsform_data || 0) + (item.plugins_orderresources_data || 0) > 0 || (item.status == 2 && item.order_model != 2) || ((item.plugins_express_data || 0) == 1 && (item.express_data || null) != null) || ((item.plugins_intellectstools_data || null) != null && (item.plugins_intellectstools_data.continue_buy_data || null) != null && item.plugins_intellectstools_data.continue_buy_data.length > 0)" class="item-operation tr br-t padding-top-main">
|
||||||
|
<button class="round bg-white cr-green br-green margin-bottom-main" type="default" size="mini" data-value="/pages/contact/contact" @tap="url_event" hover-class="none">客服</button>
|
||||||
<button v-if="item.operate_data.is_cancel == 1" class="round bg-white cr-yellow br-yellow margin-bottom-main" type="default" size="mini" @tap="cancel_event" :data-value="item.id" :data-index="index" hover-class="none">{{ $t('common.cancel') }}</button>
|
<button v-if="item.operate_data.is_cancel == 1" class="round bg-white cr-yellow br-yellow margin-bottom-main" type="default" size="mini" @tap="cancel_event" :data-value="item.id" :data-index="index" hover-class="none">{{ $t('common.cancel') }}</button>
|
||||||
<button v-if="item.operate_data.is_pay == 1" class="round bg-white cr-green br-green margin-bottom-main" type="default" size="mini" @tap="pay_event" :data-value="item.id" :data-index="index" :data-price="item.total_price" :data-payment="item.payment_id" :data-currency-symbol="item.currency_data.currency_symbol" hover-class="none">{{ $t('order.order.1i873j') }}</button>
|
<button v-if="item.operate_data.is_pay == 1" class="round bg-white cr-green br-green margin-bottom-main" type="default" size="mini" @tap="pay_event" :data-value="item.id" :data-index="index" :data-price="item.total_price" :data-payment="item.payment_id" :data-currency-symbol="item.currency_data.currency_symbol" hover-class="none">{{ $t('order.order.1i873j') }}</button>
|
||||||
<button v-if="item.operate_data.is_collect == 1" class="round bg-white cr-green br-green margin-bottom-main" type="default" size="mini" @tap="collect_event" :data-transactionid="item.weixin_collect_data || ''" :data-value="item.id" :data-index="index" hover-class="none">{{ $t('orderallot-list.orderallot-list.w2w2w4') }}</button>
|
<button v-if="item.operate_data.is_collect == 1" class="round bg-white cr-green br-green margin-bottom-main" type="default" size="mini" @tap="collect_event" :data-transactionid="item.weixin_collect_data || ''" :data-value="item.id" :data-index="index" hover-class="none">{{ $t('orderallot-list.orderallot-list.w2w2w4') }}</button>
|
||||||
|
|||||||
1034
pages/user/user.vue
1034
pages/user/user.vue
File diff suppressed because it is too large
Load Diff
1
static/icon_contact_avatar.svg
Normal file
1
static/icon_contact_avatar.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1749535935276" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7562" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M326.66 531.22v56.42a16.9 16.9 0 0 0 16.9 16.92h5.62v-90.28h-5.62a16.92 16.92 0 0 0-16.9 16.94zM704.14 514.28h-5.62v90.28h5.62a16.9 16.9 0 0 0 16.9-16.92v-56.42a16.92 16.92 0 0 0-16.9-16.94z" p-id="7563" fill="#dbdbdb"></path><path d="M512 32C246.9 32 32 246.9 32 512s214.9 480 480 480 480-214.9 480-480S777.1 32 512 32z m-73.04 492.84a23.72 23.72 0 0 1 32.48-7.76c33.38 20.22 62.2 20.22 90.96 0.66a23.76 23.76 0 0 1 32.84 6 23.32 23.32 0 0 1-6.12 32.58c-44.52 30.3-93.46 30.3-142.32 0.66a23.3 23.3 0 0 1-7.84-32.14z m327.16 62.8a62 62 0 0 1-62 62h-16.92a174.84 174.84 0 0 1-125.04 108.64q-31.82 9.24-80.56 14l-6.3 0.62 1.04-60 61.04-10.3 14 11.86a129.82 129.82 0 0 0 102.12-126.88v-112.8a129.6 129.6 0 1 0-259.18 0v158a16.9 16.9 0 0 1-16.9 16.92h-33.8a62 62 0 0 1-62-62v-56.48a62 62 0 0 1 62-62h5.72a174.66 174.66 0 0 1 349.14 0h5.72a62 62 0 0 1 62 62z" p-id="7564" fill="#dbdbdb"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
547
utils/socket.js
Normal file
547
utils/socket.js
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
// socket.js - 优化后的全局唯一socket管理模块(支付宝小程序兼容版)
|
||||||
|
|
||||||
|
// 事件总线实现
|
||||||
|
class EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
this.events = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, callback) {
|
||||||
|
if (!this.events[event]) {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
this.events[event].push(callback);
|
||||||
|
return () => this.off(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event, callback) {
|
||||||
|
if (!this.events[event]) return;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
this.events[event] = this.events[event].filter(cb => cb !== callback);
|
||||||
|
} else {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event, data) {
|
||||||
|
if (!this.events[event]) return;
|
||||||
|
this.events[event].forEach(callback => callback(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建全局事件总线
|
||||||
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
|
// 唯一的socket实例
|
||||||
|
let socketTask = null;
|
||||||
|
let connectTimer = null;
|
||||||
|
let heartbeatTimer = null;
|
||||||
|
let reconnectCount = 0;
|
||||||
|
let isConnected = false;
|
||||||
|
let lastUrl = '';
|
||||||
|
let isConnecting = false;
|
||||||
|
let isAlipayMiniProgram = false;
|
||||||
|
|
||||||
|
// 支付宝小程序特有状态
|
||||||
|
let globalEventHandlersRegistered = false;
|
||||||
|
|
||||||
|
// 初始化时检测环境
|
||||||
|
(function detectEnvironment() {
|
||||||
|
try {
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
isAlipayMiniProgram = systemInfo.platform === 'alipay' || systemInfo.environment === 'alipay' || systemInfo
|
||||||
|
.app == 'alipay';
|
||||||
|
console.log('当前运行平台===', systemInfo);
|
||||||
|
console.log(`当前运行环境: ${isAlipayMiniProgram ? '支付宝小程序' : '其他平台'}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('检测运行环境失败:', e);
|
||||||
|
isAlipayMiniProgram = false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const MAX_RECONNECT_COUNT = 5;
|
||||||
|
const RECONNECT_DELAY = 3000; // 3秒重连间隔
|
||||||
|
const HEARTBEAT_INTERVAL = 10000; // 10秒心跳间隔
|
||||||
|
|
||||||
|
// 连接socket
|
||||||
|
function connectSocket(url = 'wss://store.sxczgkj.com/shopser') {
|
||||||
|
// 如果已经连接到相同URL,直接返回
|
||||||
|
if (isConnected && lastUrl === url) {
|
||||||
|
console.log('socket已连接到:', url);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在连接中,返回pending状态的Promise
|
||||||
|
if (isConnecting) {
|
||||||
|
console.log('socket连接中,等待连接完成...');
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const unsubscribeConnected = eventBus.on('connected', () => {
|
||||||
|
unsubscribeConnected();
|
||||||
|
unsubscribeError();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribeError = eventBus.on('error', (err) => {
|
||||||
|
unsubscribeConnected();
|
||||||
|
unsubscribeError();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已有不同URL的连接,先关闭
|
||||||
|
if ((socketTask || globalEventHandlersRegistered) && lastUrl !== url) {
|
||||||
|
console.log('关闭旧连接,准备连接新URL:', url);
|
||||||
|
closeSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录当前URL
|
||||||
|
lastUrl = url;
|
||||||
|
isConnecting = true;
|
||||||
|
reconnectCount = 0; // 重置重连计数
|
||||||
|
|
||||||
|
console.log('开始连接socket:', url);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 支付宝小程序特殊处理
|
||||||
|
if (isAlipayMiniProgram) {
|
||||||
|
// 确保只注册一次全局事件处理程序
|
||||||
|
if (!globalEventHandlersRegistered) {
|
||||||
|
registerGlobalEventHandlers();
|
||||||
|
globalEventHandlersRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用连接方法
|
||||||
|
uni.connectSocket({
|
||||||
|
url,
|
||||||
|
success: () => {
|
||||||
|
console.log('socket连接请求已发送');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('socket连接失败:', err);
|
||||||
|
isConnecting = false;
|
||||||
|
|
||||||
|
// 特殊处理:如果是"WebSocket已存在"错误,尝试重用现有连接
|
||||||
|
if (err.errMsg.includes('WebSocket 已存在')) {
|
||||||
|
console.warn('检测到重复连接,尝试重用现有连接');
|
||||||
|
// 触发已连接事件(模拟连接成功)
|
||||||
|
handleSocketOpen(null);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.emit('error', err);
|
||||||
|
handleReconnect(url);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// H5和其他平台使用socketTask对象方法
|
||||||
|
socketTask = uni.connectSocket({
|
||||||
|
url,
|
||||||
|
success: () => {
|
||||||
|
console.log('socket连接请求已发送');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('socket连接失败:', err);
|
||||||
|
isConnecting = false;
|
||||||
|
eventBus.emit('error', err);
|
||||||
|
handleReconnect(url);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查socketTask是否正确返回
|
||||||
|
if (!socketTask) {
|
||||||
|
console.error('uni.connectSocket() 未返回 socketTask 对象');
|
||||||
|
isConnecting = false;
|
||||||
|
eventBus.emit('error', new Error('无法创建socket连接'));
|
||||||
|
handleReconnect(url);
|
||||||
|
reject(new Error('无法创建socket连接'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听socket连接成功
|
||||||
|
if (socketTask.onOpen) {
|
||||||
|
socketTask.onOpen((res) => {
|
||||||
|
handleSocketOpen(res, resolve);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('socketTask对象缺少onOpen方法');
|
||||||
|
isConnecting = false;
|
||||||
|
eventBus.emit('error', new Error('socketTask对象不完整'));
|
||||||
|
handleReconnect(url);
|
||||||
|
reject(new Error('socketTask对象不完整'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听socket消息
|
||||||
|
if (socketTask.onMessage) {
|
||||||
|
socketTask.onMessage((res) => {
|
||||||
|
handleSocketMessage(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听socket关闭
|
||||||
|
if (socketTask.onClose) {
|
||||||
|
socketTask.onClose((res) => {
|
||||||
|
handleSocketClose(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听socket错误
|
||||||
|
if (socketTask.onError) {
|
||||||
|
socketTask.onError((err) => {
|
||||||
|
handleSocketError(err, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册支付宝小程序全局事件监听
|
||||||
|
function registerGlobalEventHandlers() {
|
||||||
|
console.log('注册支付宝小程序全局socket事件监听');
|
||||||
|
|
||||||
|
// 连接成功
|
||||||
|
uni.onSocketOpen((res) => {
|
||||||
|
console.log('支付宝小程序: socket连接成功');
|
||||||
|
handleSocketOpen(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 接收消息
|
||||||
|
uni.onSocketMessage((res) => {
|
||||||
|
handleSocketMessage(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连接关闭
|
||||||
|
uni.onSocketClose((res) => {
|
||||||
|
handleSocketClose(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连接错误
|
||||||
|
uni.onSocketError((err) => {
|
||||||
|
handleSocketError(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理socket连接成功
|
||||||
|
function handleSocketOpen(res, resolve) {
|
||||||
|
console.log('socket连接成功:', res);
|
||||||
|
reconnectCount = 0;
|
||||||
|
isConnected = true;
|
||||||
|
isConnecting = false;
|
||||||
|
|
||||||
|
// 标记socket已创建(支付宝小程序无socketTask对象)
|
||||||
|
if (isAlipayMiniProgram) {
|
||||||
|
socketTask = true; // 使用布尔值标记连接状态
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.emit('connected');
|
||||||
|
|
||||||
|
|
||||||
|
const userInfo = uni.getStorageSync('cache_shop_user_info_key');
|
||||||
|
if (userInfo.id) {
|
||||||
|
sendMessage({
|
||||||
|
operate_type: 'init',
|
||||||
|
type: 'user_msg',
|
||||||
|
user_id: userInfo.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startHeartbeat();
|
||||||
|
|
||||||
|
if (resolve) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理socket消息
|
||||||
|
function handleSocketMessage(res) {
|
||||||
|
try {
|
||||||
|
let data = JSON.parse(res.data);
|
||||||
|
console.log('收到socket消息:', data);
|
||||||
|
eventBus.emit('message', data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析socket消息失败:', e);
|
||||||
|
eventBus.emit('error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理socket关闭
|
||||||
|
function handleSocketClose(res) {
|
||||||
|
console.log('socket已关闭:', res);
|
||||||
|
isConnected = false;
|
||||||
|
eventBus.emit('closed', res);
|
||||||
|
stopHeartbeat();
|
||||||
|
|
||||||
|
// 仅在非支付宝小程序环境下重置socketTask
|
||||||
|
if (!isAlipayMiniProgram) {
|
||||||
|
socketTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReconnect(lastUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理socket错误
|
||||||
|
function handleSocketError(err, reject) {
|
||||||
|
console.error('socket发生错误:', err);
|
||||||
|
isConnected = false;
|
||||||
|
isConnecting = false;
|
||||||
|
eventBus.emit('error', err);
|
||||||
|
stopHeartbeat();
|
||||||
|
|
||||||
|
// 仅在非支付宝小程序环境下重置socketTask
|
||||||
|
if (!isAlipayMiniProgram) {
|
||||||
|
socketTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReconnect(lastUrl);
|
||||||
|
|
||||||
|
if (reject) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
function sendMessage(data) {
|
||||||
|
if (!isConnected) {
|
||||||
|
console.error('socket未连接,无法发送消息');
|
||||||
|
// 尝试重连
|
||||||
|
connectSocket().then(() => {
|
||||||
|
sendMessage(data);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('重连失败,无法发送消息:', err);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const messageData = JSON.stringify(data);
|
||||||
|
|
||||||
|
if (isAlipayMiniProgram) {
|
||||||
|
uni.sendSocketMessage({
|
||||||
|
data: messageData,
|
||||||
|
success: () => {
|
||||||
|
console.log('消息发送成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('消息发送失败:', err);
|
||||||
|
eventBus.emit('error', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!socketTask) {
|
||||||
|
console.error('socketTask不存在,无法发送消息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查socketTask是否有send方法
|
||||||
|
if (socketTask.send) {
|
||||||
|
socketTask.send({
|
||||||
|
data: messageData,
|
||||||
|
success: () => {
|
||||||
|
console.log('消息发送成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('消息发送失败:', err);
|
||||||
|
eventBus.emit('error', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('socketTask缺少send方法');
|
||||||
|
eventBus.emit('error', new Error('socketTask对象不完整'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('消息序列化失败:', e);
|
||||||
|
eventBus.emit('error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送心跳
|
||||||
|
function sendHeartbeat() {
|
||||||
|
sendMessage({
|
||||||
|
type: 'ping_interval'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始心跳
|
||||||
|
function startHeartbeat() {
|
||||||
|
stopHeartbeat();
|
||||||
|
heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止心跳
|
||||||
|
function stopHeartbeat() {
|
||||||
|
if (heartbeatTimer) {
|
||||||
|
clearInterval(heartbeatTimer);
|
||||||
|
heartbeatTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理重连
|
||||||
|
function handleReconnect(url) {
|
||||||
|
if (reconnectCount >= MAX_RECONNECT_COUNT) {
|
||||||
|
console.error('达到最大重连次数,停止重连');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要重连(避免重复触发连接)
|
||||||
|
if (isConnecting || isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectCount++;
|
||||||
|
console.log(`准备第${reconnectCount}次重连,${RECONNECT_DELAY/1000}秒后...`);
|
||||||
|
|
||||||
|
if (connectTimer) {
|
||||||
|
clearTimeout(connectTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectTimer = setTimeout(() => {
|
||||||
|
connectSocket(url);
|
||||||
|
}, RECONNECT_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭socket
|
||||||
|
function closeSocket() {
|
||||||
|
console.log('正在关闭socket连接...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isAlipayMiniProgram) {
|
||||||
|
uni.closeSocket({
|
||||||
|
success: () => {
|
||||||
|
console.log('主动关闭socket成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('主动关闭socket失败:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!socketTask) return;
|
||||||
|
|
||||||
|
if (socketTask.close) {
|
||||||
|
socketTask.close({
|
||||||
|
success: () => {
|
||||||
|
console.log('主动关闭socket成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('主动关闭socket失败:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('socketTask缺少close方法');
|
||||||
|
isConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('关闭socket时发生异常:', e);
|
||||||
|
isConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopHeartbeat();
|
||||||
|
if (connectTimer) {
|
||||||
|
clearTimeout(connectTimer);
|
||||||
|
connectTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected = false;
|
||||||
|
isConnecting = false;
|
||||||
|
|
||||||
|
// 仅在非支付宝小程序环境下重置socketTask
|
||||||
|
if (!isAlipayMiniProgram) {
|
||||||
|
socketTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置支付宝小程序的全局事件标记
|
||||||
|
globalEventHandlersRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听消息
|
||||||
|
function onMessage(callback) {
|
||||||
|
return eventBus.on('message', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听连接状态
|
||||||
|
function onConnected(callback) {
|
||||||
|
return eventBus.on('connected', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听断开状态
|
||||||
|
function onClosed(callback) {
|
||||||
|
return eventBus.on('closed', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听错误
|
||||||
|
function onError(callback) {
|
||||||
|
return eventBus.on('error', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提供一个自动管理生命周期的组件选项
|
||||||
|
function createSocketMixin() {
|
||||||
|
return {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
socketUnsubscribes: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 自动取消所有订阅
|
||||||
|
this.socketUnsubscribes.forEach(unsubscribe => unsubscribe());
|
||||||
|
this.socketUnsubscribes = [];
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 订阅消息并自动管理生命周期
|
||||||
|
socketOnMessage(callback) {
|
||||||
|
const unsubscribe = onMessage(callback);
|
||||||
|
this.socketUnsubscribes.push(unsubscribe);
|
||||||
|
return unsubscribe;
|
||||||
|
},
|
||||||
|
socketOnConnected(callback) {
|
||||||
|
const unsubscribe = onConnected(callback);
|
||||||
|
this.socketUnsubscribes.push(unsubscribe);
|
||||||
|
return unsubscribe;
|
||||||
|
},
|
||||||
|
socketOnClosed(callback) {
|
||||||
|
const unsubscribe = onClosed(callback);
|
||||||
|
this.socketUnsubscribes.push(unsubscribe);
|
||||||
|
return unsubscribe;
|
||||||
|
},
|
||||||
|
socketOnError(callback) {
|
||||||
|
const unsubscribe = onError(callback);
|
||||||
|
this.socketUnsubscribes.push(unsubscribe);
|
||||||
|
return unsubscribe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出状态检查函数
|
||||||
|
function isSocketConnected() {
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出连接状态
|
||||||
|
function getConnectionStatus() {
|
||||||
|
return {
|
||||||
|
isConnected,
|
||||||
|
isConnecting,
|
||||||
|
lastUrl,
|
||||||
|
reconnectCount,
|
||||||
|
platform: isAlipayMiniProgram ? 'alipay' : 'other'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
connectSocket,
|
||||||
|
sendMessage,
|
||||||
|
closeSocket,
|
||||||
|
onMessage,
|
||||||
|
onConnected,
|
||||||
|
onClosed,
|
||||||
|
onError,
|
||||||
|
createSocketMixin,
|
||||||
|
isSocketConnected,
|
||||||
|
getConnectionStatus
|
||||||
|
};
|
||||||
83
utils/uploadFile.js
Normal file
83
utils/uploadFile.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
const app = getApp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择并上传图片
|
||||||
|
* @param {number} count - 最多选择的图片数量
|
||||||
|
* @param {boolean} compressed - 是否压缩图片
|
||||||
|
* @returns {Promise} - 返回上传成功后的图片URL数组
|
||||||
|
*/
|
||||||
|
export function uploadImage(count = 1, compressed = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 选择图片
|
||||||
|
uni.chooseImage({
|
||||||
|
count,
|
||||||
|
sizeType: compressed ? ['compressed'] : ['original'],
|
||||||
|
sourceType: ['album', 'camera'],
|
||||||
|
success: (res) => {
|
||||||
|
const tempFilePaths = res.tempFilePaths;
|
||||||
|
const uploadPromises = tempFilePaths.map((tempFilePath) =>
|
||||||
|
uploadSingleImage(tempFilePath)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 并行上传所有图片
|
||||||
|
Promise.all(uploadPromises)
|
||||||
|
.then(results => resolve(results))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('选择图片失败:', err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传单张图片
|
||||||
|
* @param {string} filePath - 本地图片路径
|
||||||
|
* @returns {Promise} - 返回上传成功后的图片URL
|
||||||
|
*/
|
||||||
|
function uploadSingleImage(filePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: app.globalData.get_request_url('excel', 'xo'), // 上传地址
|
||||||
|
filePath,
|
||||||
|
name: 'file', // 服务器接收的文件字段名
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(res.data);
|
||||||
|
if (data.code === 0) { // 根据实际接口返回格式调整
|
||||||
|
resolve(data.data.url); // 返回图片URL
|
||||||
|
} else {
|
||||||
|
reject(new Error(data.message || '上传失败'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('解析响应数据失败'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(`服务器响应错误: ${res.statusCode}`));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('上传图片失败:', err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览图片
|
||||||
|
* @param {Array} urls - 图片URL数组
|
||||||
|
* @param {number} current - 当前预览的图片索引
|
||||||
|
*/
|
||||||
|
export function previewImage(urls, current = 0) {
|
||||||
|
uni.previewImage({
|
||||||
|
urls,
|
||||||
|
current,
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('预览图片失败:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user