Files
cashier_wx/pageChat/chat.vue
2025-12-04 17:14:47 +08:00

727 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="min-page bg-f7 u-font-28 color-333" :style="pageHeight">
<view class="top u-flex u-row-between">
<text style="max-width: 600rpx" class="u-line-1 u-font-32">
{{ groupInfo.name }}
</text>
<text class="u-m-l-22"
>{{ membersRes?.user_list?.length || 0 }}</text
>
<!-- <view class="" @click="toMore()">
<view class="u-flex u-row-center">
<up-icon name="more-dot-fill" color="#333" size="14"></up-icon>
</view>
<view class="color-666">更多</view>
</view> -->
</view>
<view class="box-1">
<!-- @refresherrefresh="refresherrefresh" -->
<scroll-view
scroll-y
refresher-background="transparent"
style="height: 100%"
@scrolltoupper="refresherrefresh"
:refresher-enabled="false"
:scroll-with-animation="false"
:refresher-triggered="scrollView.refresherTriggered"
:scroll-into-view="scrollView.intoView"
>
<view class="scroll-view-box">
<view class="talk-list">
<view
:id="'msg-' + index"
v-for="(item, index) in chatStore.chatList"
:key="index"
>
<!-- 发消息 -->
<template
v-if="item.operate_type == 'sendMsg' || item.is_user_send == 1"
>
<view class="user u-flex u-row-right">
<view class="u-p-r-18">
<view class="color-000 u-line-1 text-right">{{ item.nick_name }}</view>
<view
class="u-m-t-14 msg u-p-l-30"
:class="['type' + item.msg_type]"
>
<chatItem :item="item"></chatItem>
</view>
</view>
<up-avatar
size="122rpx"
:src="item.avatar"
shape="square"
bg-color="#fff"
></up-avatar>
</view>
</template>
<!-- 商家消息 -->
<template v-else>
<view class="shop u-flex">
<up-avatar
:src="shopInfo.logo"
size="122rpx"
shape="square"
bg-color="#fff"
></up-avatar>
<view class="u-p-l-18">
<view class="u-flex">
<view class="tag">商家</view>
<view class="color-000">{{ shopInfo.shopName }}</view>
</view>
<view
class="u-m-t-14 msg"
:class="['type' + item.msg_type]"
>
<chatItem :item="item" @getCoupon="getCoupon"></chatItem>
</view>
</view>
</view>
</template>
</view>
</view>
<up-loadmore
v-if="isEnd"
:status="isEnd ? 'nomore' : 'loading'"
></up-loadmore>
</view>
</scroll-view>
</view>
<!-- <view :style="bottomSlotHeight"></view> -->
<view class="bottom" :class="[showMoreBtn ? '' : 'safe-bottom']">
<view class="u-flex" style="padding: 14rpx 28rpx" v-if="groupInfo&&!groupInfo.is_mute">
<!-- <input
type="text"
class="u-flex-1 u-m-r-52 iput"
placeholder="请输入内容"
v-model="msg"
/> -->
<view class="u-flex-1 u-m-r-52 iput">
<up-input
v-model="msg"
border="none"
placeholder="请输入内容"
></up-input>
</view>
<button class="send-btn" v-if="msg.trim().length > 0" @click="sendText">
<text>发送</text>
</button>
<up-icon
name="plus-circle"
color="#666"
size="40rpx"
@click="showMoreBtnToggle"
v-else
></up-icon>
</view>
<view class="color-666 u-font-28 text-center u-m-t-28" v-else>商家已禁言</view>
<view class="more-btn" v-if="showMoreBtn">
<view
v-for="(item, index) in moreBtns"
@click="moreBtnsClick(item, index)"
class="u-flex-col u-row-center u-col-center"
>
<view class="u-flex icon">
<image :src="item.icon" class="img" mode="aspectFill"></image>
</view>
<view class="u-m-t-8">{{ item.title }}</view>
</view>
</view>
</view>
</view>
<!-- 退出群聊 -->
<u-popup
:show="popupShow"
:safe-area-inset-bottom="false"
mode="center"
@close="popupShow = false"
>
<view class="popup-content">
<view class="header-wrap">
<text class="t">退出群{{ "{" + groupInfo.name + "}" }}</text>
<view class="close" @click="popupShow = false">
<u-icon name="close" size="16" color="#666"></u-icon>
</view>
</view>
<view class="u-p-40 text-center u-font-28 color-333 border-bottom"
>是否确认退出退出后将会错失更多活动和优惠</view
>
<view class="btn-content">
<view class="btn">
<u-button
color="#E8AD7B"
plain=""
shape="circle"
@click="popupShow = false"
>取消</u-button
>
</view>
<view class="btn">
<u-button color="#E8AD7B" shape="circle" @click="exitGroup"
>退出</u-button
>
</view>
</view>
</view>
</u-popup>
</template>
<script setup>
import {
onReady,
onReachBottom,
onLoad,
onPageScroll,
} from "@dcloudio/uni-app";
import {
ref,
inject,
onMounted,
nextTick,
reactive,
watch,
computed,
} from "vue";
import { useChatStore } from "@/stores/chat";
import * as chatApi from "@/http/php/chat";
import { uploadFile } from "@/common/api/upload.js";
import * as javaChatApi from "@/common/api/market/chat";
function exitGroup() {
chatApi
.groupQuit({
group_id: groupInfo.value.id,
})
.then((res) => {
if (res) {
popupShow.value = false;
uni.showToast({
title: "退出成功",
icon: "none",
duration: 1500,
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
});
}
function getCoupon(item) {
javaChatApi
.couponGrant({
id: item.coupon.activity_id,
shopUserId: shopInfo.id,
userId: uni.cache.get("userInfo").id,
})
.then((res) => {
if (res) {
uni.showToast({
title: "领取成功",
icon: "none",
duration: 2000,
});
refresh()
}
});
}
function refresh() {
query.page=1;
getMsgList();
}
const go = {
to(item) {},
};
const popupShow = ref(false);
import Modal from "./components/modal.vue";
import chatItem from "./components/chat-item.vue";
const placeholderStyle = {
color: "#999",
fontSize: "28rpx",
};
function refresherrefresh() {
console.log("refresherrefresh");
// 关键:同时判断「正在加载」和「无更多数据」,只要一个为真就关闭刷新
if (isLoading.value || isEnd.value) {
scrollView.refresherTriggered = false; // 立即关闭刷新状态
if (isEnd.value) {
uni.showToast({ title: "没有更多了", icon: "none" });
}
return; // 终止后续逻辑
}
query.page++;
getMsgList();
}
const scrollView = reactive({
refresherTriggered: false,
intoView: "",
safeAreaHeight: 0,
});
const showMoreBtn = ref(false);
function showMoreBtnToggle() {
showMoreBtn.value = !showMoreBtn.value;
if (showMoreBtn.value) {
}
}
const modalData = reactive({
show: false,
form: {
title: "",
getLimit: 1,
giveNum: 1,
couponId: "",
},
});
const chatStore = useChatStore();
chatStore.onReceiveMsg = (msg) => {
nextTick(() => {
scrollView.intoView = "msg-0";
});
};
chatStore.connectSocket();
const msg = ref("");
const shopInfo = uni.cache.get("shopInfo");
const shopUserInfo = uni.cache.get("shopUserInfo");
const moreBtns = ref([
{
icon: "/pageChat/static/pic.png",
title: "发送照片",
value: "pic",
},
{
icon: "/pageChat/static/video.png",
title: "发送视频",
value: "video",
},
{
icon: "/pageChat/static/exit.png",
title: "退出群聊",
value: "exit",
},
]);
function moreBtnsClick(item, index) {
if (item.value == "exit") {
popupShow.value = true;
return;
}
if (item.value == "pic") {
sendImg();
}
if (item.value == "video") {
sendVideo();
}
}
function videoErrorCallback(e) {
console.error("视频播放失败", e);
}
function sendImg() {
uni.chooseImage({
count: 3, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album", "camera "],
success: async function (res) {
uni.showLoading({
title: "发送中",
});
console.log(res);
for (let i = 0; i < res.tempFiles.length; i++) {
const fileRes = await uploadFile(res.tempFiles[i]);
if (fileRes) {
sendMsg({
image_url: fileRes,
msg_type: 2,
});
} else {
}
}
},
});
}
function sendVideo() {
uni.chooseVideo({
count: 1, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album", "camera "],
success: async function (res) {
uni.showLoading({
title: "发送中",
});
console.log(res);
const fileRes = await uploadFile({ path: res.tempFilePath });
uni.hideLoading();
if (fileRes) {
sendMsg({
image_url: fileRes,
msg_type: 5,
});
} else {
uni.showToast({
title: "发送失败",
icon: "none",
});
}
},
});
}
const groupInfo = ref({});
async function init() {
const res = await chatApi.groupInfo({
group_id: options.group_id,
});
console.log(res);
groupInfo.value = res || {};
getMsgList();
}
const query = reactive({
page: 1,
size: 10,
});
async function getMsgList() {
// 提前拦截:已无更多数据,直接关闭状态
if (isEnd.value) {
scrollView.refresherTriggered = false;
isLoading.value = false;
uni.showToast({ title: "没有更多了", icon: "none" });
return;
}
isLoading.value = true;
try {
const msgRes = await chatApi.messageHistory({
...query,
chat_type: "2",
session_id: options.session_id,
to_id: options.group_id,
shop_id: options.group_id,
group_id: options.group_id,
});
const data = msgRes.list || [];
const selector = `msg-${query.page == 1 ? 0 : chatStore.chatList.length}`;
if (query.page == 1) {
chatStore.chatList = data;
} else {
chatStore.chatList.push(...data);
}
isEnd.value = chatStore.chatList.length >= msgRes.total;
nextTick(() => {
scrollView.intoView = selector;
console.log(selector);
});
} catch (err) {
console.error("加载历史消息异常", err);
// 加载失败回退页码,避免页码错乱
if (query.page > 1) query.page--;
} finally {
console.log("scrollView", scrollView);
setTimeout(() => {
scrollView.refresherTriggered = false;
isLoading.value = false;
}, 100);
}
}
const options = reactive({});
const membersRes = ref(null);
onLoad((opt) => {
Object.assign(options, opt);
init();
chatApi.messageMarkReadAll({
session_ids: options.session_id,
});
chatApi.groupMembers({ group_id: options.group_id }).then((res) => {
console.log(res);
membersRes.value = res;
});
// #ifdef H5
scrollView.safeAreaHeight = uni.getSystemInfoSync().safeArea.height;
// #endif
});
function toMore() {
go.to("PAGES_CHAT_GROUP_INFO", {
group_id: groupInfo.value.id,
session_id: options.session_id,
});
}
function sendText() {
if (!msg.value.trim().length) return;
sendMsg({ content: msg.value, msg_type: 1 });
msg.value = "";
}
function sendMsg(msg) {
chatStore.sendMessage({
to_id: groupInfo.value.id,
to_user_type: groupInfo.value.id,
chat_type: 2,
content: msg.value,
image_url: "",
order_id: "",
session_id: "",
...msg,
});
}
function closeModal() {
modalData.form = {
title: "",
getLimit: 1,
giveNum: 1,
couponId: "",
};
}
function confirmCoupon() {
console.log(modalData.form);
if (!modalData.form.title) {
uni.showToast({
title: "请输入自定义文案",
icon: "none",
});
return;
}
if (!modalData.form.couponId) {
uni.showToast({
title: "请选择优惠券",
icon: "none",
});
return;
}
if (!modalData.form.giveNum) {
uni.showToast({
title: "请输入发放数量",
icon: "none",
});
return;
}
if (!modalData.form.getLimit) {
uni.showToast({
title: "请输入每人限领量",
icon: "none",
});
return;
}
}
function previewImage(url) {
uni.previewImage({
urls: [url],
});
}
//是否到底了
const isBottom = ref(false);
const isLoading = ref(false);
//消息是否完了
const isEnd = ref(false);
watch(
() => chatStore.chatList.length,
(newVal, oldVal) => {}
);
const bottomSlotHeight = computed(() => {
if (showMoreBtn.value) {
return {
height: "180px",
};
} else {
return {
height: "88px",
};
}
});
onReady(() => {});
const pageHeight = computed(() => {
const safeAreaHeight = scrollView.safeAreaHeight;
if (safeAreaHeight > 0) {
return `height: calc(${safeAreaHeight}px - var(--window-top));`;
}
return "";
});
</script>
<style lang="scss" scoped>
.top {
background-color: #fff;
padding: 40rpx 32rpx;
}
.min-page {
height: calc(100vh - var(--window-top));
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: stretch;
}
.talk-list {
padding: 30rpx 28rpx 30rpx 26rpx;
display: flex;
flex-direction: column-reverse;
flex-wrap: nowrap;
align-content: flex-start;
justify-content: flex-end;
align-items: stretch;
// 添加弹性容器,让内容自动在顶部
&::before {
content: ".";
display: inline;
visibility: hidden;
line-height: 0;
font-size: 0;
flex: 1 0 auto;
height: 1px;
}
}
.box-1 {
width: 100%;
height: 0;
flex: 1 0 auto;
box-sizing: content-box;
}
.user {
margin-bottom: 88rpx;
.msg {
padding: 16rpx 20rpx;
background-color: #fff;
border-radius: 8rpx;
}
}
.shop {
margin-bottom: 88rpx;
.tag {
margin-right: 32rpx;
padding: 8rpx 20rpx;
border: 1px solid #d9d9d9;
border-radius: 8rpx;
font-size: 24rpx;
color: #666;
}
.msg {
padding: 16rpx 20rpx;
border-radius: 8rpx;
&.type1 {
background-color: #fff;
}
&.type4 {
background-color: #fff;
}
.img {
width: 50vw;
}
}
}
.bottom {
height: auto;
z-index: 2;
border-top: #e5e5e5 solid 1px;
box-sizing: content-box;
background-color: #f3f3f3;
/* 兼容iPhoneX */
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
&.safe-bottom {
padding-bottom: calc(env(safe-area-inset-bottom) + 4rpx);
}
.iput {
background-color: #f8f8f8;
padding: 16rpx 20rpx;
}
}
.send-btn {
background-color: $my-main-color;
color: #fff;
line-height: 1;
padding: 16rpx 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
display: flex;
align-items: center;
}
.more-btn {
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx;
background-color: #f0f0f0;
gap: 74rpx;
text-align: center;
color: #666;
padding-bottom: calc(env(safe-area-inset-bottom) + 4rpx);
.icon {
width: 100rpx;
height: 100rpx;
background-color: #fff;
border-radius: 16rpx;
display: flex;
justify-content: center;
align-items: center;
.img {
height: 70rpx;
width: 70rpx;
}
}
}
.scroll-view-box {
display: flex;
flex-direction: column-reverse;
}
.popup-content {
width: 90vw;
background-color: #fff;
border-radius: 8px;
.header-wrap {
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #ececec;
padding: 0 28upx;
.t {
font-size: 32upx;
color: #333;
}
.close {
$size: 60upx;
width: $size;
height: $size;
display: flex;
align-items: center;
justify-content: center;
}
}
.btn-content {
height: 86px;
display: flex;
align-items: center;
justify-content: center;
gap: 60upx;
.btn {
width: 248upx;
}
}
}
</style>