Compare commits

..

13 Commits

20 changed files with 557 additions and 338 deletions

View File

@ -21,4 +21,7 @@ export const apiArr = {
goodsSearch: '/api/v2/wechat/commodity/search', // 商品搜索 goodsSearch: '/api/v2/wechat/commodity/search', // 商品搜索
cancelPay: '/api/v2/wechat/commodity/order/cancel_pay', // 用户取消支付 cancelPay: '/api/v2/wechat/commodity/order/cancel_pay', // 用户取消支付
adverGoodsList: '/api/v2/wechat/commodity/adver-goods-list', // 广告货品列表
} }

33
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.1", "version": "1.0.1",
"dependencies": { "dependencies": {
"abort-controller": "^3.0.0", "abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"mqtt": "^3.0.0", "mqtt": "^3.0.0",
"vue": "^3.5.21" "vue": "^3.5.21"
} }
@ -220,6 +221,30 @@
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
} }
}, },
"node_modules/bl/node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/bl/node_modules/readable-stream": { "node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@ -245,9 +270,9 @@
} }
}, },
"node_modules/buffer": { "node_modules/buffer": {
"version": "5.7.1", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -265,7 +290,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"base64-js": "^1.3.1", "base64-js": "^1.3.1",
"ieee754": "^1.1.13" "ieee754": "^1.2.1"
} }
}, },
"node_modules/buffer-from": { "node_modules/buffer-from": {

View File

@ -18,6 +18,7 @@
}, },
"dependencies": { "dependencies": {
"abort-controller": "^3.0.0", "abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"mqtt": "^3.0.0", "mqtt": "^3.0.0",
"vue": "^3.5.21" "vue": "^3.5.21"
} }

View File

@ -4,88 +4,27 @@
<view class="goods-list"> <view class="goods-list">
<!-- 商品项 --> <!-- 商品项 -->
<view v-for="(item, index) in goodsList" :key="index"> <view v-for="(item, index) in goodsList" :key="index">
<!-- 有多个货品 --> <view>
<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-item2" @click="toDetail(item)">
<view class="goods-image"> <view class="goods-image">
<image :src="item.commodity_pic" mode="aspectFill"></image> <image :src="item.commodity_pic" mode="aspectFill"></image>
</view> </view>
<view class="goods-info"> <view class="goods-info">
<view class="goods-name">{{ item.commodity_name }}</view> <view class="goods-name">{{ item.goods_name }}</view>
<view class="goods-desc">{{ item.commodity_intro }}</view> <view class="goods-desc">{{ item.goods_spec }}</view>
<view class="price-container"> <view class="price-container">
<view class="group-price"> <view class="group-price">
<view class="group-price1">团购</view> <view class="group-price1">活动价</view>
<view class="group-price2">{{ item.group_buy_goods_list[0].group_buy_price }}/{{ <view class="group-price2">{{ item.promotional_price }}/{{ item.goods_unit }}
item.group_buy_goods_list[0].goods_unit }}</view> </view>
</view> </view>
<view class="quantity-control"> <view class="quantity-control">
<view class="decrease-btn" @tap.stop="decreaseQuantity(index, 0)">-</view> <view class="decrease-btn" @tap.stop="decreaseQuantity(index)">-</view>
<view class="quantity">{{ item.group_buy_goods_list[0].quantity }}</view> <view class="quantity">{{ item.quantity }}</view>
<view class="increase-btn" @tap.stop="increaseQuantity(index, 0)">+</view> <view class="increase-btn" @tap.stop="increaseQuantity(index)">+</view>
</view> </view>
</view> </view>
<view class="original-price">单买价 {{ item.group_buy_goods_list[0].sales_price }}/{{ <view class="original-price">零售价 {{ item.retail_price }}/{{ item.goods_unit }}</view>
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>
@ -115,10 +54,12 @@ export default {
timer: null, // ID timer: null, // ID
endTime: '', // endTime: '', //
updateTime: Date.now(), // updateTime: Date.now(), //
goodsDetail: [] goodsDetail: [],
idVal: '',
}; };
}, },
onLoad() { onLoad(options) {
this.idVal = Number(options.id)
// this.getGoodsList() // this.getGoodsList()
}, },
onShow() { onShow() {
@ -150,21 +91,15 @@ export default {
return return
} }
const params = { const params = {
user_id: uni.getStorageSync('userId') adver_id: this.idVal,
} }
return request(apiArr.groupBuyList, 'POST', params).then(res => { return request(shopApi.adverGoodsList, 'POST', params).then(res => {
const list = res.group_buy_list.map(item => { const list = res.adver_goods_list.map(item => {
// quantity // quantity
const group_buy_goods_list = item.group_buy_goods_list.map(sku => ({
...sku,
commodity_pic: picUrl + sku.commodity_pic,
quantity: 0
}));
return { return {
...item, ...item,
commodity_pic: picUrl + item.commodity_pic, commodity_pic: picUrl + item.goods_pic,
showSkuList: false, quantity: 0
group_buy_goods_list
} }
}) })
this.goodsList = list this.goodsList = list
@ -173,7 +108,7 @@ export default {
}, },
getShopdetail() { getShopdetail() {
const params = { const params = {
is_group_buy: 1, is_adver: 1,
} }
return request(shopApi.getCar, "POST", params).then((res) => { return request(shopApi.getCar, "POST", params).then((res) => {
this.carNum = res.total; this.carNum = res.total;
@ -187,95 +122,82 @@ export default {
if (!this.goodsDetail || !this.goodsList || this.goodsList.length === 0) { if (!this.goodsDetail || !this.goodsList || this.goodsList.length === 0) {
return; return;
} }
// // item
this.goodsList.forEach(goods => { this.goodsList.forEach(goods => {
// //
goods.group_buy_goods_list.forEach(sku => { const matchedItem = this.goodsDetail.find(item => item.goods_id === goods.goods_id);
//
const matchedItem = this.goodsDetail.find(item => item.goods_id === sku.goods_id);
// quantity // quantity
if (matchedItem) { if (matchedItem) {
sku.quantity = matchedItem.count; goods.quantity = matchedItem.count;
} else { } else {
// 0 // 0
sku.quantity = 0; goods.quantity = 0;
} }
}); });
});
}, },
toDetail(itemObj) { toDetail(itemObj) {
const item = { // const item = {
...itemObj, // ...itemObj,
groupById: itemObj.group_buy_activity_info.id // groupById: itemObj.group_buy_activity_info ? itemObj.group_buy_activity_info.id : ''
}; // };
NavgateTo(`/packages/shop/groupPurchaseDetail/index?item=${JSON.stringify(item)}`) // NavgateTo(`/packages/shop/groupPurchaseDetail/index?item=${JSON.stringify(item)}`)
}, },
// //
getPriceRange(goodsList) { increaseQuantity(index) {
if (!goodsList || goodsList.length === 0) return '¥0'; const item = this.goodsList[index]
const prices = goodsList.map(item => Number(item.sales_price)); if (item.quantity == 0) {
const minPrice = Math.min(...prices); item.quantity += item.min_order_quantity || 1
const maxPrice = Math.max(...prices); this.carNum += item.min_order_quantity || 1
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 { } else {
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].total_stock) { if (item.quantity == item.total_stock) {
uni.showToast({ uni.showToast({
title: '库存不足', title: '库存不足',
icon: 'none' icon: 'none'
}); });
return return
} }
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].max_limit_quantity) { if (item.quantity == item.max_limit_quantity) {
uni.showToast({ uni.showToast({
title: '一次最多购买' + this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].max_limit_quantity + '件', title: '一次最多购买' + item.max_limit_quantity + '件',
icon: 'none' icon: 'none'
}); });
return return
} }
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity++; item.quantity++;
this.carNum++; this.carNum++;
} }
const params = { const params = {
goods_id_and_count: [ goods_id_and_count: [
{ {
goods_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].id, goods_id: item.goods_id,
count: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity, count: item.quantity,
}, },
], ],
group_buy_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].group_buy_activity_info.id adver_id: item.adver_id
} }
this.updateCar(params); this.updateCar(params);
}, },
// //
decreaseQuantity(goodsIndex, skuIndex) { decreaseQuantity(index) {
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity > 0) { const item = this.goodsList[index]
if (this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity == this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].min_order_quantity) { if (item.quantity > 0) {
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity = 0 if (item.quantity == (item.min_order_quantity || 1)) {
item.quantity = 0
this.carNum = 0 this.carNum = 0
} else { } else {
this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity--; item.quantity--;
this.carNum--; this.carNum--;
} }
const params = { const params = {
goods_id_and_count: [ goods_id_and_count: [
{ {
goods_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].id, goods_id: item.goods_id,
count: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].quantity, count: item.quantity,
}, },
], ],
group_buy_id: this.goodsList[goodsIndex].group_buy_goods_list[skuIndex].group_buy_activity_info.id adver_id: item.adver_id
} }
this.updateCar(params); this.updateCar(params);
} else { } else {
@ -298,9 +220,9 @@ export default {
// //
shopCar() { shopCar() {
const item = { const item = {
is_group_buy: 1, is_adver: 1,
} }
NavgateTo("../shopCar/index?item=" + JSON.stringify(item)); NavgateTo("/packages/shop/shopCar/index?item=" + JSON.stringify(item));
}, },
// //
getEndTheCountdown(endTime) { getEndTheCountdown(endTime) {

View File

@ -6,14 +6,15 @@
<view v-if="isLoading" class="loading">加载中...</view> <view v-if="isLoading" class="loading">加载中...</view>
<view v-else-if="recordsList.length === 0" class="empty-records">暂无聊天记录</view> <view v-else-if="recordsList.length === 0" class="empty-records">暂无聊天记录</view>
<view v-else> <view v-else>
<view v-for="record in recordsList" :key="record.mchId" class="record-item" @tap="goToChatPage(record)"> <view v-for="record in recordsList" :key="record.id">
<view class="record-item" @tap="goToChatPage(record)">
<image class="record-avatar" :src="record.avatar || '/static/logo.png'" mode="aspectFill"></image> <image class="record-avatar" :src="record.avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="record-info"> <view class="record-info">
<view class="record-title-row"> <view class="record-title-row">
<text class="record-title">{{ record.title }}</text> <text class="record-title">{{ record.contact_name }}</text>
<text class="record-time">{{ formatTime(record.lastMsgTime) }}</text> <text class="record-time">{{ record.update_time }}</text>
</view>
</view> </view>
<text class="record-last-msg">{{ record.lastMsg || '暂无消息' }}</text>
</view> </view>
</view> </view>
</view> </view>
@ -61,63 +62,14 @@ export default {
page_num: this.page_num, page_num: this.page_num,
page_size: this.page_size, page_size: this.page_size,
}).then((res) => { }).then((res) => {
console.log("🚀 ~ loadChattingRecords ~ res:", res) this.recordsList = res.msg_list
}) })
// 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) { } catch (error) {
console.error('加载聊天记录失败', error); console.error('加载聊天记录失败', error);
// 使
this.recordsList = this.getMockRecords();
} finally { } finally {
this.isLoading = false; 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) { formatTime(time) {
const date = new Date(time); const date = new Date(time);
@ -152,13 +104,29 @@ export default {
// //
goToChatPage(record) { goToChatPage(record) {
// 使 const params = {
uni.setStorageSync('currentChatTarget', record); mch_id: record.mch_id,
}
request(apiArr.csGetMchContactList, "POST", params).then((res) => {
if (res.rows && res.rows.length > 0) {
res.rows.map(item => {
item.employee_image = picUrl + item.employee_image;
})
const itemObj = res.rows.find(item => item.employee_mobile === record.two.account)
// //
uni.navigateTo({ uni.navigateTo({
url: '/packages/customerService/index/index?mchId=' + record.mchId url: '/packages/customerService/index/index?item=' + JSON.stringify(itemObj)
}); });
} else {
console.log("没有获取到客服列表数据");
uni.showToast({
title: '该客服不存在',
icon: 'none'
})
}
})
} }
} }
}; };

View File

@ -1,7 +1,8 @@
/* 客服聊天页面样式 */ /* 客服聊天页面样式 */
page { page {
background-color: #f6f7fb; background-color: #f6f7fb;
overflow-y: hidden; height: 100vh;
overflow: hidden;
} }
/* 聊天容器 */ /* 聊天容器 */
@ -10,6 +11,11 @@ page {
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
background-color: #f6f7fb; background-color: #f6f7fb;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1;
} }
/* 聊天头部 */ /* 聊天头部 */
@ -22,6 +28,8 @@ page {
background-color: #fff; background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding-bottom: 15rpx; padding-bottom: 15rpx;
position: relative;
z-index: 2;
} }
.back-btn { .back-btn {
@ -130,6 +138,8 @@ page {
padding: 10px 15px; padding: 10px 15px;
border-top: 1px solid #eee; border-top: 1px solid #eee;
margin-bottom: 20rpx; margin-bottom: 20rpx;
position: relative;
z-index: 2;
} }
.input-container { .input-container {

View File

@ -1,4 +1,4 @@
<template> packages/customerService/index/index<template>
<view class="chat-container"> <view class="chat-container">
<!-- 聊天头部 --> <!-- 聊天头部 -->
<view :style="{ paddingTop: top + 'px', height: localHeight + 'px' }" class="chat-header"> <view :style="{ paddingTop: top + 'px', height: localHeight + 'px' }" class="chat-header">
@ -15,8 +15,8 @@
<view v-if="connectingStatus" class="connecting-status">{{ connectingStatus }}</view> <view v-if="connectingStatus" class="connecting-status">{{ connectingStatus }}</view>
<!-- 聊天消息区域 --> <!-- 聊天消息区域 -->
<scroll-view :scroll-into-view="scrollToView" class="chat-messages" scroll-y="true" <scroll-view :scroll-into-view="scrollToView" class="chat-messages" scroll-y="true" @scrolltoupper="loadMoreHistory"
@scrolltoupper="loadMoreHistory"> @scrolltolower="loadMoreHistory" lower-threshold="100" upper-threshold="100">
<!-- 加载历史消息提示 --> <!-- 加载历史消息提示 -->
<view v-if="isLoadingHistory" class="message-time">加载历史消息...</view> <view v-if="isLoadingHistory" class="message-time">加载历史消息...</view>
@ -31,7 +31,7 @@
'other': !message.isSelf, 'other': !message.isSelf,
'loading': message.isLoading 'loading': message.isLoading
}" class="message-item"> }" class="message-item">
<image :src="message.isSelf ? userAvatar : (chatTarget.avatar || '/static/logo.png')" class="message-avatar" <image :src="message.isSelf ? userAvatar : (chatTarget.employee_image)" class="message-avatar"
mode="aspectFill"></image> mode="aspectFill"></image>
<view class="message-content"> <view class="message-content">
{{ message.content }} {{ message.content }}
@ -44,7 +44,9 @@
<view class="chat-input-area"> <view class="chat-input-area">
<view class="input-container"> <view class="input-container">
<textarea v-model="inputMessage" :adjust-position="true" class="message-input" placeholder="请输入消息..." <textarea v-model="inputMessage" :adjust-position="true" class="message-input" placeholder="请输入消息..."
@confirm="sendMessage" @input="handleInput"></textarea> @confirm="sendMessage" @input="handleInput" auto-height hold-keyboard="true"
enable-keyboard-accessory-view="true" cursor-spacing="10" maxlength="500" @focus="onInputFocus"
@blur="onInputBlur"></textarea>
<button :disabled="!canSend || !client || !isConnected" class="send-btn" @tap="sendMessage"> <button :disabled="!canSend || !client || !isConnected" class="send-btn" @tap="sendMessage">
发送 发送
</button> </button>
@ -54,9 +56,9 @@
</template> </template>
<script> <script>
import { menuButtonInfo, request } from '@/utils' import { picUrl, menuButtonInfo, request, NavgateTo } from "../../../utils";
import { apiArr } from '@/api/customerService' import { apiArr } from '@/api/customerService'
import MqttUtils from '@/utils/mqtt' import mqttTool from '@/utils/mqtt'
export default { export default {
data() { data() {
@ -71,7 +73,7 @@ export default {
openId: '' // open_id openId: '' // open_id
}, },
// //
userAvatar: '/static/logo.png', userAvatar: '',
// //
messages: [], messages: [],
// //
@ -93,7 +95,14 @@ export default {
client: null, client: null,
mqttConfig: {}, mqttConfig: {},
// //
reconnectFailedTimer: null reconnectFailedTimer: null,
//
pageNum: 1,
pageSize: 10,
//
hasMoreHistory: true,
//
scrollToBottomFlag: false
} }
}, },
onLoad(options) { onLoad(options) {
@ -108,21 +117,47 @@ export default {
// MQTT // MQTT
this.initChat() this.initChat()
this.getMqttConfig() this.getMqttConfig()
//
this.userAvatar = picUrl + uni.getStorageSync('headPhoto')
}, },
onShow() { onShow() {
}, },
methods: { methods: {
async connect() { async connect() {
this.client = null
const options = { const options = {
clientId: this.selfClientId clientId: this.selfClientId
} }
this.client = MqttUtils.connect(options)
//
const callbacks = {
onConnect: () => {
console.log('客服连接成功')
this.isConnected = true
this.connectingStatus = ''
},
onDisconnect: this.onDisconnect.bind(this),
onError: (error) => {
console.error('客服连接错误:', error)
this.isConnected = false
this.connectingStatus = '连接错误,请重试'
},
onReconnect: () => {
console.log('客服正在重连...')
this.isConnected = false
this.connectingStatus = '连接已断开,正在重连...'
}
}
this.client = mqttTool.connect(options, callbacks)
this.isConnected = !!this.client this.isConnected = !!this.client
await this.subscribe() await this.subscribe()
this.client.on('message', (topic, message) => { this.client.on('message', (topic, message) => {
let de = new TextDecoder('utf-8') const msgStr = Buffer.from(message).toString('utf8'); // UTF-8
let msg = de.decode(message) const msg = JSON.parse(msgStr); //
let jsMsg = JSON.parse(msg)
let jsMsg = msg // 使
console.log('收到消息', topic, msg) console.log('收到消息', topic, msg)
if (jsMsg.send_client === this.selfClientId || jsMsg.receive_client === this.selfClientId) { if (jsMsg.send_client === this.selfClientId || jsMsg.receive_client === this.selfClientId) {
console.log('接收或发送人是我') console.log('接收或发送人是我')
@ -142,9 +177,10 @@ export default {
}, },
async subscribe() { async subscribe() {
if (this.isConnected && this.client) { if (this.isConnected && this.client) {
this.client.subscribe('contact/message/receive_msg', { qos: 0 }, function(err){ this.client.subscribe('contact/message/receive_msg', { qos: 0 }, (err) => {
if (!err) { if (!err) {
console.log('订阅成功', 'contact/message/receive_msg', { qos: 0 }) console.log('订阅成功', 'contact/message/receive_msg', { qos: 0 })
this.connectingStatus = ''
} else { } else {
console.log('订阅失败:', err) console.log('订阅失败:', err)
this.connectingStatus = '订阅失败,请重试' this.connectingStatus = '订阅失败,请重试'
@ -152,20 +188,25 @@ export default {
}) })
} else { } else {
console.log('连接失败', this.isConnected, this.client) console.log('连接失败', this.isConnected, this.client)
this.connectingStatus = '连接失败,请重试'
} }
}, },
// //
async initChat() { async initChat() {
try { try {
// //
this.connectingStatus = '连接中...' this.connectingStatus = '正在连接客服...'
await this.connect() await this.connect()
//
this.startKeepalive()
//
this.loadHistoryMessages()
} catch (error) { } catch (error) {
console.error('初始化聊天失败', error) console.error('初始化聊天失败', error)
this.connectingStatus = '连接失败,请重试' this.connectingStatus = '连接失败,请检查网络'
// //
setTimeout(() => { this.reconnectFailedTimer = setTimeout(() => {
this.initChat() this.initChat()
}, 3000) }, 3000)
} }
@ -173,7 +214,6 @@ export default {
// MQTT // MQTT
async getMqttConfig() { async getMqttConfig() {
console.log('🚀 ~ getMqttConfig ~ getMqttConfig:', 11111)
try { try {
// clientIdAPI // clientIdAPI
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -204,19 +244,16 @@ export default {
} }
}, },
// MQTT () // MQTT
onDisconnect(packet) { onDisconnect(packet) {
console.log('MQTT连接断开', packet) console.log('MQTT连接断开', packet)
this.isConnected = false this.isConnected = false
this.client = null
// //
if (packet.error) { if (packet && packet.error) {
// //
this.connectingStatus = '连接失败,请检查网络或服务器' this.connectingStatus = '连接失败,请检查网络'
} else
if (packet.reconnecting) {
//
this.connectingStatus = '连接已断开,正在重连...'
} else { } else {
// //
this.connectingStatus = '连接已断开,正在重连...' this.connectingStatus = '连接已断开,正在重连...'
@ -228,8 +265,53 @@ export default {
// //
async loadHistoryMessages() { async loadHistoryMessages() {
if (!this.hasMoreHistory || this.isLoadingHistory) {
return
}
try { try {
this.isLoadingHistory = true this.isLoadingHistory = true
// mqttConfig.bind_id
if (!this.mqttConfig.bind_id) {
await this.getMqttConfig()
}
const params = {
bind_id: this.mqttConfig.bind_id,
order: 'desc', //
page_num: this.pageNum,
page_size: this.pageSize
}
console.log('请求历史消息参数:', params)
const res = await request(apiArr.csGetMsgRecord, 'POST', params)
console.log('历史消息返回结果:', res)
if (res && res.code === 1 && res.data && res.data.msg_record) {
const historyMessages = res.data.msg_record
//
if (historyMessages.length === 0) {
this.hasMoreHistory = false
return
}
//
//
const formattedMessages = historyMessages.map(msg => ({
content: msg.content,
time: new Date(msg.create_time).getTime(),
isSelf: msg.send_client === this.mqttConfig.clientId,
isLoading: false
})).reverse() //
//
this.messages = [...formattedMessages, ...this.messages]
//
this.pageNum++
}
} catch (error) { } catch (error) {
console.error('加载历史消息失败', error) console.error('加载历史消息失败', error)
} finally { } finally {
@ -239,9 +321,9 @@ export default {
// //
loadMoreHistory() { loadMoreHistory() {
if (this.isLoadingHistory) { if (!this.isLoadingHistory && this.hasMoreHistory) {
this.loadHistoryMessages()
} }
//
}, },
// //
@ -285,6 +367,21 @@ export default {
this.canSend = this.inputMessage.trim().length > 0 this.canSend = this.inputMessage.trim().length > 0
}, },
onInputFocus() {
//
this.scrollToBottomFlag = true;
setTimeout(() => {
if (this.scrollToBottomFlag) {
this.scrollToBottom();
}
}, 300);
},
onInputBlur() {
//
this.scrollToBottomFlag = false;
},
// 线 // 线
needShowTime(index) { needShowTime(index) {
if (index === 0) return true if (index === 0) return true

View File

@ -116,8 +116,8 @@ export default {
currentIndex: 0, currentIndex: 0,
checkedItems: [false, false, false, false], checkedItems: [false, false, false, false],
address: "", address: "",
page_size: "10", page_size: 10,
page_num: "1", page_num: 1,
flag: false, flag: false,
merchatList: [], merchatList: [],
@ -213,7 +213,7 @@ export default {
}); });
if (res.rows.length == this.page_size) { if (res.rows.length == this.page_size) {
this.page_num = this.page_num + 1; this.page_num = this.page_size + 10;
this.flag = true; this.flag = true;
} else { } else {
this.flag = false; this.flag = false;

View File

@ -83,6 +83,7 @@ page {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: 3rpx -3rpx 15rpx 0rpx rgba(255,27,27,0.05); box-shadow: 3rpx -3rpx 15rpx 0rpx rgba(255,27,27,0.05);
padding-bottom: 30rpx;
} }
.left { .left {

View File

@ -144,10 +144,11 @@ export default {
}, },
onLoad(options) { onLoad(options) {
const params = { const params = {
id: uni.getStorageSync("merchantInfo").id id: Number(uni.getStorageSync("merchantInfo").id)
} }
request(apiArr.getMerchantInfo, "POST", params).then(res => { request(apiArr.getMerchantInfo, "POST", params).then(res => {
this.info = res this.info = res
uni.setStorageSync('merchantInfo', res)
// onLoad // onLoad
this.page_num = 1 this.page_num = 1

View File

@ -121,8 +121,8 @@ export default {
}, },
watch: { watch: {
Money(newVal) { Money(newVal) {
this.homeMoney = Math.round(newVal * (this.info.refund_property_fee_ratio || 0)); this.homeMoney = newVal * (this.info.refund_property_fee_ratio || 0);
this.integral = Math.round(newVal * (this.info.refund_user_points_ratio || 0)); this.integral = newVal * (this.info.refund_user_points_ratio || 0);
}, },
}, },
@ -304,6 +304,7 @@ export default {
success: (payRes) => { success: (payRes) => {
const params = { const params = {
id: this.OrderMsg.id, id: this.OrderMsg.id,
back_wygjj: this.homeMoney,
} }
request(apiArr.tradeQuery, "POST", params).then(res => { request(apiArr.tradeQuery, "POST", params).then(res => {
}) })

View File

@ -375,9 +375,10 @@ page {
.shadowBox_img { .shadowBox_img {
width: 600rpx; width: 600rpx;
height: 500rpx; height: 800rpx;
background-color: #fff; background-color: #fff;
border-radius: 20rpx; border-radius: 20rpx;
position: relative;
} }
.boxshadow_tit { .boxshadow_tit {
@ -403,9 +404,88 @@ page {
text-align: center; text-align: center;
} }
.wealBoxTit {
display: flex;
align-items: flex-end;
margin-top: 30rpx;
margin-left: 20rpx;
}
.wealBoxTit1 {
color: #fe1535;
font-size: 32rpx;
font-weight: bold;
}
.wealBoxTit2 {
color: #fe1535;
font-size: 26rpx;
margin-left: 15rpx;
}
.wealBox{
width: 93%;
height: 170rpx;
margin: 15rpx auto;
display: flex;
}
.wealBoxItem{
width: 150rpx;
height: 150rpx;
margin: 0 10rpx;
border-radius: 15rpx;
padding: 20rpx 10rpx;
text-align: center;
display: flex; /* 添加 flex 布局 */
flex-direction: column; /* 设置主轴为垂直方向 */
justify-content: space-between; /* 垂直方向上平均分布 */
align-items: center; /* 水平方向居中 */
}
.wealBoxItem1{
background-color: #fff4f1;
}
.wealBoxItem2{
background-color: #fff7f1;
}
.wealBoxItem3{
background-color: #fffaf0;
}
.wealBoxItemTop{
display: flex;
align-items: center;
justify-content: center;
}
.wealBoxItemTop image{
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.wealBoxItemBottom{
font-size: 26rpx;
color: #999999;
}
.bottom {
width: 100%;
position: absolute;
bottom: 30rpx;
}
.boxbottom1 {
margin: 0 auto;
}
.boxbottom { .boxbottom {
width: 100%; width: 100%;
margin-top: 50rpx; margin-bottom: 50rpx;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -428,7 +508,7 @@ page {
.shadowBox1 { .shadowBox1 {
width: 100%; width: 100%;
display: flex; display: flex;
margin-top: 30rpx; margin-bottom: 30rpx;
} }
.shadowBox1Item_btn { .shadowBox1Item_btn {

View File

@ -233,11 +233,59 @@
已支付成功 已支付成功
</view> </view>
<view class="boxshadow_img"> <view class="boxshadow_img">
<view>核销码{{ verifyCode }}</view> <view v-if="verifyCode">核销码{{ verifyCode }}</view>
<!-- <image <!-- <image
src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/shop_share_img.png"> src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/shop_share_img.png">
</image> --> </image> -->
</view> </view>
<view>
<view class="wealBoxTit">
<view class="wealBoxTit1">下单福利</view>
<view class="wealBoxTit2">已获得</view>
</view>
<view class="wealBox">
<view class="wealBoxItem wealBoxItem1">
<view class="wealBoxItemTop">
<image src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/activity_fen.png" mode="aspectFit">
</image>
<view>石榴分</view>
</view>
<view>
{{ slFen }}
</view>
<view class="wealBoxItemBottom">
可抵扣 {{ slFen }}
</view>
</view>
<view class="wealBoxItem wealBoxItem2">
<view class="wealBoxItemTop">
<image src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/activity_zi.png" mode="aspectFit">
</image>
<view>石榴籽</view>
</view>
<view>
{{ slZi }}
</view>
<view class="wealBoxItemBottom">
可抵扣 {{ slZi }}
</view>
</view>
<view class="wealBoxItem wealBoxItem3">
<view class="wealBoxItemTop">
<image src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/activity_jin.png" mode="aspectFit"/>
<view>石榴金</view>
</view>
<view>
{{ slJin }}
</view>
<view class="wealBoxItemBottom">
可抵扣 {{ slJin }}
</view>
</view>
</view>
</view>
<view class="bottom">
<view class="boxbottom1">
<view class="boxbottom"> <view class="boxbottom">
<view class="line1"></view> <view class="line1"></view>
赶快邀请好友来下单吧 赶快邀请好友来下单吧
@ -270,6 +318,8 @@
</view> </view>
</view> </view>
</view> </view>
</view>
</view>
<!-- 海报 --> <!-- 海报 -->
<view class="shadow" @click="changeShadow2" v-if="boxshadow2"> <view class="shadow" @click="changeShadow2" v-if="boxshadow2">
@ -315,6 +365,10 @@ export default {
orderList2: [], orderList2: [],
carList: [], carList: [],
slJin: 0,
slFen: 0,
slZi: 0,
// id // id
group_buy_activity_id: 0, group_buy_activity_id: 0,
@ -338,7 +392,6 @@ export default {
}, },
onLoad(options) { onLoad(options) {
this.carList = JSON.parse(options.shopCarList) this.carList = JSON.parse(options.shopCarList)
console.log("🚀 ~ onLoad ~ JSON.parse(options.shopCarList):", JSON.parse(options.shopCarList))
}, },
onShow() { onShow() {
this.getUserAddress() this.getUserAddress()
@ -425,7 +478,6 @@ export default {
} }
}, },
increaseQuantity(item) { increaseQuantity(item) {
console.log("🚀 ~ increaseQuantity ~ item:", item)
const currentTime = new Date().getTime(); const currentTime = new Date().getTime();
const startTime = new Date(item.commodity_goods_info.group_buy_activity_info?.start_time).getTime(); const startTime = new Date(item.commodity_goods_info.group_buy_activity_info?.start_time).getTime();
const endTime = new Date(item.commodity_goods_info.group_buy_activity_info?.end_time).getTime(); const endTime = new Date(item.commodity_goods_info.group_buy_activity_info?.end_time).getTime();
@ -600,7 +652,8 @@ export default {
// //
const params = { const params = {
user_id: uni.getStorageSync('userId'), user_id: uni.getStorageSync('userId'),
is_group_buy: isGroupBuyValid, // shopCarListisAdvertrue
order_cate : this.carList.some(item => item.isAdver === true) ? 3 : (isGroupBuyValid ? 2 : 1),
goods_list: Object.keys(this.supplierGroups).map(supplierId => { goods_list: Object.keys(this.supplierGroups).map(supplierId => {
const group = this.supplierGroups[supplierId]; const group = this.supplierGroups[supplierId];
const firstItem = group[0]; const firstItem = group[0];
@ -662,11 +715,13 @@ export default {
success: (payRes) => { success: (payRes) => {
const params = { const params = {
order_id: orderId, order_id: orderId,
from: 2, from: this.carList.some(item => item.isAdver === true) ? 3 : (isGroupBuyValid ? 2 : 1),
group_buy_activity_id: this.group_buy_activity_id, group_buy_activity_id: this.group_buy_activity_id,
adver_id: this.carList.some(item => item.isAdver === true) ? this.carList.find(item => item.isAdver === true).adver_id : ''
} }
request(apiArr.queryOrder, "POST", params).then(res => { request(apiArr.queryOrder, "POST", params).then(res => {
this.verifyCode = res.verification_code this.verifyCode = res.verification_code
this.slJin = res.shiliu_money
this.boxshadow1 = true this.boxshadow1 = true
}) })
}, },

View File

@ -228,7 +228,8 @@ export default {
isDayshow: false, isDayshow: false,
isParcelPostchecked: false, isParcelPostchecked: false,
parcelPostshow: false, parcelPostshow: false,
is_group_buy: '' is_group_buy: '',
is_adver: ''
}; };
}, },
// watch: { // watch: {
@ -297,14 +298,20 @@ export default {
this.$u.toast("请选择商品"); this.$u.toast("请选择商品");
return; return;
} }
// isAdver=true
const updatedArr = arr.map(item => ({
...item,
isAdver: item.adver_id ? true : ''
}));
// NavgateTo(`../submitOrder/index?shopCarList=${JSON.stringify(arr)}`); // NavgateTo(`../submitOrder/index?shopCarList=${JSON.stringify(arr)}`);
NavgateTo(`../groupPurchaseSubmit/index?shopCarList=${JSON.stringify(arr)}`); NavgateTo(`../groupPurchaseSubmit/index?shopCarList=${JSON.stringify(updatedArr)}`);
}, },
// //
getShopCar() { getShopCar() {
const params = { const params = {
is_group_buy: this.is_group_buy is_group_buy: this.is_group_buy,
is_adver: this.is_adver
} }
request(apiArr.getCar, "POST", params).then((res) => { request(apiArr.getCar, "POST", params).then((res) => {
if (res.normal_cart_list.length > 0) { if (res.normal_cart_list.length > 0) {
@ -728,6 +735,7 @@ export default {
this.top = meun.top; this.top = meun.top;
this.localHeight = meun.height; this.localHeight = meun.height;
this.is_group_buy = options.item ? JSON.parse(options.item).is_group_buy : '' this.is_group_buy = options.item ? JSON.parse(options.item).is_group_buy : ''
this.is_adver = options.item ? JSON.parse(options.item).is_adver : ''
this.getShopCar(); this.getShopCar();
}, },
onShow() { onShow() {

View File

@ -109,6 +109,7 @@
icon: 'success', icon: 'success',
mask: true mask: true
}) })
uni.setStorageSync('headPhoto', this.avatarInfo.picUrl)
setTimeout(() => { setTimeout(() => {
uni.navigateBack({ delta: 1 }) uni.navigateBack({ delta: 1 })
}, 2000) }, 2000)

View File

@ -93,11 +93,12 @@
<!-- 广告横幅 --> <!-- 广告横幅 -->
<view class="serverList1"> <view class="serverList1">
<view class="serverList1_left" @click="toAdvertisingView"> <view class="serverList1_left" @click="toAdvertisingView(serverLeftList)">
<image :src="serverLeft" mode="aspectFill" /> <image :src="serverLeft" mode="aspectFill" />
</view> </view>
<view class="serverList1_right"> <view class="serverList1_right">
<view :class="['serverItemRight', `serverItemRight${index + 1}`]" @tap="headerServerClick(item)" <view :class="['serverItemRight', `serverItemRight${index + 1}`]"
@tap="index === 1 ? toAdvertisingView(serverRightList) : headerServerClick(item)"
v-for="(item, index) in serverRightList" :key="index"> v-for="(item, index) in serverRightList" :key="index">
<image :src="item.pic_src" mode="" /> <image :src="item.pic_src" mode="" />
</view> </view>
@ -308,6 +309,7 @@ export default {
// 1广 // 1广
serverLeft: '', serverLeft: '',
serverLeftList: [],
// 1广 // 1广
serverRightList: [], serverRightList: [],
@ -351,8 +353,9 @@ export default {
}, },
// 广 // 广
toAdvertisingView(item) { toAdvertisingView(itemArry) {
NavgateTo('/packages/advertising/index/index') const item = itemArry[0]
NavgateTo('/packages/advertising/index/index?id=' + item.id)
}, },
async goToWuye() { async goToWuye() {
@ -679,6 +682,7 @@ export default {
}, { silent: false }); }, { silent: false });
if (res.rows.length) { if (res.rows.length) {
this.serverLeftList = res.rows
let filterRes = this.filterShowList(res?.rows, 1); let filterRes = this.filterShowList(res?.rows, 1);
filterRes.forEach(item => { filterRes.forEach(item => {
item.pic_src = picUrl + item.pic_src item.pic_src = picUrl + item.pic_src

View File

@ -90,6 +90,7 @@ export default {
uni.setStorageSync('userId', res2.user_id); uni.setStorageSync('userId', res2.user_id);
uni.setStorageSync('openId', res2.open_id); uni.setStorageSync('openId', res2.open_id);
uni.setStorageSync('shopId', res2.wshop_id); uni.setStorageSync('shopId', res2.wshop_id);
uni.setStorageSync('headPhoto', res2.img);
this.isLogin = true; this.isLogin = true;
that.getUserInfo(); that.getUserInfo();

View File

@ -33,7 +33,7 @@
<view>繁华币</view> <view>繁华币</view>
</view> </view>
<view class="section_label"> <view class="section_label">
<view>0.00</view> <view>{{ userInfo.property_housing_fund }}</view>
<view>物业费公积金</view> <view>物业费公积金</view>
</view> </view>
<view class="section_label"> <view class="section_label">

View File

@ -1,11 +1,19 @@
import mqtt from 'mqtt/dist/mqtt' import mqtt from 'mqtt/dist/mqtt'
import Vue from 'vue' import Vue from 'vue'
// 引入Buffer适配小程序环境
import { Buffer } from 'buffer';
// 挂载到全局,确保所有文件可访问
if (typeof window !== 'undefined') {
window.Buffer = Buffer;
} else if (typeof global !== 'undefined') {
global.Buffer = Buffer;
}
let mqttTool = { let mqttTool = {
client: null client: null
} }
mqttTool.connect = function(params){ mqttTool.connect = function(params, callbacks = {}){
let options = { let options = {
clientId: params.clientId, clientId: params.clientId,
username: 'dev01', username: 'dev01',
@ -20,11 +28,36 @@ mqttTool.connect = function(params){
client = mqtt.connect('wx://api.hshuishang.com:8084/mqtt', options) client = mqtt.connect('wx://api.hshuishang.com:8084/mqtt', options)
console.log('WX', client) console.log('WX', client)
mqttTool.client = client mqttTool.client = client
if (mqttTool.client) {
console.log('连接成功') // 设置连接状态回调
} else { if (callbacks.onConnect) {
console.log('连接失败') client.on('connect', callbacks.onConnect)
} }
if (callbacks.onDisconnect) {
client.on('disconnect', callbacks.onDisconnect)
}
if (callbacks.onError) {
client.on('error', callbacks.onError)
}
if (callbacks.onReconnect) {
client.on('reconnect', callbacks.onReconnect)
}
client.on('connect', function() {
console.log('MQTT连接成功')
})
client.on('error', function(error) {
console.log('MQTT连接错误:', error)
})
client.on('reconnect', function() {
console.log('MQTT正在重连...')
})
return client return client
} }

View File

@ -164,6 +164,14 @@ buffer@^5.5.0:
base64-js "^1.3.1" base64-js "^1.3.1"
ieee754 "^1.1.13" ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
callback-stream@^1.0.2: callback-stream@^1.0.2:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz" resolved "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz"
@ -384,7 +392,7 @@ help-me@^1.0.1:
through2 "^2.0.1" through2 "^2.0.1"
xtend "^4.0.0" xtend "^4.0.0"
ieee754@^1.1.13: ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==