| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278 |
- <template>
- <div class="global-im">
- <!-- 悬浮按钮与角标 -->
- <div class="im-trigger" :class="{ active: visible }" @click="toggleChat">
- <el-icon :size="18"><el-icon-chat-round /></el-icon>
- <div class="unreadCount">
- <el-badge :value="unreadCount" :hidden="unreadCount === 0" :max="99">
- <i class="el-icon-chat-dot-round" style="font-size: 24px"></i>
- </el-badge>
- </div>
- </div>
- </div>
- <el-drawer v-model="visible" title="在线客服" size="100%" destroy-on-close :close-on-click-modal="false" :with-header="false">
- <el-container class="flex-column">
- <div class="drawer-detail-main">
- <div class="drawer-detail-header">
- <div class="drawer-detail-header-body">
- <div class="drawer-detail-header-left">在线客服</div>
- <div class="drawer-detail-header-left">
- <el-button type="default" icon="el-icon-close" round @click="hideWindow"></el-button>
- </div>
- </div>
- </div>
- </div>
- <el-main class="nopadding">
- <el-container>
- <el-aside width="300px" v-loading="loadMsg">
- <el-container class="flex-column">
- <!-- <div class="aside-tabs bg">
- <div :class="orderData.tab=='user'?'aside-tab-items active':'aside-tab-items'" @click="handleClick('user')">当前咨询</div>
- <div :class="orderData.tab=='order'?'aside-tab-items active':'aside-tab-items'" @click="handleClick('order')">历史咨询</div>
- </div> -->
- <el-main class="nopadding">
- <div class="chat-contact">
- <div :class="checkChat==index?'contact-item active':'contact-item'" v-for="(item,index) in chatContact" :key="index" @click="checkChatItem(index)">
- <div class="img">
- <el-image :src="item.user.avatar" fit="contain" style="width: 40px; height: 40px"></el-image>
- <span class="last-num" v-if="item.last.num > 0">{{item.last.num}}</span>
- </div>
- <div class="kefu-store">
- <div class="name">{{item.user.nickname}}</div>
- <div class="desc" v-if="item.last.type=='text'">{{item.last.content}}</div>
- <div class="desc" v-if="item.last.type=='order'">[订单消息]</div>
- <div class="desc" v-if="item.last.type=='image'">[图片消息]</div>
- <div class="desc" v-if="item.last.type=='video'">[视频消息]</div>
- <div class="desc" v-if="item.last.type=='pay'">[支付消息]</div>
- <div class="desc" v-if="item.last.type=='address'">[收集地址]</div>
- </div>
- <div class="time">
- {{item.last.create_at}}
- </div>
- </div>
- </div>
- </el-main>
- </el-container>
- </el-aside>
- <el-container v-if="chatItem">
- <el-header><div class="msg-title">与{{ chatItem.user.nickname }}的对话</div></el-header>
- <el-main class="nopadding" ref="main">
- <div class="chat-container" v-if="chatItem">
- <div class="loading-text" v-if="msgLoad">努力加载中...</div>
- <div class="message-list" ref="messageListRef" @scroll="handleScroll">
- <template v-for="(item, index) in history.messages">
- <div class="z_index2" style="transform: rotate(-180deg)" :key="item.messageId" v-if="!item.isHide">
- <div class="icon_ text_26 color__ time">
- {{ renderMessageDate(item, index) }}
- </div>
- <div :key="item.messageIds" v-if="!item.recalled">
- <item :isMy="isSelf(item.senderId)" :myid="myid" :item="item" @onClick="onItem"
- @onLongpress="onLongpress" @mention="mention"></item>
- </div>
- <div class="icon_ text_26 recalled" v-else>
- <div class="">
- <span v-if="isSelf(item.senderId)">你</span>
- <span v-else>{{ item.senderData.name }}</span>
- 撤回了一条消息
- </div>
- <div class="recalled-edit" v-if="item.type === 'text' && isSelf(item.senderId)"
- @click="recalledEdit(item)">重新编辑</div>
- </div>
- </div>
- </template>
- </div>
- <div class="input-area">
- <div class="quick-group">
- <div class="emoji-item">
- <el-popover placement="top" :width="400" trigger="click">
- <template #reference>
- <div class="item-btn">
- <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/face.svg"></el-image>
- <div class="name">表情</div>
- </div>
- </template>
- <div class="swiper-item-box">
- <div class="flex_r swiper-item-box-l" v-for="(im, ix) in emojiList" :key="ix">
- <div
- class="icon_ emoji-item"
- v-for="(item, index) in im"
- :key="index"
- :style="{ opacity: ix === opacityGroup && (index == 5 || index == 6 || index == 7) ? opacity : 1 }"
- >
- <el-image class="img" :src="emojiUrl + item.image" @click="chooseEmoji(item.text)"></el-image>
- </div>
- </div>
- </div>
- </el-popover>
- </div>
- <div class="emoji-item">
- <el-popover placement="top" :width="174">
- <template #reference>
- <div class="item-btn">
- <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/more.svg"></el-image>
- <div class="name">图片</div>
- </div>
- </template>
- <sc-upload v-model="uploadImg" :on-success="uploadSuccess"></sc-upload>
- </el-popover>
- </div>
- <div class="emoji-item" @click="showQuick">
- <el-popover placement="top" :width="400" trigger="click" @show="getQuick">
- <template #reference>
- <div class="item-btn">
- <el-image style="width:25px;height:25px;" src="https://jymini.oss-cn-guangzhou.aliyuncs.com/pc/quick.svg"></el-image>
- <div class="name">快捷回复</div>
- </div>
- </template>
- <div class="quick-table">
- <div class="quick-search">
- <div class="search-input">
- <el-input placeholder="搜索一下..." style="width: 100%;" />
- <el-button>搜索</el-button>
- </div>
- <div class="search-btn-txt"><el-button type="primary" @click="addQuick">添加</el-button></div>
- </div>
- <div class="quick-table-body">
- <el-table :data="quickData.list" style="width: 100%" height="300" border>
- <el-table-column prop="content" label="内容"></el-table-column>
- <el-table-column prop="content" label="操作" width="80">
- <template #default="scope">
- <el-button size="small" @click="sendQuick(scope.row.content)">发送</el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <div class="quick-page">
- <el-pagination
- small
- background
- layout="prev, pager, next"
- :total="quickData.total"
- @size-change="quickPageChange" @current-change="quickPageChange"
- />
- </div>
- </div>
- </el-popover>
- </div>
- </div>
- <el-input
- type="textarea"
- :rows="3"
- v-model="inputMessage"
- placeholder="请输入消息..."
- @keyup.enter.native="handleKeyCode"
- @keydown.native="handleKeyCode"
- ></el-input>
- <div class="send-btn-group">
- <div class="send-btn-right">
- <el-button type="primary" @click="sendMessage">发送</el-button>
- </div>
- </div>
- </div>
- </div>
- </el-main>
- </el-container>
- <el-container v-else>
- <div class="empty-data">
- 请选择左侧的聊天列表
- </div>
- </el-container>
- <el-aside width="400px" v-if="chatItem">
- <el-container class="flex-column">
- <div class="aside-tabs">
- <div :class="orderData.tab=='user'?'aside-tab-items active':'aside-tab-items'" @click="handleClick('user')">用户信息</div>
- <div :class="orderData.tab=='order'?'aside-tab-items active':'aside-tab-items'" @click="handleClick('order')">团购订单</div>
- <!-- <div :class="orderData.tab=='price'?'aside-tab-items active':'aside-tab-items'" @click="handleClick('price')">差价订单</div> -->
- </div>
- <el-main class="nopadding">
- <template v-if="orderData.tab == 'user'">
- <div class="order-info-user">
- <div class="info-item">
- <div class="name">昵称</div>
- <div class="right">{{ chatItem.user.nickname }} </div>
- </div>
- <div class="info-item">
- <div class="name">OpenId</div>
- <div class="right">{{ chatItem.user.openid }} </div>
- </div>
- <div class="info-item">
- <div class="name">手机号码</div>
- <div class="right">{{ chatItem.user.mobile }} </div>
- </div>
- <div class="info-item">
- <div class="name">注册时间</div>
- <div class="right">{{ chatItem.user.create_at }} </div>
- </div>
- </div>
- <div class="order-info-user" v-if="msgOrder">
- <div class="title">咨询订单</div>
- <div class="m-order-s" style="margin-top: 15px;">
- <div class="m-order-item" @click="viewOrder(msgOrder)">
- <div class="order-img"><el-image style="width: 80px;height: 80px;" :src="msgOrder.img" fit="fit"></el-image></div>
- <div class="order-info">
- <div class="title" style="padding: 0;">{{msgOrder.product.product_name}}</div>
- <div class="desc" v-if="msgOrder.sku_name">规格:{{msgOrder.sku_name}}</div>
- <div class="desc">{{msgOrder.order_sn}}</div>
- <div class="price">
- {{$TOOL.money(msgOrder.price)}}
- <text v-if="msgOrder.custom_price > 0">差价:{{$TOOL.money(msgOrder.custom_price)}}</text>
- </div>
- </div>
- <div class="order-status-btn">
- <div class="status info" v-if="msgOrder.status=='0'">未支付</div>
- <div class="status primary" v-if="msgOrder.status=='1'">待使用</div>
- <div class="status success" v-if="msgOrder.status=='2'">已完成</div>
- <div class="status danger" v-if="msgOrder.status=='3'">已退款</div>
- <div class="status warn" v-if="msgOrder.status=='4'">退款中</div>
- <div class="status warn" v-if="msgOrder.status=='5'">已关闭</div>
- <div class="status success" v-if="msgOrder.product.product_type==11 && msgOrder.custom_price > 0 && msgOrder.custom_status ==1">差价已付</div>
- <div class="status danger" v-if="msgOrder.product.product_type==11 && msgOrder.custom_price > 0 && msgOrder.custom_status ==0">差价未付</div>
- </div>
- </div>
- <div class="order-btn-group">
- <div class="status primary cus" @click="sendAddress(msgOrder)" v-if="msgOrder.status=='1' && user.trade == '2'">发起预约</div>
- <div class="status info cus" @click="editAddress(msgOrder)" v-if="msgOrder.status=='1' && user.trade == '2'">代客预约</div>
- <!-- <div class="status warn cus" v-if="msgOrder.product.product_type==11 && msgOrder.custom_price > 0 && msgOrder.custom_status ==0" @click="submitPrice(msgOrder)">补差价</div> -->
- <!-- <div class="status danger cus" v-if="msgOrder.product.product_type==11 && msgOrder.custom_price > 0 && msgOrder.custom_status ==0" @click="submitPrice(msgOrder)">改差价</div> -->
- <div class="status danger cus" @click="sendOrder(msgOrder)">发送订单</div>
- <div class="status primary cus" @click="doneOrder(msgOrder)" v-if="msgOrder.status=='1' && user.trade == '1'">手动核销</div>
- </div>
- </div>
- </div>
- </template>
- <template v-if="orderData.tab == 'price'">
- <el-container>
- <el-header>
- <div class="order-search-input">
- <el-input v-model="priceData.order" placeholder="搜索订单编号..." style="width: 100%;" />
- <el-button @click="searchPrice">搜索</el-button>
- </div>
- </el-header>
- <el-main>
- </el-main>
- <el-footer style="text-align: right;">
- <div class="order-page">
- <el-pagination small background :default-page-size="priceData.size" layout="prev, pager, next" @size-change="orderPageChange" @current-change="orderPageChange" :total="priceData.total" />
- </div>
- </el-footer>
- </el-container>
- </template>
- <template v-if="orderData.tab == 'order'">
- <el-container v-loading="orderData.loading">
- <el-header>
- <div class="order-search-input">
- <el-input v-model="orderData.order" placeholder="搜索订单编号..." style="width: 100%;" />
- <el-button @click="searchOrder">搜索</el-button>
- </div>
- </el-header>
- <el-main>
- <div class="order-list-chat" v-if="orderData.list.length > 0">
- <div class="m-order-s" v-for="(item,index) in orderData.list" :key="index">
- <div class="m-order-item" @click="viewOrder(item)">
- <div class="order-img"><el-image style="width: 80px;height: 80px;" :src="item.img" fit="fit"></el-image></div>
- <div class="order-info">
- <div class="title">{{item.product.product_name}}</div>
- <div class="desc" v-if="item.sku_name">规格:{{item.sku_name}}</div>
- <div class="desc">{{item.order_sn}}</div>
- <div class="price">
- {{$TOOL.money(item.price)}}
- <text v-if="item.custom_price > 0">差价:{{$TOOL.money(item.custom_price)}}</text>
- </div>
- </div>
- <div class="order-status-btn">
- <div class="status info" v-if="item.status=='0'">未支付</div>
- <div class="status primary" v-if="item.status=='1'">待使用</div>
- <div class="status success" v-if="item.status=='2'">已完成</div>
- <div class="status danger" v-if="item.status=='3'">已退款</div>
- <div class="status warn" v-if="item.status=='4'">退款中</div>
- <div class="status warn" v-if="item.status=='5'">已关闭</div>
- <div class="status success" v-if="item.product.product_type==11 && item.custom_price > 0 && item.custom_status ==1">差价已付</div>
- <div class="status danger" v-if="item.product.product_type==11 && item.custom_price > 0 && item.custom_status ==0">差价未付</div>
- </div>
- </div>
- <div class="order-btn-group">
- <div class="status primary cus" @click="sendAddress(item)" v-if="item.status=='1' && user.trade == '2'">发起预约</div>
- <div class="status info cus" @click="editAddress(item)" v-if="item.status=='1' && user.trade == '2'">代客预约</div>
- <!-- <div class="status warn cus" v-if="item.product.product_type==11 && item.custom_price > 0 && item.custom_status ==0" @click="submitPrice(item)">补差价</div>
- <div class="status danger cus" v-if="item.product.product_type==11 && item.custom_price > 0 && item.custom_status ==0" @click="submitPrice(item)">改差价</div> -->
- <div class="status danger cus" @click="sendOrder(item)">发送订单</div>
- <div class="status primary cus" @click="doneOrder(item)" v-if="item.status=='1' && user.trade == '1'">手动核销</div>
- </div>
- </div>
- </div>
- <div class="order-list-chat" v-else>
- <el-empty description="没有订单" />
- </div>
- </el-main>
- <el-footer style="text-align: right;">
- <div class="order-page">
- <el-pagination small background :default-page-size="orderData.size" layout="prev, pager, next" @size-change="orderPageChange" @current-change="orderPageChange" :total="orderData.total" />
- </div>
- </el-footer>
- </el-container>
- </template>
- </el-main>
- </el-container>
- </el-aside>
- </el-container>
- </el-main>
- </el-container>
- </el-drawer>
- <orderDetail ref="orderDetail" @success="orderSuccess"></orderDetail>
- <quickTxt ref="quickTxt" @success="orderSuccess"></quickTxt>
- <addressMain ref="addressMain" @success="addressSuccess"></addressMain>
- </template>
- <script>
- import {
- mapState
- } from 'vuex';
- import { Push } from "@/utils/push";
- import { emojiMap } from "@/utils/emoji";
- import item from '@/components/chat/index';
- var pushObj = null;
- let height2 = null;
- const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
- import {
- formatDate,
- to as tofn
- } from '@/util/index.js';
- import orderDetail from '@/components/imFloat/order';
- import quickTxt from '@/components/imFloat/quickTxt';
- import addressMain from '@/components/imFloat/address';
- let imageList = [];
- export default {
- name: 'GlobalIM',
- components:{
- item,
- orderDetail,
- quickTxt,
- addressMain
- },
- props: {
- user: { type: Object, default: () => {} }
- },
- data() {
- return {
- activeName:"user",
- rules:{
- name: [
- {required: true, message: '请输入'}
- ],
- price: [
- {required: true, message: '请输入'}
- ],
- },
- priceForm:{
- name:"补差价"
- },
- uploadImg:"",
- loadMsg:false,
- history: {
- messages: [],
- allLoaded: false
- },
- msgLoad:false,
- emojiUrl,
- loading:false,
- visible: false, // 聊天窗口可见性
- unreadCount: 0, // 未读消息数量
- inputMessage: '', // 输入的消息
- timer: null, // 定时器
- messages: [],
- wsAuth:"https://tran.jsshuita.cn/plugin/webman/push/auth",
- wsUrl: 'wss://tran.jsshuita.cn/ws',
- // wsAuth:"http://127.0.0.1:9981/plugin/webman/push/auth",
- // wsUrl: 'ws://127.0.0.1:3131',
- page:1,
- size:10,
- msgPage:1,
- msgSize:20,
- orderSize:10,
- chatContact:[],
- checkChat:null,
- chatItem:null,
- emojiList:[],
- sendId:null,
- myid: this.user.userId,
- userId:null,
- isSaveing:false,
- quickData:{
- page:1,
- size:10,
- list:[],
- popState:false,
- total:0
- },
- msgOrder:null, // 咨询订单
- orderData:{
- page:1,
- size:10,
- list:[],
- loading:false,
- total:0,
- tab:"user",
- order:""
- },
- priceData:{
- page:1,
- size:10,
- list:[],
- loading:false,
- total:0,
- order:""
- }
- }
- },
- watch: {
- chatItem(val){
- if (val) {
- this.$nextTick(() => {
- this.$refs.messageListRef?.addEventListener('scroll', this.handleScroll)
- })
- }
- }
- },
- computed: mapState({
- //显示时间
- renderMessageDate() {
- return (message, index) => {
- if (message.timestamp - this.history.messages[index + 1]?.timestamp > 3 * 60 * 1000) {
- return formatDate(message.timestamp, 'timestamp');
- }
- return '';
- };
- },
- // 是否本人isMy
- isSelf() {
- return (senderId) => {
- const {
- member_id = this.myid
- } = {"member_id":this.myid};
- return senderId === `${member_id}`;
- };
- }
- }),
- mounted() {
- // console.log("pushObj")
- this.initEmoji()
- this.initPush();
- // this.simulateNewMessage()
- },
- beforeDestroy() {
- // if (this.timer) {
- // clearInterval(this.timer)
- // }
- this.$refs.messageListRef.removeEventListener('scroll', this.handleScroll)
- },
- methods: {
- editAddress(item){
- this.$nextTick(() => {
- this.$refs.addressMain.open("edit").setData(item);
- })
- },
- addressSuccess(){
- this.getOrderList();
- },
- handleClick(event){
- this.orderData.tab = event;
- switch (event) {
- case "user":
- break;
- case "order":
- this.orderData.page = 1;
- this.orderData.list = [];
- this.getOrderList();
- break;
- case "price":
- this.priceData.page = 1;
- this.priceData.list = [];
- this.getOrderPrice();
- break;
- }
- },
- searchOrder(){
- this.orderData.page = 1;
- this.orderData.list = [];
- this.getOrderList();
- },
- searchPrice(){
- this.priceData.page = 1;
- this.priceData.list = [];
- this.getOrderPrice();
- },
- getOrderPrice(){
- },
- viewOrder(data){
- data.order = data.out_order_no;
- this.$refs.orderDetail.open("add").setData(data)
- },
- quickPageChange(e){
- this.quickData.page = e;
- this.getQuick()
- },
- async getQuick(){
- var resp = await this.$API.merQuick.list.get({"page":this.quickData.page,"pageSize":this.quickData.size});
- if (resp.code == 0) {
- return this.$message.error(resp.msg)
- }
- this.quickData.total = resp.data.total;
- this.quickData.list = resp.data.rows;
- },
- addQuick(){
- this.$nextTick(() => {
- this.$refs.quickTxt.open("add")
- })
- },
- sendQuick(text){
- this.inputMessage = text;
- this.sendMessage()
- },
- orderSuccess(type,data){
- switch(type) {
- case "address":
- this.sendAddress(data)
- break;
- }
- },
- async submitPrice(data){
- var { priceForm,$message } = this;
- if (data.custom_status == 1) return $message.error("该订单差价已完成支付")
- // var validate = await this.$refs.priceForm.validate().catch(()=>{});
- // if(!validate){ return false }
- this.isSaveing = true;
- priceForm.name = "订单差额补款";
- priceForm.price = parseInt(data.custom_price) / 100;
- priceForm.type = "pay";
- priceForm.groupId = this.chatItem.poi_id
- priceForm.openid = this.chatItem.openid
- var resp = await this.$API.chat.price.post(priceForm);
- this.isSaveing = false;
- if (resp.code == 0) {
- return $message.error(resp.msg)
- }
- this.sendMessageData({
- payload: {
- pay: resp.data,
- },
- type: 'pay'
- });
- this.uploadImg = "";
- },
- orderPageChange(e){
- this.orderData.page = e;
- this.getOrderList()
- },
- hideWindow(){
- this.chatItem = null;
- this.msgPage = 1;
- this.unreadCount = 0;
- this.checkChat = null;
- this.visible = false;
- },
- handleScroll(e){
- const { scrollTop, clientHeight, scrollHeight } = e.target
- const isBottom = scrollTop + clientHeight >= scrollHeight - 5 // 5px 缓冲区
- const isTop = scrollTop <= 5
- // console.log("滚动",this.history.allLoaded)
- if (isBottom && !this.history.allLoaded) {
- console.log('到达底部')
- // 加载更多等操作
- this.getMsgMore()
- }
- if (isTop && this.history.allLoaded) {
- // console.log('到达顶部')
- }
- },
- playAudio(){
- const audio = new Audio('/img/msn.mp3');
- audio.play().catch(error => {
- console.error("播放音频时出错:", error);
- });
- },
- onLongpress(){},
- onItem(data){
- switch(data.type)
- {
- case "order":
- this.$nextTick(() => {
- this.$refs.orderDetail.open("add").setData(data.payload.order)
- })
- break;
- case "address":
- this.$nextTick(() => {
- this.$refs.orderDetail.open("add").setData(data.payload.address)
- })
- break;
- }
- },
- chooseEmoji(data){
- this.inputMessage += data;
- },
- initEmoji() {
- let newList = [];
- let i = 1;
- for (let key in emojiMap) {
- let obj = {};
- obj['index'] = i;
- obj['text'] = key;
- obj['image'] = emojiMap[key];
- i++;
- newList.push(obj);
- }
- var list = [];
- let A = newList.filter((item, index) => {
- return (index + 1) % 8 === 0;
- });
- A.unshift({ index: 0 });
- A.push({ index: newList.length });
- this.emojiList = A.map((item, index) => {
- return newList.slice(A[index].index, A[index + 1]?.index || newList.length);
- });
- // this.scroll({ detail: { scrollTop: 10 } });
- },
- /**
- * 发送订单消息
- * @param data
- */
- async sendOrder(data){
- var orderData = {"img":data.img,"name":data.product.product_name,"goods_id":data.life_goods_id,"order":data.out_order_no,"price":this.$TOOL.disMoney(data.pay_money),"status":data.status};
- var resp = await this.sendMessageApi("order",orderData);
- if (!resp) {
- return ;
- }
- this.sendMessageData({
- payload: {
- order: orderData
- },
- type: 'order'
- });
- },
- /**
- * 收集地址消息
- * @param data
- */
- async sendAddress(data){
- var orderData = {"img":data.img,"name":"完善会员信息","order":data.out_order_no,"status":0};
- var resp = await this.sendMessageApi("address",orderData);
- if (!resp) {
- return ;
- }
- this.sendMessageData({
- payload: {
- address: orderData
- },
- type: 'address'
- });
- },
- async sendMessage(){
- if (!this.inputMessage) return ;
- var resp = await this.sendMessageApi("text",this.inputMessage);
- if (!resp) {
- this.inputMessage = "";
- return ;
- }
- this.sendMessageData({
- payload: {
- text: this.inputMessage
- },
- type: 'text'
- });
- this.inputMessage = ""
- },
- sendMessageData({payload,type}){
- const message = {
- groupId: this.chatItem.poi_id,
- senderData: {
- avatar:this.chatItem.avatar
- },
- senderId: this.myid,
- messageId: Date.now(),
- payload: payload,
- timestamp: Date.now(),
- type: type,
- recalled: false,
- status: 'success',
- isHide: 0
- };
- this.pushList(message);
- },
- handleKeyCode(event){
- if (event.keyCode == 13) {
- if (!event.ctrlKey) {
- this.sendMessage()
- event.preventDefault();
- } else {
- this.inputMessage += "\n";
- }
- } else {
- return ;
- }
- },
- checkChatItem(index){
- this.checkChat = index;
- this.chatItem = this.chatContact[index]
- this.chatContact[index].last.num = 0;
- this.msgPage = 1;
- this.history.messages = []
- this.history.allLoaded = false
- this.orderData.list = [];
- this.orderData.page = 1;
- this.orderData.tab = "user";
- this.getMsgData();
- this.getMsgOrder();
- this.getOrderList();
- },
- async getMsgOrder(){
- var resp = await this.$API.chat.msgOrder.get({"msgId":this.chatItem.id})
- if (resp.code == 0) return ;
- this.msgOrder = resp.data.order;
- },
- initPush(){
- var _this = this;
- if (!this.pushObj) {
- this.pushObj = new Push({
- "url":this.wsUrl,
- "app_key":"265c33b73d5c04f918978577df2c48d2",
- "auth":this.wsAuth
- });
- }
- var user_channel = this.pushObj.subscribe('service-' + this.myid);
- user_channel.on('message', function (data) {
- _this.playAudio()
- _this.unreadCount ++;
- _this.sortItemData(data.item)
- if (_this.chatItem && _this.chatItem.openid == data.openid) { // 当前窗口实时推送
- _this.formatMsg(data);
- }
- // 给消息标红并更新最后一条消息
- var msgData = _this.chatContact;
- for (var i=0; i <= msgData.length;i ++) {
- var item = msgData[i];
- if (item && item.openid == data.openid) {
- item.last.type = data.type;
- item.last.content = data.content;
- item.last.create_at = data.create_at
- item.last.last_at = data.last_at
- // item.last.num ++;
- }
- }
- })
- },
- truncateByBytes(str, maxBytes) {
- let count = 0;
- let result = '';
- for (const char of str) {
- // 中文、全角符号算 2 字节,其他算 1 字节
- const byteLen = char.match(/[^\x00-\xff]/) ? 2 : 1;
- if (count + byteLen > maxBytes) break;
- result += char;
- count += byteLen;
- }
- return result + (count < str.length ? '...' : '');
- },
- sortItemData(data){
- const existIdx = this.chatContact.findIndex(item => item.id === data.id);
- if (existIdx !== -1) { // 若已存在,按更新处理
- this.handleNewMessage(data);
- return;
- }
- // 新增数据到最顶部
- this.chatContact.unshift(data);
- },
- handleNewMessage(updateData) {
- const idx = this.chatContact.findIndex(item => item.id === updateData.id);
- this.chatContact.splice(idx, 1, updateData);
- this.sortSessions();
- },
- sortSessions() {
- this.chatContact.sort((a, b) => {
- const timeA = a.last_at && a.last_at !== '-' ? new Date(a.last_at).getTime() : 0;
- const timeB = b.last_at && b.last_at !== '-' ? new Date(b.last_at).getTime() : 0;
- return timeB - timeA;
- });
- },
- sortBooks() {
- this.chatContact.sort((a, b) => b.last_at - a.last_at)
- },
- formatMsg(item){
- var payload = {};
- if (item.type == 'text') {
- payload.text = item.content
- }
- if (item.type == 'order') {
- var order = JSON.parse(item.content)
- payload.order = order
- }
- if (item.type == 'pay') {
- var order = JSON.parse(item.content)
- payload.pay = order
- }
- if (item.type == 'address') {
- var order = JSON.parse(item.content)
- payload.address = order
- }
- if (item.type == 'image') {
- payload = {
- contentType: 'image/png',
- name: 'uni-image.png',
- size: 82942,
- url: item.content,
- width: 2732,
- height: 2732,
- thumbnail: item.content
- };
- }
- const message = {
- groupId: item.poi_id,
- senderData: {
- avatar:item.source==2?item.avatar:item.user.avatar,
- name:item.source==2?item.nick.truename:item.user.nickname
- },
- senderId: (item.source==1?item.openid:item.service_id),
- messageId: item.msgId,
- payload: payload,
- timestamp: item.time,
- type: item.type,
- recalled: false,
- status: 'success',
- isHide: 0
- };
- this.pushList(message);
- },
- async pushList(message) {
- this.initMessageItem(message);
- this.history.messages.unshift(message);
- // 是否触发文字动效果
- if (message.type === 'text' || message.type === 'text_quote') {
- this.onSetText(message.payload.text);
- }
- // 缓存照片地址,
- if (message.type === 'image' || message.type === 'image_transmit') {
- imageList.push(message.payload.url);
- }
- },
- // 组装item
- initMessageItem(message, index) {
- message['isHide'] = 0;
- if (index === 0 && (message.type === 'text' || message.type === 'text_quote')) {
- this.onSetText(message.payload.text);
- }
- },
- // 文本触发效果相关========
- onSetText(text) {},
- // 切换聊天窗口
- async toggleChat() {
- this.visible = !this.visible
- if (this.visible) {
- // 打开窗口时清除未读角标
- this.unreadCount = 0
- }
- if (this.chatContact.length == 0) {
- this.loadMsg = true;
- var resp = await this.$API.chat.list.get({page:this.page,pageSize:this.size})
- this.loadMsg = false;
- if (resp.code == 0) {
- return this.$message.error(resp.msg)
- }
- this.chatContact = this.chatContact.concat(resp.data.rows)
- }
- // this.chatItem = this.chatContact[0]
- // this.userId = this.chatItem.openid;
- // this.getMsgData();
- },
- /**
- * 加载更多
- */
- async getMsgMore(){
- this.history.allLoaded = true;
- this.msgLoad = true;
- var resp = await this.$API.chat.msg.get({"page":this.msgPage,"size":this.msgSize,"openid":this.chatItem.openid})
- this.msgLoad = false;
- if (resp.data.rows.length > 0) {
- this.msgPage ++;
- var msgData = []
- var list = resp.data.rows
- // 同步混入数据
- list.forEach((item, ix) => {
- var payload = {};
- if (item.type == 'text') {
- payload.text = item.content
- }
- if (item.type == 'order') {
- var order = JSON.parse(item.content)
- payload.order = order
- }
- if (item.type == 'pay') {
- var order = JSON.parse(item.content)
- payload.pay = order
- }
- if (item.type == 'address') {
- var order = JSON.parse(item.content)
- payload.address = order
- }
- if (item.type == 'image') {
- payload = {
- contentType: 'image/png',
- name: 'uni-image.png',
- size: 82942,
- url: item.content,
- width: 2732,
- height: 2732,
- thumbnail: item.content
- };
- }
- const message = {
- groupId: item.poi_id,
- senderData: {
- avatar:item.avatar
- },
- senderId: (item.source==1?item.openid:item.service_id),
- messageId: item.msgId,
- payload: payload,
- timestamp: item.time,
- type: item.type,
- recalled: false,
- status: 'success',
- isHide: 0
- };
- msgData[ix] = message
- });
- this.history.messages = [...this.history.messages, ...msgData];
- this.history.allLoaded = false;
- } else {
- this.history.allLoaded = true;
- }
- },
- /**
- * 首次加载
- */
- async getMsgData(){
- var resp = await this.$API.chat.msg.get({"page":this.msgPage,"size":this.msgSize,"openid":this.chatItem.openid})
- if (resp.data.rows.length > 0) {
- var msgData = resp.data.rows
- msgData.forEach((item,index)=>{
- this.formatMsg(item);
- })
- this.msgPage ++;
- }
- },
- async uploadSuccess(data){
- var resp = await this.sendMessageApi("image",data.data.src);
- if (!resp) {
- return ;
- }
- this.sendMessageData({
- payload: {
- contentType: 'image/png',
- name: data.data.fileName,
- size: 0,
- url: data.data.src,
- width: 2732,
- height: 2732,
- thumbnail: data.data.src
- },
- type: 'image'
- });
- this.uploadImg = "";
- },
- async sendMessageApi(type,content){
- this.chatContact[this.checkChat].last.num = 0;
- var resp = await this.$API.chat.send.post({type:type,content: content,groupId: this.chatItem.poi_id,openid:this.chatItem.openid})
- if (resp.code == 0) return false;
- this.chatContact[this.checkChat].last.type = type;
- this.chatContact[this.checkChat].last.content = (type=='text'?this.truncateByBytes(content,18):content);
- this.chatContact[this.checkChat].last.create_at = resp.data.time;
- return true;
- },
- doneOrder(data){
- this.$confirm(`手动核销订单后,该订单不支持线上退款,如需退款需与客户线下进行处理❗️如商家原因拒绝退款,平台会代替商家先行垫付且店铺无法正常经营`, '注意事项', {
- type: 'warning'
- }).then(async ()=>{
- this.isDoneing = true;
- var resp = await this.$API.merOrder.done.post({"id":data.order_sn});
- this.isDoneing = false;
- if (resp.code == 0) {
- return this.$message.error(resp.msg);
- }
- this.$message.success(resp.msg);
- this.getOrderList()
- }).catch(()=>{})
- },
- async getOrderList(){
- this.orderData.loading = true;
- var resp = await this.$API.order.list.get({"page":this.orderData.page,"pageSize":this.orderData.size,"order":this.orderData.order,"openid":this.chatItem.openid});
- this.orderData.loading = false;
- if (resp.code == 0) {
- return ;
- }
- this.orderData.list = resp.data.rows;
- this.orderData.total = resp.data.total;
- }
- }
- }
- </script>
- <style scoped lang="scss">
- @import '@/style/index.scss';
- .order-info-user{background-color: #fff;padding: 10px;margin-top: 10px;}
- .order-info-user .title{font-size: 16px;color: #333;font-weight: bold;padding-bottom: 10px;border-bottom: 1px solid #f8f8f8;}
- .order-info-user .info-item{height: 40px;line-height: 40px;font-size: 12px;display: flex;align-items: center;border-bottom: 1px solid #f8f8f8;}
- .order-info-user .info-item .right{color: #666;margin-left: auto;margin-right: 0;}
- .order-info-user .info-item .right text{border: 1px solid #ccc;color: #333;font-size: 12px;padding: 0 15upx;margin-left: 10px;border-radius: 5px;}
- .aside-tabs{display: flex;height: 65px;align-items: center;gap: 10px;border-bottom: 1px solid var(--el-border-color-light);padding: 13px 15px;}
- .aside-tabs .aside-tab-items{font-size: 14px;cursor: pointer;padding: 0 20px;}
- .aside-tabs .aside-tab-items.active{color: var(--el-color-primary);}
- .quick-search{display: flex;align-items: center;justify-content: space-between;gap: 10px;}
- .quick-search .search-input{flex: 1;display: flex;align-items: center;gap: 5px;}
- .quick-search .search-btn-txt{font-size: 14px;color: #409eff;cursor: pointer;}
- .quick-table .quick-table-body{margin: 10px 0;}
- .border-none{border: 0;}
- .msg-title{font-size: 18px;font-weight: bold;}
- .loading-text{background-color: #f8f8f8;text-align: center;font-size: 12px;}
- .order-list-chat{height: 100%;flex-direction: column;flex: 1;display: flex;}
- .m-order-s{background-color: #fff;border-radius: 5px;margin-bottom: 10px;border: 1px solid #f8f8f8;}
- .m-order-s .m-order-item{display: flex;font-size: 14px;position: relative;background-color: #f8f8f8;}
- .m-order-s .order-btn-group{display: flex;align-items: center;padding: 10px;gap: 10px;}
- .order-search-input{display: flex;align-items: center;gap: 10px;flex: 1;}
- .m-order-s .order-img{display: flex;}
- .m-order-s .order-img image{width: 80px;height: 80px;}
- .m-order-s .order-info {padding: 5px 10px 10px;flex: 1;}
- .m-order-s .order-info .title{font-size: 14px;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}
- .m-order-s .order-info .desc{font-size: 12px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;color: #999;}
- .m-order-s .order-info .price{font-size: 14px;color: #f00;}
- .m-order-s .order-status-btn{position: absolute;right: 0;bottom: 0;display: flex;align-items: center;gap: 5px;}
- .m-order-s .status{border-radius: 5px 0 5px 0;background-color: #909399;font-size: 12px;color: #fff;padding: 5px;}
- .m-order-s .status.cus{cursor: pointer;}
- .m-order-s .status.info{background-color: #909399;}
- .m-order-s .status.primary{background-color: #409eff;}
- .m-order-s .status.success{background-color: #67c23a;}
- .m-order-s .status.danger{background-color: #f56c6c;}
- .m-order-s .status.warn{background-color: #e6a23c;}
- .hideStr{overflow: hidden;width: 80%;}
- .contact-item{display: flex;align-items: center;background-color: #fff;padding: 10px;border-bottom: 1px solid #f8f8f8;cursor: pointer;}
- .contact-item.active{background-color: #f8f8f8;}
- .contact-item .img{position: relative;border: 1px solid #f8f8f8;border-radius: 5px;display: flex;align-items: center;justify-content: center;}
- .contact-item span.last-num{background-color: #f00;color: #fff;width: 20px;height: 20px;display: flex;align-items: center;justify-content: center;border-radius: 20px;font-size: 12px;margin-left: 10px;position: absolute;right: 0;top: 0;}
- .contact-item .kefu-store{margin-left: 10px;}
- .contact-item .kefu-store .name{font-size: 14px;color: #333;display: flex;align-items: center;}
- .contact-item .kefu-store .desc{font-size: 12px;color: #666;margin-top: 5px;}
- .contact-item .time{margin-left: auto;margin-right: 0;font-size: 12px;color: #666;}
- .send-btn-group{margin-top: 10px;display: flex;align-items: center;justify-content: space-between;}
- .send-btn-group .send-emoji{display: flex;align-items: center;gap: 15px;cursor: pointer;}
- .quick-group{display: flex;align-items: center;gap: 10px;padding: 15px 0;}
- .quick-group .item-btn{background-color: #f8f8f8;padding: 5px;border-radius: 3px;display: flex;align-items: center;gap: 5px;cursor: pointer;}
- .z_index2{position: relative;z-index: 2;}
- .empty-data{display: flex;width: 100%;text-align: center;justify-content: center;font-size: 18px;background-color: #f8f8f8;align-items: center;}
- .swiper-item-box {
- position: relative;
- z-index: 1;
- box-sizing: border-box;
- width: 100%;
- height: 100%;
- max-height: 300px;
- overflow: auto;
- flex-wrap: wrap;
- align-items: flex-start;
- .swiper-item-box-l {
- // width: calc(62.5vw);
- width: calc(100%);
- flex-wrap: wrap;
- align-items: flex-start;
- }
- .swiper-item-box-r {
- width: calc(37.5%);
- flex-wrap: wrap;
- align-items: flex-start;
- }
- .emoji-item {
- width: calc(12.5%);
- height: calc(12.5%);
- flex-shrink: 0;
- transition: all 0.1s;
- padding: 10px 0;
-
- .img {
- width: 55%;
- height: 55%;
- cursor: pointer;
- }
- &:hover {
- background-color: #f8f8f8;
- }
- }
- }
- .flex_r {
- display: flex;
- flex-direction: row;
- }
- .swiper-item-box-b {
- width: calc(38%);
- position: absolute;
- z-index: 99;
- right: 0rpx;
- bottom: 0px;
- padding: 0 0 40rpx 0;
- // background-color: #000;
- background-image: linear-gradient(to top, #ececec, #ececec, #ececec, #ececec, #ececec, rgba(0, 0, 0, 0));
- }
- .swiper-item-box-delete {
- width: 116rpx;
- height: calc(12.5%);
- border-radius: 10rpx;
- margin-right: 20rpx;
- background-color: #fff;
- .img {
- width: 45%;
- height: 45%;
- margin-right: 4rpx;
- }
- }
- .swiper-item-box-button {
- width: 116rpx;
- height: calc(12.5vw);
- border-radius: 10rpx;
- margin-right: 10rpx;
- background-color: #05c160;
- }
- .page {
- position: fixed;
- z-index: 1;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- background-color: #ededed;
- }
- .scroll-Y {
- width: 100%;
- height: 0;
- transition: all 0.2s;
- transform: rotate(180deg);
- background-color: #ededed;
- ::-webkit-scrollbar {
- display: none;
- }
- }
- .scroll-view-str {
- width: 100%;
- }
- .message-list .time {
- width: 100%;
- color: #a3a3a3;
- line-height: 50px;
- }
- .message-list .recalled {
- width: 100%;
- height: 50rpx;
- margin: 20rpx 0;
- color: #a3a3a3;
- .recalled-edit {
- color: #5a6693;
- margin-left: 14rpx;
- }
- }
- @keyframes simple-blink {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0; }
- }
- .global-im {
- .unreadCount{
- position: fixed;
- right: 42px;
- bottom: 162px;
- animation: simple-blink 2s ease-in-out infinite;
- }
- .im-trigger {
- position: fixed;
- right: 24px;
- bottom: 124px;
- width: 48px;
- height: 48px;
- background: #409eff;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
- z-index: 2000;
- transition: all 0.3s;
- color: white;
- &:hover {
- background: #66b1ff;
- }
- &.active {
- background: #909399;
- }
- }
- }
- .chat-container {
- display: flex;
- flex-direction: column;
- height: 100%;
- .message-list {
- flex: 1;
- overflow-y: auto;
- background-color: #f8f8f8;
- transition: all 0.2s;
- transform: rotate(180deg);
- .message-item {
- display: flex;
- margin-bottom: 16px;
- &.self {
- flex-direction: row-reverse;
- .content {
- margin-right: 12px;
- align-items: flex-end;
- .text {
- background: #95ec69;
- }
- }
- }
- &.other {
- .content {
- margin-left: 12px;
- }
- }
- .content {
- display: flex;
- flex-direction: column;
- max-width: 70%;
- .name {
- font-size: 12px;
- color: #999;
- margin-bottom: 4px;
- }
- .text {
- padding: 8px 12px;
- background: #f0f0f0;
- border-radius: 12px;
- word-break: break-all;
- }
- .time {
- font-size: 10px;
- color: #ccc;
- margin-top: 4px;
- }
- }
- }
- }
- .input-area {
- padding: 0 16px 16px 16px;
- border-top: 1px solid #eee;
- }
- }
- </style>
|