客服部分
This commit is contained in:
parent
6b9ad175d6
commit
5f6e76ea71
6
api/customerService.js
Normal file
6
api/customerService.js
Normal file
@ -0,0 +1,6 @@
|
||||
export const apiArr = {
|
||||
csGetMsgList: "/api/v2/wechat/contactServer/getMsgList", //获取联系客服消息列表
|
||||
csGetMsgRecord: "/api/v2/wechat/contactServer/getMsgRecord", //获取联系客服消息记录
|
||||
csGetToClientId: "/api/v2/wechat/contactServer/getToClientId", //获取联系客服客户端ID
|
||||
csGetMchContactList: "/api/v2/wechat/contactServer/mchContactList", //商户客服列表
|
||||
};
|
||||
@ -21,6 +21,8 @@ export const apiArr = {
|
||||
|
||||
createPay:"/api/v2/wechat/quick-payment-record-crud/creat",//创建支付信息
|
||||
getPayInfo:"/api/v2/wechat/quick-payment-record-crud/info", //支付记录
|
||||
getPreOrderInfo:"/api/v2/wechat/quick-payment-record-crud/preorder", //预下单
|
||||
tradeQuery:"/api/v2/wechat/quick-payment-record-crud/trade-query", //查单
|
||||
|
||||
|
||||
createStore:"/api/v2/wechat/store-info-crud/creat",//门店信息创建
|
||||
|
||||
5244
package-lock.json
generated
5244
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,5 +15,9 @@
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"mqtt": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
291
packages/advertising/index/index.css
Normal file
291
packages/advertising/index/index.css
Normal file
@ -0,0 +1,291 @@
|
||||
.group-purchase-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
max-width: 750rpx;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 顶部横幅 */
|
||||
.banner-content {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
font-size: 18rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 5rpx 10rpx;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 商品列表 */
|
||||
.goods-list {
|
||||
background-color: #ffffff;
|
||||
padding: 30rpx 20rpx;
|
||||
height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
border-radius: 10rpx;
|
||||
padding: 15rpx;
|
||||
margin-bottom: 25rpx;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.goods-item2 {
|
||||
border-radius: 10rpx;
|
||||
padding: 15rpx;
|
||||
margin-bottom: 25rpx;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.goods-image {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 15rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.goods-image image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-desc {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 28rpx;
|
||||
color: #e63946;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.price-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.group-price {
|
||||
font-size: 28rpx;
|
||||
color: #e63946;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 4rpx;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.group-price1{
|
||||
width: 100rpx;
|
||||
padding: 10rpx 15rpx;
|
||||
color: #ffffff;
|
||||
background-color: #fc5d15;
|
||||
border-radius: 15rpx 0 0 15rpx;
|
||||
}
|
||||
|
||||
.group-price2{
|
||||
width: auto;
|
||||
padding: 10rpx 15rpx;
|
||||
background: linear-gradient(to bottom, #fef6d6, #fee8a9);
|
||||
border-radius: 0 15rpx 15rpx 0;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
width: auto;
|
||||
font-size: 22rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 15rpx;
|
||||
border-radius: 50rpx;
|
||||
background-color: #fe2f01;
|
||||
position: absolute;
|
||||
/* top: 120rpx; */
|
||||
bottom: -30rpx;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.quantity-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.decrease-btn {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: #f5f5f5;
|
||||
border: 1rpx solid #ccc;
|
||||
border-radius: 50%;
|
||||
line-height: 33rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.increase-btn {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: #ff502a;
|
||||
border-radius: 50%;
|
||||
line-height: 33rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.quantity {
|
||||
margin: 0 15rpx;
|
||||
font-size: 24rpx;
|
||||
width: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 购物车按钮 */
|
||||
.shop_car {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
position: fixed;
|
||||
right: 33rpx;
|
||||
bottom: 80rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.shop_car image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.u-badge {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -10rpx;
|
||||
}
|
||||
|
||||
/* 规格标签样式 */
|
||||
.specification-tag {
|
||||
width: 130rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #feeceb;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 40rpx;
|
||||
margin: 15rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #dd4020;
|
||||
}
|
||||
|
||||
.specification-tag text {
|
||||
margin-right: 5rpx;
|
||||
}
|
||||
|
||||
/* 货品列表样式 */
|
||||
.sku-list {
|
||||
margin-top: 10rpx;
|
||||
padding: 15rpx;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.sku-item{
|
||||
position: relative;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.sku-info{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sku-image{
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 15rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.sku-price {
|
||||
font-size: 26rpx;
|
||||
color: #e63946;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 4rpx;
|
||||
display: flex;
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.sku-price1{
|
||||
width: auto;
|
||||
padding: 10rpx 15rpx;
|
||||
color: #ffffff;
|
||||
background-color: #fc5d15;
|
||||
border-radius: 15rpx 0 0 15rpx;
|
||||
}
|
||||
|
||||
.sku-price2{
|
||||
width: auto;
|
||||
padding: 10rpx 15rpx;
|
||||
background: linear-gradient(to bottom, #fef6d6, #fee8a9);
|
||||
border-radius: 0 15rpx 15rpx 0;
|
||||
}
|
||||
|
||||
.sku-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.sku-countdown {
|
||||
width: auto;
|
||||
font-size: 22rpx;
|
||||
padding: 5rpx 20rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 15rpx;
|
||||
border-radius: 50rpx;
|
||||
background-color: #fe2f01;
|
||||
position: absolute;
|
||||
top: 170rpx;
|
||||
right: 0;
|
||||
}
|
||||
345
packages/advertising/index/index.vue
Normal file
345
packages/advertising/index/index.vue
Normal file
@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<view class="group-purchase-container">
|
||||
<!-- 商品列表 -->
|
||||
<view class="goods-list">
|
||||
<!-- 商品项 -->
|
||||
<view v-for="(item, index) in goodsList" :key="index">
|
||||
<!-- 有多个货品 -->
|
||||
<view class="goods-item" v-if="item.group_buy_goods_list.length > 1">
|
||||
<view class="goods-image">
|
||||
<image :src="item.commodity_pic" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.commodity_name }}</view>
|
||||
<view class="goods-desc">{{ item.commodity_intro }}</view>
|
||||
<view class="goods-price"> {{ getPriceRange(item.group_buy_goods_list) }} </view>
|
||||
|
||||
<!-- 选择规格标签 -->
|
||||
<view class="specification-tag" @click.stop="toggleSkuList(index)">
|
||||
<text>{{ item.showSkuList ? '收起' : '选择规格' }}</text>
|
||||
<u-icon name="arrow-down" size="26rpx" color="#FF370B" v-if="!item.showSkuList"></u-icon>
|
||||
<u-icon name="arrow-up" size="26rpx" color="#FF370B" v-else></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 货品列表 -->
|
||||
<view class="sku-list" v-if="item.showSkuList">
|
||||
<view v-for="(sku, skuIndex) in item.group_buy_goods_list" :key="skuIndex">
|
||||
<view class="sku-item" @click="toDetail(sku)">
|
||||
<view class="sku-info">
|
||||
<view class="sku-image">
|
||||
<image :src="sku.commodity_pic" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ sku.goods_name }}</view>
|
||||
<view class="goods-desc">{{ sku.goods_spec }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="price-container">
|
||||
<view class="sku-price">
|
||||
<view class="sku-price1">团购价</view>
|
||||
<view class="sku-price2">¥{{ sku.group_buy_price }}/{{ sku.goods_unit }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="sku-control">
|
||||
<view class="decrease-btn" @tap.stop="decreaseQuantity(index, skuIndex)">-
|
||||
</view>
|
||||
<view class="quantity">{{ sku.quantity }}</view>
|
||||
<view class="increase-btn" @tap.stop="increaseQuantity(index, skuIndex)">+
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="original-price">单买价 ¥{{ sku.sales_price }}/{{ sku.goods_unit }}</view>
|
||||
<view class="sku-countdown">
|
||||
{{
|
||||
getEndTheCountdown(sku.group_buy_activity_info.end_time)
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 只有一个货品 -->
|
||||
<view v-else>
|
||||
<view class="goods-item2" @click="toDetail(item)">
|
||||
<view class="goods-image">
|
||||
<image :src="item.commodity_pic" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.commodity_name }}</view>
|
||||
<view class="goods-desc">{{ item.commodity_intro }}</view>
|
||||
<view class="price-container">
|
||||
<view class="group-price">
|
||||
<view class="group-price1">团购价</view>
|
||||
<view class="group-price2">¥{{ item.group_buy_goods_list[0].group_buy_price }}/{{
|
||||
item.group_buy_goods_list[0].goods_unit }}</view>
|
||||
</view>
|
||||
<view class="quantity-control">
|
||||
<view class="decrease-btn" @tap.stop="decreaseQuantity(index, 0)">-</view>
|
||||
<view class="quantity">{{ item.group_buy_goods_list[0].quantity }}</view>
|
||||
<view class="increase-btn" @tap.stop="increaseQuantity(index, 0)">+</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="original-price">单买价 ¥{{ item.group_buy_goods_list[0].sales_price }}/{{
|
||||
item.group_buy_goods_list[0].goods_unit }}</view>
|
||||
<view class="countdown">
|
||||
{{ getEndTheCountdown(item.group_buy_goods_list[0].group_buy_activity_info.end_time) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 购物车按钮 -->
|
||||
<view class="shop_car" @click="shopCar">
|
||||
<u-badge numberType="limit" type="error" max="99" :value="carNum"></u-badge>
|
||||
<image src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/shop_car_num.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { picUrl, menuButtonInfo, request, NavgateTo } from "../../../utils";
|
||||
import { apiArr } from '@/api/groupPurchase.js'
|
||||
import { apiArr as shopApi } from "../../../api/shop.js";
|
||||
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
goodsList: [],
|
||||
carNum: 0,
|
||||
quantity: 0,
|
||||
timer: null, // 定时器ID
|
||||
endTime: '', // 初始化结束时间
|
||||
updateTime: Date.now(), // 用于触发倒计时更新的时间戳
|
||||
goodsDetail: []
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
// this.getGoodsList()
|
||||
},
|
||||
onShow() {
|
||||
// 在页面显示时启动定时器
|
||||
if (!this.timer) {
|
||||
this.timer = setInterval(() => {
|
||||
// 更新时间戳,触发页面重新渲染
|
||||
this.updateTime = Date.now();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 确保getGoodsList和getShopdetail都执行完毕后再执行getGoodsNum
|
||||
Promise.all([
|
||||
// 确保getGoodsList已完成
|
||||
this.goodsList.length > 0 ? Promise.resolve() : this.getGoodsList(),
|
||||
// 调用getShopdetail并等待其完成
|
||||
this.getShopdetail()
|
||||
]).then(() => {
|
||||
this.getGoodsNum();//获取货品在购物车中的数量
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getGoodsList() {
|
||||
if(!uni.getStorageSync('userId')){
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
const params = {
|
||||
user_id: uni.getStorageSync('userId')
|
||||
}
|
||||
return request(apiArr.groupBuyList, 'POST', params).then(res => {
|
||||
const list = res.group_buy_list.map(item => {
|
||||
// 为每个货品初始化quantity
|
||||
const group_buy_goods_list = item.group_buy_goods_list.map(sku => ({
|
||||
...sku,
|
||||
commodity_pic: picUrl + sku.commodity_pic,
|
||||
quantity: 0
|
||||
}));
|
||||
return {
|
||||
...item,
|
||||
commodity_pic: picUrl + item.commodity_pic,
|
||||
showSkuList: false,
|
||||
group_buy_goods_list
|
||||
}
|
||||
})
|
||||
this.goodsList = list
|
||||
return res;
|
||||
})
|
||||
},
|
||||
getShopdetail() {
|
||||
const params = {
|
||||
is_group_buy: 1,
|
||||
}
|
||||
return request(shopApi.getCar, "POST", params).then((res) => {
|
||||
this.carNum = res.total;
|
||||
// 合并当日达和普通商品数据
|
||||
this.goodsDetail = [].concat(res.same_day_cart_list, res.normal_cart_list)
|
||||
.flatMap(supplier => supplier.commodity_cart_and_goods_model);
|
||||
return res;
|
||||
});
|
||||
},
|
||||
getGoodsNum() {
|
||||
if (!this.goodsDetail || !this.goodsList || this.goodsList.length === 0) {
|
||||
return;
|
||||
}
|
||||
// 遍历所有商品
|
||||
this.goodsList.forEach(goods => {
|
||||
// 遍历商品的所有货品
|
||||
goods.group_buy_goods_list.forEach(sku => {
|
||||
// 在购物车数据中查找对应的货品
|
||||
const matchedItem = this.goodsDetail.find(item => item.goods_id === sku.goods_id);
|
||||
|
||||
// 如果找到匹配项,更新quantity
|
||||
if (matchedItem) {
|
||||
sku.quantity = matchedItem.count;
|
||||
} else {
|
||||
// 如果没有找到,设置为0
|
||||
sku.quantity = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
toDetail(itemObj) {
|
||||
const item = {
|
||||
...itemObj,
|
||||
groupById: itemObj.group_buy_activity_info.id
|
||||
};
|
||||
NavgateTo(`/packages/shop/groupPurchaseDetail/index?item=${JSON.stringify(item)}`)
|
||||
},
|
||||
// 获取商品价格范围
|
||||
getPriceRange(goodsList) {
|
||||
if (!goodsList || goodsList.length === 0) return '¥0';
|
||||
const prices = goodsList.map(item => Number(item.sales_price));
|
||||
const minPrice = Math.min(...prices);
|
||||
const maxPrice = Math.max(...prices);
|
||||
return minPrice === maxPrice ? `¥${minPrice}` : `¥${minPrice} ~ ¥${maxPrice}`;
|
||||
},
|
||||
// 展开/收起货品列表
|
||||
toggleSkuList(index) {
|
||||
this.goodsList[index].showSkuList = !this.goodsList[index].showSkuList;
|
||||
},
|
||||
// 增加货品数量
|
||||
increaseQuantity(goodsIndex, skuIndex) {
|
||||
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == 0) {
|
||||
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity += this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].min_order_quantity
|
||||
this.carNum += this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].min_order_quantity
|
||||
} else {
|
||||
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].total_stock) {
|
||||
uni.showToast({
|
||||
title: '库存不足',
|
||||
icon: 'none'
|
||||
});
|
||||
return
|
||||
}
|
||||
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].max_limit_quantity) {
|
||||
uni.showToast({
|
||||
title: '一次最多购买' + this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].max_limit_quantity + '件',
|
||||
icon: 'none'
|
||||
});
|
||||
return
|
||||
}
|
||||
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity++;
|
||||
this.carNum++;
|
||||
}
|
||||
const params = {
|
||||
goods_id_and_count: [
|
||||
{
|
||||
goods_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].id,
|
||||
count: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity,
|
||||
},
|
||||
],
|
||||
group_buy_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].group_buy_activity_info.id
|
||||
}
|
||||
this.updateCar(params);
|
||||
},
|
||||
// 减少货品数量
|
||||
decreaseQuantity(goodsIndex, skuIndex) {
|
||||
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity > 0) {
|
||||
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].min_order_quantity) {
|
||||
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity = 0
|
||||
this.carNum = 0
|
||||
} else {
|
||||
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity--;
|
||||
this.carNum--;
|
||||
}
|
||||
|
||||
const params = {
|
||||
goods_id_and_count: [
|
||||
{
|
||||
goods_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].id,
|
||||
count: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity,
|
||||
},
|
||||
],
|
||||
group_buy_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].group_buy_activity_info.id
|
||||
}
|
||||
this.updateCar(params);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '已经没有了...',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
// 请求更改购物车接口
|
||||
async updateCar(params) {
|
||||
return request(shopApi.updateCar, "POST", params).then((res) => {
|
||||
this.getShopdetail();
|
||||
uni.showToast({
|
||||
title: "操作成功!",
|
||||
success() { },
|
||||
});
|
||||
});
|
||||
},
|
||||
// 跳转到购物车
|
||||
shopCar() {
|
||||
const item = {
|
||||
is_group_buy: 1,
|
||||
}
|
||||
NavgateTo("../shopCar/index?item=" + JSON.stringify(item));
|
||||
},
|
||||
// 计算距离结束日期的剩余时间
|
||||
getEndTheCountdown(endTime) {
|
||||
// 获取当前时间和结束时间的时间戳
|
||||
const now = new Date().getTime();
|
||||
const end = new Date(endTime).getTime();
|
||||
|
||||
// 计算时间差(毫秒)
|
||||
let diff = end - now;
|
||||
|
||||
// 如果已经结束,返回提示
|
||||
if (diff <= 0) {
|
||||
return '团购已结束';
|
||||
}
|
||||
|
||||
// 计算天、小时、分钟
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
diff -= days * (1000 * 60 * 60 * 24);
|
||||
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||
diff -= hours * (1000 * 60 * 60);
|
||||
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
diff -= minutes * (1000 * 60);
|
||||
|
||||
// 返回格式化的字符串
|
||||
return `${days}天${hours}小时${minutes}分钟后结束`;
|
||||
}
|
||||
},
|
||||
onHide() {
|
||||
// 清除定时器
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("./index.css");
|
||||
</style>
|
||||
26
packages/community/index/data.json
Normal file
26
packages/community/index/data.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"defaultCategoryList": [
|
||||
{
|
||||
"id": 1,
|
||||
"category_name": "物业介绍"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"category_name": "物业缴费"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"category_name": "物业公积金"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"category_name": "报事报修"
|
||||
}
|
||||
],
|
||||
"defaultInfoList": {
|
||||
"1": "11",
|
||||
"2": "22",
|
||||
"3": "33",
|
||||
"4": "44"
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,26 @@
|
||||
</u-grid-item>
|
||||
</u-grid>
|
||||
</view>
|
||||
|
||||
<!-- <view class="tabs" v-if="defaultCategoryList.length > 0">
|
||||
<view v-for="(item, index) in defaultCategoryList" :key="index"
|
||||
:class="['tabItem', selectedTab === index ? 'active2' : '']" @click="selectTab(index, item)">
|
||||
{{ item.category_name }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="newsList" v-if="defaultCategoryList.length > 0">
|
||||
<view class="newsItem" v-for="item in defaultInfoList" @click="detail(item)" :key="item.id">
|
||||
<view class="newsItem_left">
|
||||
<view class="newsItem_left_tit">{{ item.title }}</view>
|
||||
<view class="newsItem_left_sub">{{ item.author }}</view>
|
||||
</view>
|
||||
<view class="newsItem_right">
|
||||
<image :src="item.list_image" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="searchBox" :style="{ height: localHeight + 'px', paddingTop: top + 'px' }">
|
||||
@ -158,6 +178,9 @@ import {
|
||||
import { apiArr } from "../../../api/v2Community";
|
||||
import { apiArr as apiArr2 } from "../../../api/community";
|
||||
|
||||
// 引入数据文件
|
||||
import dataJson from './data.json';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@ -242,6 +265,10 @@ export default {
|
||||
isShowBill: false,
|
||||
|
||||
houseVal: "",
|
||||
|
||||
// 从JSON文件中获取默认数据
|
||||
defaultCategoryList: dataJson.defaultCategoryList,
|
||||
defaultInfoList: dataJson.defaultInfoList,
|
||||
};
|
||||
},
|
||||
async onLoad(options) {
|
||||
|
||||
@ -485,13 +485,43 @@ export default {
|
||||
// this.payInfoId
|
||||
await request(apiArr.OrderPay, "POST", { order_pay_id: this.payInfoId }).then(
|
||||
async (res) => {
|
||||
const params = {
|
||||
order_pay_id: this.payInfoId,
|
||||
}
|
||||
await request(apiArr.OrderPay, "POST", params).then(
|
||||
(res) => {
|
||||
if (res && res.timeStamp && res.nonceStr && res.package && res.signType && res.paySign) {
|
||||
// 调用微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: res.timeStamp,
|
||||
nonceStr: res.nonceStr,
|
||||
package: res.package,
|
||||
signType: res.signType,
|
||||
paySign: res.paySign,
|
||||
success: (payRes) => {
|
||||
const params = {
|
||||
order_pay_id: this.payInfoId,
|
||||
}
|
||||
request(apiArr.tradeQuery, "POST", params).then(res => {
|
||||
})
|
||||
},
|
||||
fail: (payErr) => {
|
||||
uni.showToast({
|
||||
title: payErr.errMsg == 'requestPayment:fail cancel' ? '已取消支付' : '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
const params = {
|
||||
order_ids: orderId,
|
||||
}
|
||||
request(apiArr.cancelPay, "POST", params).then(res => {
|
||||
})
|
||||
},
|
||||
complete: () => {
|
||||
// 支付完成后的回调,无论成功失败都会执行
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.error("获取支付参数失败,缺少必要参数")
|
||||
uni.showToast({
|
||||
title: '获取支付信息失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
160
packages/customerService/changeService/index.css
Normal file
160
packages/customerService/changeService/index.css
Normal file
@ -0,0 +1,160 @@
|
||||
page{
|
||||
background-color: #f6f7fb;
|
||||
}
|
||||
|
||||
/* 切换客服页面样式 */
|
||||
.change-service-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.change-service-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 50px;
|
||||
padding: 0 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.empty-header {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
/* 客服列表 */
|
||||
.service-list {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-service {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 客服项 */
|
||||
.service-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.service-item:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.service-item.selected {
|
||||
border: 2px solid #07c160;
|
||||
}
|
||||
|
||||
/* 客服头像 */
|
||||
.service-avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 客服信息 */
|
||||
.service-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.service-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 选中图标 */
|
||||
.selected-icon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* 确认按钮区域 */
|
||||
.confirm-section {
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
border-radius: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.confirm-btn:disabled {
|
||||
background-color: #ccc;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.service-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.service-list::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.service-list::-webkit-scrollbar-thumb {
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.service-list::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #a8a8a8;
|
||||
}
|
||||
124
packages/customerService/changeService/index.vue
Normal file
124
packages/customerService/changeService/index.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<view class="change-service-container">
|
||||
<view class="change-service-header" :style="{ paddingTop: top + 'px', height: localHeight + 'px' }">
|
||||
<view class="back-btn" @tap="goBack">
|
||||
<uni-icons type="left" size="28" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="page-title">选择客服</view>
|
||||
<view class="empty-header"></view>
|
||||
</view>
|
||||
|
||||
<!-- 客服列表 -->
|
||||
<view class="service-list">
|
||||
<view v-if="isLoading" class="loading">加载中...</view>
|
||||
<view v-else-if="serviceList.length === 0" class="empty-service">暂无客服</view>
|
||||
<view v-else>
|
||||
<view v-for="service in serviceList" :key="service.id">
|
||||
<view class="service-item" @tap="confirmChange(service)">
|
||||
<image class="service-avatar" :src="service.employee_image" mode="aspectFill"></image>
|
||||
<view class="service-info">
|
||||
<text class="service-name">{{ service.employee_name }}</text>
|
||||
<text class="service-desc">{{ service.expertise || '专业客服为您服务' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
request,
|
||||
picUrl,
|
||||
uniqueByField,
|
||||
menuButtonInfo,
|
||||
NavgateTo
|
||||
} from '../../../utils';
|
||||
import { apiArr } from '@/api/customerService';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
localHeight: "",
|
||||
top: "",
|
||||
// 客服列表
|
||||
serviceList: [],
|
||||
// 是否加载中
|
||||
isLoading: false,
|
||||
// 当前选中的客服ID
|
||||
selectedServiceId: ''
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
const meun = menuButtonInfo();
|
||||
this.top = meun.top;
|
||||
this.localHeight = meun.height;
|
||||
// 从选项中获取当前选中的客服ID(如果有)
|
||||
if (options.currentMchId) {
|
||||
this.selectedServiceId = options.currentMchId;
|
||||
}
|
||||
|
||||
// 加载客服列表
|
||||
this.loadServiceList();
|
||||
},
|
||||
methods: {
|
||||
// 加载客服列表
|
||||
async loadServiceList() {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
|
||||
request(apiArr.csGetMchContactList, "POST", {
|
||||
mch_id: uni.getStorageSync("merchantInfo").id,
|
||||
}).then((res) => {
|
||||
if (res.rows && res.rows.length > 0) {
|
||||
res.rows.map(item => {
|
||||
item.employee_image = picUrl + item.employee_image;
|
||||
})
|
||||
this.serviceList = res.rows
|
||||
} else {
|
||||
console.log("没有获取到客服列表数据");
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载客服列表失败', error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 选择客服
|
||||
selectService(service) {
|
||||
this.selectedServiceId = service.mchId;
|
||||
},
|
||||
|
||||
// 确认切换客服
|
||||
confirmChange(item) {
|
||||
if (!item) {
|
||||
uni.showToast({
|
||||
title: '请选择客服',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 存储当前聊天对象的信息
|
||||
// uni.setStorageSync('currentChatTarget', item);
|
||||
|
||||
// 跳转到聊天页面
|
||||
uni.navigateTo({
|
||||
url: '/packages/customerService/index/index?item=' + JSON.stringify(item)
|
||||
});
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("./index.css");
|
||||
</style>
|
||||
136
packages/customerService/chattingRecords/index.css
Normal file
136
packages/customerService/chattingRecords/index.css
Normal file
@ -0,0 +1,136 @@
|
||||
/* 聊天记录页面样式 */
|
||||
page{
|
||||
background-color: #f6f7fb;
|
||||
}
|
||||
|
||||
/* 聊天记录容器 */
|
||||
.chatting-records-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f6f7fb;
|
||||
}
|
||||
|
||||
/* 聊天记录头部 */
|
||||
.records-header {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 聊天记录列表 */
|
||||
.records-list {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-records {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 聊天记录项 */
|
||||
.record-item {
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.record-item:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 头像 */
|
||||
.record-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* 记录信息 */
|
||||
.record-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
/* 标题行 */
|
||||
.record-title-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.record-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 时间 */
|
||||
.record-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 最后一条消息 */
|
||||
.record-last-msg {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.records-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.records-list::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.records-list::-webkit-scrollbar-thumb {
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.records-list::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #a8a8a8;
|
||||
}
|
||||
169
packages/customerService/chattingRecords/index.vue
Normal file
169
packages/customerService/chattingRecords/index.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view class="chatting-records-container">
|
||||
|
||||
<!-- 聊天记录列表 -->
|
||||
<view class="records-list">
|
||||
<view v-if="isLoading" class="loading">加载中...</view>
|
||||
<view v-else-if="recordsList.length === 0" class="empty-records">暂无聊天记录</view>
|
||||
<view v-else>
|
||||
<view v-for="record in recordsList" :key="record.mchId" class="record-item" @tap="goToChatPage(record)">
|
||||
<image class="record-avatar" :src="record.avatar || '/static/logo.png'" mode="aspectFill"></image>
|
||||
<view class="record-info">
|
||||
<view class="record-title-row">
|
||||
<text class="record-title">{{ record.title }}</text>
|
||||
<text class="record-time">{{ formatTime(record.lastMsgTime) }}</text>
|
||||
</view>
|
||||
<text class="record-last-msg">{{ record.lastMsg || '暂无消息' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
request,
|
||||
picUrl,
|
||||
NavgateTo,
|
||||
menuButtonInfo,
|
||||
} from "../../../utils/index";
|
||||
import { apiArr } from "../../../api/customerService";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 聊天记录列表
|
||||
recordsList: [],
|
||||
// 是否加载中
|
||||
isLoading: false,
|
||||
page_num: 1,
|
||||
page_size: 10,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
// 加载聊天记录列表
|
||||
this.loadChattingRecords();
|
||||
},
|
||||
onShow() {
|
||||
// 页面显示时重新加载聊天记录
|
||||
this.loadChattingRecords();
|
||||
},
|
||||
methods: {
|
||||
// 加载聊天记录列表
|
||||
async loadChattingRecords() {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
|
||||
// 获取聊天记录列表
|
||||
request(apiArr.csGetMsgList, "POST", {
|
||||
open_id: uni.getStorageSync("openId"),
|
||||
page_num: this.page_num,
|
||||
page_size: this.page_size,
|
||||
}).then((res) => {
|
||||
console.log("🚀 ~ loadChattingRecords ~ res:", res)
|
||||
})
|
||||
|
||||
// const response = await this.$http.get(apiArr.csGetMsgList);
|
||||
|
||||
// if (response.success && response.data && response.data.list) {
|
||||
// this.recordsList = response.data.list.map(item => ({
|
||||
// mchId: item.mchId,
|
||||
// title: item.title || `客服${item.mchId}`,
|
||||
// avatar: item.avatar,
|
||||
// lastMsg: item.lastMsg,
|
||||
// lastMsgTime: item.lastMsgTime || Date.now(),
|
||||
// unreadCount: item.unreadCount || 0
|
||||
// }));
|
||||
// } else {
|
||||
// // 使用模拟数据
|
||||
// this.recordsList = this.getMockRecords();
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('加载聊天记录失败', error);
|
||||
// 使用模拟数据
|
||||
this.recordsList = this.getMockRecords();
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取模拟数据
|
||||
getMockRecords() {
|
||||
return [
|
||||
{
|
||||
mchId: '1001',
|
||||
title: '客服小明',
|
||||
avatar: '/static/logo.png',
|
||||
lastMsg: '您好,请问有什么可以帮助您的吗?',
|
||||
lastMsgTime: Date.now() - 30 * 60 * 1000,
|
||||
unreadCount: 0
|
||||
},
|
||||
{
|
||||
mchId: '1002',
|
||||
title: '客服小丽',
|
||||
avatar: '/static/logo.png',
|
||||
lastMsg: '您的问题我已经记录,稍后会有专人与您联系。',
|
||||
lastMsgTime: Date.now() - 2 * 60 * 60 * 1000,
|
||||
unreadCount: 1
|
||||
},
|
||||
{
|
||||
mchId: '1003',
|
||||
title: '客服小张',
|
||||
avatar: '/static/logo.png',
|
||||
lastMsg: '感谢您的咨询,还有其他问题吗?',
|
||||
lastMsgTime: Date.now() - 5 * 60 * 60 * 1000,
|
||||
unreadCount: 0
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
const date = new Date(time);
|
||||
const now = new Date();
|
||||
const diff = now - date;
|
||||
|
||||
// 小于1分钟显示"刚刚"
|
||||
if (diff < 60 * 1000) {
|
||||
return '刚刚';
|
||||
}
|
||||
|
||||
// 小于1小时显示"XX分钟前"
|
||||
if (diff < 60 * 60 * 1000) {
|
||||
return Math.floor(diff / (60 * 1000)) + '分钟前';
|
||||
}
|
||||
|
||||
// 小于24小时显示"XX小时前"
|
||||
if (diff < 24 * 60 * 60 * 1000) {
|
||||
return Math.floor(diff / (60 * 60 * 1000)) + '小时前';
|
||||
}
|
||||
|
||||
// 小于7天显示"XX天前"
|
||||
if (diff < 7 * 24 * 60 * 60 * 1000) {
|
||||
return Math.floor(diff / (24 * 60 * 60 * 1000)) + '天前';
|
||||
}
|
||||
|
||||
// 其他情况显示具体日期
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
return month + '-' + day;
|
||||
},
|
||||
|
||||
// 跳转到聊天页面
|
||||
goToChatPage(record) {
|
||||
// 存储当前聊天对象的信息,供聊天页面使用
|
||||
uni.setStorageSync('currentChatTarget', record);
|
||||
|
||||
// 跳转到聊天页面
|
||||
uni.navigateTo({
|
||||
url: '/packages/customerService/index/index?mchId=' + record.mchId
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("./index.css");
|
||||
</style>
|
||||
187
packages/customerService/index/index.css
Normal file
187
packages/customerService/index/index.css
Normal file
@ -0,0 +1,187 @@
|
||||
/* 客服聊天页面样式 */
|
||||
page {
|
||||
background-color: #f6f7fb;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* 聊天容器 */
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f6f7fb;
|
||||
}
|
||||
|
||||
/* 聊天头部 */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 50px;
|
||||
padding: 0 15px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.empty-header {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
/* 切换客服按钮 */
|
||||
.change-service-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 连接状态提示 */
|
||||
.connecting-status {
|
||||
padding: 5px 15px;
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 聊天消息区域 */
|
||||
.chat-messages {
|
||||
width: 97%;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 时间分割线 */
|
||||
.message-time {
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 消息项 */
|
||||
.message-item {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.message-item.self {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/* 头像 */
|
||||
.message-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
/* 消息内容 */
|
||||
.message-content {
|
||||
max-width: 70%;
|
||||
padding: 10px 15px;
|
||||
border-radius: 10px;
|
||||
word-break: break-word;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.message-item.self .message-content {
|
||||
background-color: #91d5ff;
|
||||
color: #333;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.message-item.other .message-content {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
border-bottom-left-radius: 4px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.message-item.loading .message-content {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.chat-input-area {
|
||||
background-color: #fff;
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid #eee;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
flex: 1;
|
||||
height: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 20px;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
resize: none;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
margin-left: 10px;
|
||||
padding: 0 20px;
|
||||
height: 40px;
|
||||
background-color: #07c160;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.send-btn:disabled {
|
||||
background-color: #ccc;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.chat-messages::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-thumb {
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #a8a8a8;
|
||||
}
|
||||
596
packages/customerService/index/index.vue
Normal file
596
packages/customerService/index/index.vue
Normal file
@ -0,0 +1,596 @@
|
||||
<template>
|
||||
<view class="chat-container">
|
||||
<!-- 聊天头部 -->
|
||||
<view class="chat-header" :style="{ paddingTop: top + 'px', height: localHeight + 'px' }">
|
||||
<view class="back-btn" @tap="goBack">
|
||||
<uni-icons type="left" size="28" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="chat-title">{{ chatTarget.title || '客服' }}</view>
|
||||
<view class="change-service-btn" @tap="goToChangeService">
|
||||
<uni-icons type="switch" size="22" color="#333"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 连接状态提示 -->
|
||||
<view v-if="connectingStatus" class="connecting-status">{{ connectingStatus }}</view>
|
||||
|
||||
<!-- 聊天消息区域 -->
|
||||
<scroll-view class="chat-messages" scroll-y="true" :scroll-into-view="scrollToView"
|
||||
@scrolltoupper="loadMoreHistory">
|
||||
<!-- 加载历史消息提示 -->
|
||||
<view v-if="isLoadingHistory" class="message-time">加载历史消息...</view>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<block v-for="(message, index) in messages" :key="index">
|
||||
<!-- 时间分割线 -->
|
||||
<view v-if="needShowTime(index)" class="message-time">{{ formatTime(message.time) }}</view>
|
||||
|
||||
<!-- 消息项 -->
|
||||
<view class="message-item" :class="{
|
||||
'self': message.isSelf,
|
||||
'other': !message.isSelf,
|
||||
'loading': message.isLoading
|
||||
}" :id="'msg-' + index">
|
||||
<image class="message-avatar" :src="message.isSelf ? userAvatar : (chatTarget.avatar || '/static/logo.png')"
|
||||
mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
{{ message.content }}
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<view class="chat-input-area">
|
||||
<view class="input-container">
|
||||
<textarea class="message-input" placeholder="请输入消息..." v-model="inputMessage" @input="handleInput"
|
||||
@confirm="sendMessage" :adjust-position="true"></textarea>
|
||||
<button class="send-btn" :disabled="!canSend || (!mqttUtils || !mqttUtils.getIsConnected())" @tap="sendMessage">
|
||||
发送
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
request,
|
||||
picUrl,
|
||||
uniqueByField,
|
||||
menuButtonInfo,
|
||||
NavgateTo
|
||||
} from '../../../utils';
|
||||
import { apiArr } from '../../../api/customerService';
|
||||
// 修改导入路径
|
||||
import mqtt from 'mqtt'
|
||||
import MqttUtils from '../../../utils/mqtt';
|
||||
import awaitRated from "@/packages/myOrders/awaitRated/awaitRated.vue";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
localHeight: "",
|
||||
top: "",
|
||||
// 聊天目标信息
|
||||
chatTarget: {
|
||||
mchId: '',
|
||||
title: '',
|
||||
avatar: '',
|
||||
openId: '' // 接收方的open_id
|
||||
},
|
||||
// 用户头像
|
||||
userAvatar: '/static/logo.png',
|
||||
// 消息列表
|
||||
messages: [],
|
||||
// 输入的消息
|
||||
inputMessage: '',
|
||||
// 是否可以发送消息
|
||||
canSend: false,
|
||||
// 连接状态
|
||||
isConnected: false,
|
||||
// 连接状态文本
|
||||
connectingStatus: '',
|
||||
// 滚动到指定视图
|
||||
scrollToView: '',
|
||||
// 是否加载历史消息
|
||||
isLoadingHistory: false,
|
||||
// 心跳包定时器
|
||||
keepaliveTimer: null,
|
||||
// MQTT工具实例
|
||||
client: null,
|
||||
// 重连失败提示定时器
|
||||
reconnectFailedTimer: null
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
const meun = menuButtonInfo();
|
||||
this.top = meun.top;
|
||||
this.localHeight = meun.height;
|
||||
// 获取聊天对象信息
|
||||
if (options.item) {
|
||||
this.chatTarget = JSON.parse(options.item);
|
||||
this.chatTarget.title = `客服${this.chatTarget.employee_name}`;
|
||||
}
|
||||
|
||||
// 创建MQTT工具实例
|
||||
// this.mqttUtils = new MqttUtils({
|
||||
// username: 'dev01',
|
||||
// password: '211561',
|
||||
// clientId: 'mqtt_8812321421_' + Math.random().toString(16).substr(2, 8),
|
||||
// protocolVersion: 3,
|
||||
// connectTimeout: 30 * 1000,
|
||||
// reconnectPeriod: 5000,
|
||||
// keepalive: 60,
|
||||
// clean: true,
|
||||
// // 配置重连参数
|
||||
// maxReconnectAttempts: 15,
|
||||
// reconnectExponentialBackoff: true
|
||||
// });
|
||||
// this.mqttUtils.connect()
|
||||
|
||||
// 初始化MQTT连接
|
||||
this.initChat();
|
||||
},
|
||||
onShow() {
|
||||
// 检查MQTT连接状态
|
||||
// if (this.mqttUtils && !this.mqttUtils.getIsConnected()) {
|
||||
// this.connectingStatus = '连接中...';
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
async connect() {
|
||||
const options = {
|
||||
clientId: 'mqtt_821321321',
|
||||
username: 'dev01',
|
||||
password: '211561',
|
||||
keepalive: 30,
|
||||
protocolId: 'MQTT',
|
||||
protocolVersion: 3,
|
||||
reconnectPeriod: 1000,
|
||||
connectTimeout: 15 * 1000,
|
||||
clean: true,
|
||||
path: '/',
|
||||
pathname: 'mqtt'
|
||||
}
|
||||
// #ifdef H5
|
||||
this.client = mqtt.connect('ws://api.hshuishang.com:8084', options)
|
||||
console.log('H5',this.client)
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN||APP-PLUS
|
||||
this.client = mqtt.connect('wx://api.hshuishang.com:8084', options)
|
||||
console.log('WX',this.client)
|
||||
// #endif
|
||||
this.client.on('connect', async function () {
|
||||
this.isConnected = true
|
||||
console.log('连接成功')
|
||||
this.subscribe()
|
||||
}).on('reconnect', function () {
|
||||
console.log('正在重新连接.....')
|
||||
}).on('error', function (err) {
|
||||
console.log('on error',err)
|
||||
}).on('end', function () {
|
||||
console.log('on end')
|
||||
}).on('message', function (topic, message) {
|
||||
console.log(message)
|
||||
console.log(message.toString())
|
||||
})
|
||||
},
|
||||
async subscribe() {
|
||||
if (this.isConnected && this.client) {
|
||||
this.client.subscribe('contact/message/receive_msg',{qos:0}, function (err) {
|
||||
if (!err) {
|
||||
console.log('订阅成功')
|
||||
this.client.publish({'contact/message/send_msg': 0}, 'hello mqtt')
|
||||
} else {
|
||||
console.log('订阅失败:', err);
|
||||
this.connectingStatus = '订阅失败,请重试';
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('连接失败', this.isConnected, this.client)
|
||||
}
|
||||
},
|
||||
// 初始化聊天
|
||||
async initChat() {
|
||||
try {
|
||||
// 显示连接状态
|
||||
this.connectingStatus = '连接中...';
|
||||
|
||||
this.connect()
|
||||
// // 获取MQTT连接配置
|
||||
// await this.getMqttConfig();
|
||||
|
||||
// // 连接MQTT服务器
|
||||
// this.mqttUtils.connect();
|
||||
|
||||
// // 监听事件
|
||||
// this.mqttUtils.on('connect', (connack) => {
|
||||
// console.log('MQTT连接成功:', connack);
|
||||
// this.isConnected = true;
|
||||
// this.connectingStatus = '';
|
||||
|
||||
// // 订阅主题
|
||||
// this.mqttUtils.subscribe('contact/message/receive_msg', (err) => {
|
||||
// if (err) {
|
||||
// console.error('订阅失败:', err);
|
||||
// this.connectingStatus = '订阅失败,请重试';
|
||||
// } else {
|
||||
// console.log('订阅成功: contact/message/receive_msg');
|
||||
// // 加载历史消息
|
||||
// this.loadHistoryMessages();
|
||||
|
||||
// // 启动心跳包
|
||||
// this.startKeepalive();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// this.mqttUtils.on('error', (error) => {
|
||||
// console.log('MQTT连接错误:', error);
|
||||
// console.error('错误码:', error.code);
|
||||
// console.error('错误详情:', error.message);
|
||||
// this.isConnected = false;
|
||||
// this.connectingStatus = '连接失败,请检查网络';
|
||||
// });
|
||||
|
||||
// this.mqttUtils.on('disconnect', () => {
|
||||
// console.log('MQTT连接已断开');
|
||||
// })
|
||||
|
||||
// this.mqttUtils.on('message', (topic, message) => {
|
||||
// console.log('收到消息:', topic, message.toString());
|
||||
// try {
|
||||
// // 解析消息
|
||||
// let msgData;
|
||||
// if (typeof message === 'string') {
|
||||
// try {
|
||||
// msgData = JSON.parse(message);
|
||||
// } catch (e) {
|
||||
// console.error('消息解析失败', e);
|
||||
// return;
|
||||
// }
|
||||
// } else {
|
||||
// msgData = JSON.parse(message.toString());
|
||||
// }
|
||||
|
||||
// // 只处理与当前聊天对象相关的消息
|
||||
// if (msgData.receive_client === this.mqttConfig.clientId &&
|
||||
// msgData.send_client === this.chatTarget.openId) {
|
||||
|
||||
// // 添加到消息列表
|
||||
// this.messages.push({
|
||||
// id: `msg_${Date.now()}_${Math.random()}`,
|
||||
// content: msgData.content,
|
||||
// isSelf: false,
|
||||
// time: Date.now(),
|
||||
// isLoading: false
|
||||
// });
|
||||
|
||||
// // 滚动到底部
|
||||
// this.scrollToBottom();
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('处理消息失败', error);
|
||||
// }
|
||||
// });
|
||||
|
||||
// this.mqttUtils.on('reconnect', (attempts, delay) => {
|
||||
// console.log('MQTT正在重连...', attempts, delay);
|
||||
// this.connectingStatus = `连接已断开,正在重连(${attempts})...`;
|
||||
// });
|
||||
|
||||
// this.mqttUtils.on('close', () => {
|
||||
// console.log('MQTT连接关闭');
|
||||
// this.isConnected = false;
|
||||
// this.connectingStatus = '连接已关闭';
|
||||
// this.stopKeepalive();
|
||||
// });
|
||||
|
||||
// this.mqttUtils.on('offline', () => {
|
||||
// console.log('MQTT离线');
|
||||
// this.isConnected = false;
|
||||
// this.connectingStatus = '离线,请检查网络';
|
||||
// this.stopKeepalive();
|
||||
// });
|
||||
|
||||
// this.mqttUtils.on('end', () => {
|
||||
// console.log('MQTT客户端结束');
|
||||
// this.isConnected = false;
|
||||
// this.connectingStatus = '客户端已结束';
|
||||
// this.stopKeepalive();
|
||||
// });
|
||||
|
||||
// // 添加重连失败事件监听
|
||||
// this.mqttUtils.on('reconnectFailed', () => {
|
||||
// console.error('MQTT重连失败,已达到最大重连次数');
|
||||
// this.connectingStatus = '连接失败,请检查网络后重试';
|
||||
|
||||
// // 5秒后尝试重新初始化连接
|
||||
// this.reconnectFailedTimer = setTimeout(() => {
|
||||
// console.log('重连失败后,尝试重新初始化连接');
|
||||
// this.initChat();
|
||||
// }, 5000);
|
||||
// });
|
||||
|
||||
} catch (error) {
|
||||
console.error('初始化聊天失败', error);
|
||||
this.connectingStatus = '连接失败,请重试';
|
||||
|
||||
// 失败后尝试重新连接
|
||||
setTimeout(() => {
|
||||
this.initChat();
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取MQTT连接配置
|
||||
async getMqttConfig() {
|
||||
console.log("🚀 ~ getMqttConfig ~ getMqttConfig:", 11111)
|
||||
try {
|
||||
// // 直接使用已创建的MQTT实例的clientId
|
||||
// if (this.mqttUtils && this.mqttUtils.options.clientId) {
|
||||
// this.mqttConfig.clientId = this.mqttUtils.options.clientId;
|
||||
// return Promise.resolve();
|
||||
// }
|
||||
|
||||
// 如果没有已创建的实例或clientId,则通过API获取
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {
|
||||
worker_id: this.chatTarget.id || '',
|
||||
open_id: uni.getStorageSync('openId') || '',
|
||||
}
|
||||
request(apiArr.csGetToClientId, "POST", params).then((res) => {
|
||||
// 检查响应数据格式是否正确
|
||||
if (res && res.client_bind && res.client_bind.client_id_one && res.client_bind.client_id_two) {
|
||||
this.mqttConfig.clientId = res.client_bind.client_id_one; // 登录人的open_id
|
||||
this.chatTarget.openId = res.client_bind.client_id_two; // 接收方的open_id
|
||||
|
||||
// 如果MQTT实例已创建,更新clientId
|
||||
if (this.mqttUtils) {
|
||||
this.mqttUtils.options.clientId = this.mqttConfig.clientId;
|
||||
}
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
console.error('MQTT配置响应格式不正确:', res);
|
||||
reject(new Error('未获取到有效的MQTT配置'));
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取MQTT配置失败', error);
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取MQTT配置失败', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// MQTT断开连接回调 (为了兼容旧的调用)
|
||||
onDisconnect(packet) {
|
||||
console.log('MQTT连接断开', packet);
|
||||
this.isConnected = false;
|
||||
|
||||
// 根据断开原因设置不同的连接状态文本
|
||||
if (packet.error) {
|
||||
// 连接失败的情况
|
||||
this.connectingStatus = '连接失败,请检查网络或服务器';
|
||||
} else if (packet.reconnecting) {
|
||||
// 正在重连的情况
|
||||
this.connectingStatus = '连接已断开,正在重连...';
|
||||
} else {
|
||||
// 其他断开连接的情况
|
||||
this.connectingStatus = '连接已断开,正在重连...';
|
||||
}
|
||||
|
||||
// 停止心跳包
|
||||
this.stopKeepalive();
|
||||
},
|
||||
|
||||
// 加载历史消息
|
||||
async loadHistoryMessages() {
|
||||
try {
|
||||
this.isLoadingHistory = true;
|
||||
|
||||
// 调用API获取历史消息
|
||||
// const response = await this.$http.get(apiArr.csGetMsgRecord, {
|
||||
// mchId: this.chatTarget.mchId,
|
||||
// pageSize: 20,
|
||||
// pageNum: 1
|
||||
// });
|
||||
|
||||
// if (response.success && response.data && response.data.list) {
|
||||
// // 处理历史消息
|
||||
// const historyMessages = response.data.list.map(item => ({
|
||||
// id: item.id || `msg_${item.time}_${Math.random()}`,
|
||||
// content: item.content,
|
||||
// isSelf: item.from === 'self',
|
||||
// time: item.time,
|
||||
// isLoading: false
|
||||
// }));
|
||||
|
||||
// // 添加到消息列表开头
|
||||
// this.messages = [...historyMessages, ...this.messages];
|
||||
|
||||
// // 滚动到底部
|
||||
// this.scrollToBottom();
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('加载历史消息失败', error);
|
||||
} finally {
|
||||
this.isLoadingHistory = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多历史消息
|
||||
loadMoreHistory() {
|
||||
if (this.isLoadingHistory) return;
|
||||
// 这里可以实现加载更多历史消息的逻辑
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage() {
|
||||
const content = this.inputMessage.trim();
|
||||
if (!content || !this.mqttUtils || !this.mqttUtils.getIsConnected()) return;
|
||||
|
||||
// 创建消息对象
|
||||
const message = {
|
||||
id: `msg_${Date.now()}_${Math.random()}`,
|
||||
content: content,
|
||||
isSelf: true,
|
||||
time: Date.now(),
|
||||
isLoading: true
|
||||
};
|
||||
|
||||
// 添加到消息列表
|
||||
// this.messages.push(message);
|
||||
|
||||
// 滚动到底部
|
||||
this.scrollToBottom();
|
||||
|
||||
// 按照用户提供的格式构建发送消息
|
||||
const msgData = {
|
||||
bind_id: 1, // 聊天列表的ID,这里暂时固定为1
|
||||
send_client: this.mqttConfig.clientId, // 消息发送方open_id
|
||||
receive_client: this.chatTarget.openId, // 消息接收方open_id
|
||||
type: 1, // 消息类型,1表示文字消息
|
||||
content: content, // 消息内容
|
||||
receive_read_status: 2, // 接收方阅读状态
|
||||
};
|
||||
|
||||
this.mqttUtils.publish(
|
||||
'contact/message/send_msg', // 使用指定的发送消息主题
|
||||
JSON.stringify(msgData),
|
||||
{ Qos: 0 },
|
||||
(err) => {
|
||||
// 更新消息状态
|
||||
const index = this.messages.findIndex(m => m.id === message.id);
|
||||
if (index !== -1) {
|
||||
this.$set(this.messages[index], 'isLoading', false);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
console.error('发送消息失败', err);
|
||||
// 可以在这里添加消息发送失败的处理逻辑
|
||||
} else {
|
||||
console.log('发送消息成功');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 清空输入框
|
||||
this.inputMessage = '';
|
||||
this.canSend = false;
|
||||
},
|
||||
|
||||
// 处理输入事件
|
||||
handleInput() {
|
||||
this.canSend = this.inputMessage.trim().length > 0;
|
||||
},
|
||||
|
||||
// 是否需要显示时间分割线
|
||||
needShowTime(index) {
|
||||
if (index === 0) return true;
|
||||
|
||||
const currentMsg = this.messages[index];
|
||||
const prevMsg = this.messages[index - 1];
|
||||
|
||||
// 如果两条消息间隔超过5分钟,则显示时间分割线
|
||||
return (currentMsg.time - prevMsg.time) > 5 * 60 * 1000;
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
const date = new Date(time);
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
return `${hours}:${minutes}`;
|
||||
},
|
||||
|
||||
// 滚动到底部
|
||||
scrollToBottom() {
|
||||
setTimeout(() => {
|
||||
this.scrollToView = 'msg-' + (this.messages.length - 1);
|
||||
}, 100);
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 启动心跳包 - 增强版:添加错误处理和状态检查
|
||||
startKeepalive() {
|
||||
// 停止之前的定时器
|
||||
this.stopKeepalive();
|
||||
|
||||
// 每30秒发送一次心跳包
|
||||
this.keepaliveTimer = setInterval(() => {
|
||||
if (this.mqttUtils && this.mqttUtils.getIsConnected()) {
|
||||
const keepaliveData = {
|
||||
client_id: this.mqttConfig.clientId
|
||||
};
|
||||
|
||||
this.mqttUtils.publish(
|
||||
'contact/message/keep_time',
|
||||
JSON.stringify(keepaliveData),
|
||||
{},
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error('发送心跳包失败', err);
|
||||
// 心跳包发送失败可能表示连接有问题,可以考虑触发重连
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
console.log('心跳包发送失败,尝试检查连接状态');
|
||||
// 这里可以添加额外的连接检查逻辑
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.warn('MQTT未连接,停止心跳包');
|
||||
this.stopKeepalive();
|
||||
}
|
||||
}, 30000);
|
||||
},
|
||||
|
||||
// 停止心跳包
|
||||
stopKeepalive() {
|
||||
if (this.keepaliveTimer) {
|
||||
clearInterval(this.keepaliveTimer);
|
||||
this.keepaliveTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// 跳转到切换客服页面
|
||||
goToChangeService() {
|
||||
uni.navigateTo({
|
||||
url: '/packages/customerService/changeService/index?currentMchId=' + this.chatTarget.mchId
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 页面卸载时停止心跳包
|
||||
onUnload() {
|
||||
// 断开MQTT连接
|
||||
if (this.mqttUtils) {
|
||||
this.mqttUtils.disconnect();
|
||||
}
|
||||
|
||||
// 停止心跳包
|
||||
this.stopKeepalive();
|
||||
|
||||
// 清除重连失败提示定时器
|
||||
if (this.reconnectFailedTimer) {
|
||||
clearTimeout(this.reconnectFailedTimer);
|
||||
this.reconnectFailedTimer = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("./index.css");
|
||||
</style>
|
||||
@ -109,6 +109,12 @@
|
||||
mode="" />
|
||||
<view>点评</view>
|
||||
</view>
|
||||
<view class="left_label" @click="handleKfClick">
|
||||
<image
|
||||
src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/kefu0.png"
|
||||
mode="" />
|
||||
<view>客服</view>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="isShow" class="btn" @click="handleQuickPayClick">快捷买单</text>
|
||||
</view>
|
||||
@ -233,6 +239,11 @@ export default {
|
||||
NavgateTo('../comment/index');
|
||||
},
|
||||
|
||||
// 客服
|
||||
handleKfClick() {
|
||||
NavgateTo('/packages/customerService/changeService/index');
|
||||
},
|
||||
|
||||
like(e) {
|
||||
request(apiArr.merChantCommentLike, "POST", {
|
||||
evaluation_id: e.id,
|
||||
|
||||
@ -18,9 +18,8 @@
|
||||
|
||||
<view class="Msg">
|
||||
<view class="payMony">
|
||||
¥<input type="number" v-model="Money" placeholder="付款金额" placeholder-style="font-size: 50rpx;"
|
||||
@blur="handleMoneyInput" @focus="showKeyboard" step="0.01" min="0.01">
|
||||
<cu-keyboard ref="cukeyboard" @change="change" @confirm="keyboardConfirm" @hide="hide"></cu-keyboard>
|
||||
¥<input type="digit" v-model="Money" placeholder="付款金额" placeholder-style="font-size: 50rpx;"
|
||||
@blur="handleMoneyInput" step="0.01" min="0.01">
|
||||
</view>
|
||||
<view class="payRemark">
|
||||
<input type="text" v-model="remarks" placeholder="付款备注">
|
||||
@ -138,48 +137,7 @@ export default {
|
||||
|
||||
|
||||
methods: {
|
||||
showKeyboard() {
|
||||
this.$refs.cukeyboard.open();
|
||||
},
|
||||
// 键盘输入内容变化时触发
|
||||
change(value) {
|
||||
// 先进行基本的输入格式化
|
||||
let formattedValue = value.toString().replace(/[^\d.]/g, '');
|
||||
|
||||
// 如果第一个数字是0且后面还有其他数字,则删除这个0
|
||||
if (formattedValue.length >= 2 && formattedValue[0] === '0' && formattedValue[1] !== '.') {
|
||||
formattedValue = formattedValue.substring(1);
|
||||
}
|
||||
|
||||
// 限制只能有一个小数点
|
||||
formattedValue = formattedValue.replace(/\.{2,}/g, '.');
|
||||
|
||||
// 确保小数点后最多两位
|
||||
const parts = formattedValue.split('.');
|
||||
if (parts.length > 1) {
|
||||
formattedValue = parts[0] + '.' + parts[1].substring(0, 2);
|
||||
}
|
||||
|
||||
// 处理开头是小数点的情况
|
||||
if (formattedValue.startsWith('.')) {
|
||||
formattedValue = '0' + formattedValue;
|
||||
}
|
||||
|
||||
this.Money = formattedValue;
|
||||
},
|
||||
// 点击键盘确认按钮时触发
|
||||
keyboardConfirm(value) {
|
||||
// 如果最后一位是小数点,则删除小数点
|
||||
if (value && value.endsWith('.')) {
|
||||
value = value.slice(0, -1);
|
||||
}
|
||||
this.Money = value;
|
||||
this.$refs.cukeyboard.close();
|
||||
},
|
||||
// 键盘隐藏时触发
|
||||
hide() {
|
||||
// 可以在这里添加键盘隐藏后的处理逻辑
|
||||
},
|
||||
handleMoneyInput() {
|
||||
// 清除非数字和非小数点的字符
|
||||
let value = this.Money.toString().replace(/[^\d.]/g, '');
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
<view class="grid-item">
|
||||
<image class="nav_icon" :src="item.image" mode=""></image>
|
||||
</view>
|
||||
<text class="grid-text">{{ item.name }}</text>
|
||||
<text class="grid-text">{{ item.name }}</text>
|
||||
</u-grid-item>
|
||||
</u-grid>
|
||||
</view>
|
||||
@ -85,25 +85,29 @@ import {
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
top: "",
|
||||
localHeight: "",
|
||||
active: 1,
|
||||
show: false,
|
||||
show2: false,
|
||||
baseList: [
|
||||
{
|
||||
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/page_user_Group_1568.png",
|
||||
name: "订单",
|
||||
url: "",
|
||||
}, {
|
||||
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/gp_cancelAfterVerification.png",
|
||||
name: "到店核销",
|
||||
url: "",
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
return {
|
||||
top: "",
|
||||
localHeight: "",
|
||||
active: 1,
|
||||
show: false,
|
||||
show2: false,
|
||||
baseList: [
|
||||
{
|
||||
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/page_user_Group_1568.png",
|
||||
name: "订单",
|
||||
url: "/packages/storeManagement/order/index",
|
||||
}, {
|
||||
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/gp_cancelAfterVerification.png",
|
||||
name: "到店核销",
|
||||
url: "/packages/storeManagement/orderVerification/index",
|
||||
}, {
|
||||
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/kefu0.png",
|
||||
name: "客服",
|
||||
url: "/packages/customerService/chattingRecords/index",
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
const meun = menuButtonInfo();
|
||||
this.top = meun.top;
|
||||
@ -129,10 +133,12 @@ export default {
|
||||
},
|
||||
click(item) {
|
||||
console.log("🚀 ~ click ~ item:", item)
|
||||
if(item.name === '到店核销'){
|
||||
NavgateTo('/packages/storeManagement/orderVerification/index')
|
||||
}else{
|
||||
NavgateTo('/packages/storeManagement/order/index')
|
||||
// 根据item中的url进行跳转
|
||||
if (item.url) {
|
||||
NavgateTo(item.url)
|
||||
} else {
|
||||
console.warn('未配置跳转URL');
|
||||
// 可添加默认跳转或提示
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
42
pages.json
42
pages.json
@ -109,6 +109,48 @@
|
||||
"navigationBarTitleText": "选择小区",
|
||||
"navigationBarBackgroundColor": "#fff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "routingInspection/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "巡更巡检",
|
||||
"navigationBarBackgroundColor": "#fff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "addRoutingInspection/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "任务详情",
|
||||
"navigationBarBackgroundColor": "#fff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "packages/customerService",
|
||||
"pages": [
|
||||
{
|
||||
"path": "index/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#F9F9F9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "chattingRecords/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服聊天记录",
|
||||
"navigationBarBackgroundColor": "#fff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "changeService/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#F9F9F9"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
0
utils/mqtt.js
Normal file
0
utils/mqtt.js
Normal file
Loading…
x
Reference in New Issue
Block a user