客服部分

This commit is contained in:
赵毅 2025-09-24 08:37:16 +08:00
parent 6b9ad175d6
commit 5f6e76ea71
21 changed files with 3591 additions and 5307 deletions

6
api/customerService.js Normal file
View 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", //商户客服列表
};

View File

@ -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",//门店信息创建

4596
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,5 +15,9 @@
"前端组件",
"通用组件"
]
},
"dependencies": {
"abort-controller": "^3.0.0",
"mqtt": "^3.0.0"
}
}

View 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;
}

View 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);
}
// getGoodsListgetShopdetailgetGoodsNum
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>

View 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"
}
}

View File

@ -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) {

View File

@ -485,13 +485,43 @@ export default {
// this.payInfoId
await request(apiArr.OrderPay, "POST", { order_pay_id: this.payInfoId }).then(
async (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,
}
await request(apiArr.OrderPay, "POST", params).then(
(res) => {
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'
})
}
);
}
);
},

View 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;
}

View 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>

View 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;
}

View 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>

View 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;
}

View 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 {
// // 使MQTTclientId
// if (this.mqttUtils && this.mqttUtils.options.clientId) {
// this.mqttConfig.clientId = this.mqttUtils.options.clientId;
// return Promise.resolve();
// }
// clientIdAPI
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
// MQTTclientId
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, // ID1
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>

View File

@ -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,

View File

@ -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, '');
// 00
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, '');

View File

@ -95,11 +95,15 @@ export default {
{
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/page_user_Group_1568.png",
name: "订单",
url: "",
url: "/packages/storeManagement/order/index",
}, {
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/gp_cancelAfterVerification.png",
name: "到店核销",
url: "",
url: "/packages/storeManagement/orderVerification/index",
}, {
image: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com/kefu0.png",
name: "客服",
url: "/packages/customerService/chattingRecords/index",
}
],
}
@ -129,10 +133,12 @@ export default {
},
click(item) {
console.log("🚀 ~ click ~ item:", item)
if(item.name === '到店核销'){
NavgateTo('/packages/storeManagement/orderVerification/index')
// itemurl
if (item.url) {
NavgateTo(item.url)
} else {
NavgateTo('/packages/storeManagement/order/index')
console.warn('未配置跳转URL');
//
}
}
},

View File

@ -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
View File

1384
yarn.lock

File diff suppressed because it is too large Load Diff