Files
cashier_app/pageChat/chat.vue
2025-12-04 17:17:59 +08:00

664 lines
16 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">
<view>
<text style="max-width: 286rpx" class="u-line-1 u-font-32">
{{ groupInfo.name }}
</text>
<text class="u-m-l-22">{{membersRes.user_list.length}}</text>
</view>
<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="shop u-flex u-row-right">
<view class="u-p-r-18">
<view class="u-flex u-row-right">
<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"></chatItem>
</view>
<view class="text-center u-m-t-20" v-if="item.msg_type==4&&item.hasGet">
<text>{{item.hasGet||0}}人已领取</text>
<text class="color-main">优惠券</text>
</view>
</view>
<up-avatar
size="122rpx"
shape="square"
bg-color="#fff"
></up-avatar>
</view>
</template>
<!-- 收消息 -->
<template v-else>
<view class="user u-flex">
<up-avatar
size="122rpx"
:src="item.avatar"
shape="square"
bg-color="#fff"
></up-avatar>
<view class="u-p-l-18">
<view class="color-000 u-line-1">{{ 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>
</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">
<!-- <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">
发送
</button>
<up-icon
name="plus-circle"
color="#666"
size="40rpx"
@click="showMoreBtnToggle"
v-else
></up-icon>
</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>
<!-- 发送优惠券 -->
<Modal
title="发送优惠券"
v-model="modalData.show"
confirmText="确认发送"
@close="closeModal"
@confirm="confirmCoupon"
>
<view class="u-p-30">
<view class="font-bold u-m-b-16">自定义文案</view>
<up-input
v-model="modalData.form.title"
placeholder="请输入自定义文案"
:placeholderStyle="placeholderStyle"
></up-input>
<view class="font-bold u-m-b-16 u-m-t-32">发放数量()</view>
<up-number-box
v-model="modalData.form.giveNum"
inputWidth="240rpx"
@change="valChange"
></up-number-box>
<view class="font-bold u-m-b-16 u-m-t-32">每人限领量()</view>
<up-number-box
v-model="modalData.form.getLimit"
inputWidth="240rpx"
@change="valChange"
></up-number-box>
<view class="font-bold u-m-b-16 u-m-t-32">选择优惠券</view>
<chooseCoupon v-model="modalData.form.couponId"></chooseCoupon>
</view>
</Modal>
</view>
</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 "@/store/chat";
import * as chatApi from "@/http/php/chat";
import * as chatCouponApi from "@/http/api/market/chat";
import { uploadFile } from "@/http/api/index.js";
import go from "@/commons/utils/go.js";
import Modal from "./components/modal.vue";
import chooseCoupon from "./components/coupon.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 websocketUtil = inject("websocketUtil");
websocketUtil.closeSocket();
websocketUtil.offMessage();
const chatStore = useChatStore();
chatStore.onReceiveMsg = (msg) => {
nextTick(() => {
scrollView.intoView = "msg-0";
});
};
chatStore.connectSocket();
const msg = ref("");
const shopInfo = uni.getStorageSync("shopInfo");
const moreBtns = ref([
{
icon: "/pageChat/static/pic.png",
title: "发送照片",
value: "pic",
},
{
icon: "/pageChat/static/video.png",
title: "发送视频",
value: "video",
},
{
icon: "/pageChat/static/coupon.png",
title: "发送优惠券",
value: "coupon",
},
]);
function moreBtnsClick(item, index) {
if (item.value == "coupon") {
modalData.show = 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 {
}
}
uni.hideLoading();
},
});
}
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=reactive({
user_list:[]
})
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.user_list=res.user_list||[]
})
// #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;
}
chatCouponApi.chatCouponCreate(modalData.form).then((res) => {
if (res) {
modalData.show = false;
const couponJson = JSON.parse(res.couponJson);
sendMsg({
coupon: { ...couponJson, title: modalData.form.title,activity_id:res.id },
chat_coupon_id:res.id,
msg_type: 4,
});
} else {
uni.showToast({
title: "发送失败",
icon: "none",
});
}
});
}
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;
}
.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;
}
</style>