|
@@ -0,0 +1,295 @@
|
|
|
|
|
+<!--
|
|
|
|
|
+ 商城「消息」Tab 页:聊天会话列表
|
|
|
|
|
+ 谁用:底部 tabBar「消息」入口
|
|
|
|
|
+ 解决:展示用户近期聊天门店列表,点击后进入 chatPage 会话详情
|
|
|
|
|
+-->
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="app-main app-content">
|
|
|
|
|
+ <view class="list-wrap">
|
|
|
|
|
+ <block v-if="$util.isEmpty(list.data) === false">
|
|
|
|
|
+ <block v-for="(item, index) in list.data" :key="index">
|
|
|
|
|
+ <tui-list-cell :arrow="true" @click="handleItemClick(item)">
|
|
|
|
|
+ <view class="user-card">
|
|
|
|
|
+ <view class="user-avatar">
|
|
|
|
|
+ <image
|
|
|
|
|
+ :src="item.avatar || getDefaultImage()"
|
|
|
|
|
+ class="avatar-img"
|
|
|
|
|
+ mode="aspectFill"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="user-info">
|
|
|
|
|
+ <view class="user-main">
|
|
|
|
|
+ <!-- 首店用商户名/手机号兜底,避免显示无意义的「首店」 -->
|
|
|
|
|
+ <view class="user-name">{{
|
|
|
|
|
+ item.shopName != "首店"
|
|
|
|
|
+ ? item.shopName
|
|
|
|
|
+ : item.merchantName || item.mobile
|
|
|
|
|
+ }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="user-meta">
|
|
|
|
|
+ <view class="visit-time">
|
|
|
|
|
+ {{ formatChatTime(item.chatTime) }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="item.unReadNum > 0" class="unread-badge">
|
|
|
|
|
+ {{ item.unReadNum > 9 ? '9+' : item.unReadNum }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </tui-list-cell>
|
|
|
|
|
+ </block>
|
|
|
|
|
+ </block>
|
|
|
|
|
+ <block v-else>
|
|
|
|
|
+ <app-wrapper-empty :is-empty="true" />
|
|
|
|
|
+ </block>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+<script>
|
|
|
|
|
+/**
|
|
|
|
|
+ * 消息 Tab:拉取最近聊天门店列表,并通过 eventChannel 打开聊天详情页
|
|
|
|
|
+ */
|
|
|
|
|
+import TuiListCell from "@/components/plugin/list-cell";
|
|
|
|
|
+import AppWrapperEmpty from "@/components/app-wrapper-empty";
|
|
|
|
|
+import { list } from "@/mixins";
|
|
|
|
|
+import { getLatestUsers } from "@/api/chat";
|
|
|
|
|
+import { mapGetters } from "vuex";
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: "MessageIndex",
|
|
|
|
|
+ components: {
|
|
|
|
|
+ TuiListCell,
|
|
|
|
|
+ AppWrapperEmpty
|
|
|
|
|
+ },
|
|
|
|
|
+ mixins: [list],
|
|
|
|
|
+ data() {
|
|
|
|
|
+ // 预计算常用日期值,减少 formatChatTime 中重复 new Date
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
|
|
|
+
|
|
|
|
|
+ const formatDateToString = (date) => {
|
|
|
|
|
+ const y = date.getFullYear();
|
|
|
|
|
+ const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
+ const d = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
+ return `${y}-${m}-${d}`;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ shopAvatar: '',
|
|
|
|
|
+ currentYear: now.getFullYear(),
|
|
|
|
|
+ todayStr: formatDateToString(now),
|
|
|
|
|
+ yesterdayStr: formatDateToString(yesterday),
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ // 进入页面时刷新日期基准,避免跨天后「今天/昨天」判断错误
|
|
|
|
|
+ this.updateDateStrings();
|
|
|
|
|
+ },
|
|
|
|
|
+ onPullDownRefresh() {
|
|
|
|
|
+ this.resetList();
|
|
|
|
|
+ this.getMyShopList().then(() => {
|
|
|
|
|
+ uni.stopPullDownRefresh();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ onReachBottom() {
|
|
|
|
|
+ if (!this.list.finished) {
|
|
|
|
|
+ this.getMyShopList();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ ...mapGetters(["getLoginInfo"]),
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad() {
|
|
|
|
|
+ // 当前登录用户头像:用于进入聊天页时作为自己的头像
|
|
|
|
|
+ this.shopAvatar = this.getLoginInfo.avatar != ''
|
|
|
|
|
+ ? this.getLoginInfo.avatar
|
|
|
|
|
+ : this.$constant.imgUrl + '/hhb_small.png?x-oss-process=image/resize,m_fill,h_130,w_130';
|
|
|
|
|
+ },
|
|
|
|
|
+ onShow() {
|
|
|
|
|
+ // Tab 页保活,每次切回消息 Tab 都刷新会话列表与未读数
|
|
|
|
|
+ this.getMyShopList();
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 点击会话项:本地清未读,经 eventChannel 打开 chatPage
|
|
|
|
|
+ * @param {Object} item 列表项,含 customId/id/shopName/unReadNum 等
|
|
|
|
|
+ */
|
|
|
|
|
+ handleItemClick(item) {
|
|
|
|
|
+ if (item.unReadNum > 0) {
|
|
|
|
|
+ item.unReadNum = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ customId: item.customId,
|
|
|
|
|
+ shopId: item.id,
|
|
|
|
|
+ name: this.getLoginInfo.name,
|
|
|
|
|
+ avatar: this.getLoginInfo.avatar,
|
|
|
|
|
+ chatPerson: item.shopName != "首店" ? item.shopName : item.merchantName || item.mobile,
|
|
|
|
|
+ };
|
|
|
|
|
+ uni.navigateTo({
|
|
|
|
|
+ url: '/pages/chat/chatPage',
|
|
|
|
|
+ success: function(res) {
|
|
|
|
|
+ res.eventChannel.emit('acceptDataFromOpenerPage', params)
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ init() {
|
|
|
|
|
+ },
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 拉取最近聊天门店列表
|
|
|
|
|
+ * @returns {Promise} 接口 Promise,供下拉刷新 await
|
|
|
|
|
+ */
|
|
|
|
|
+ getMyShopList() {
|
|
|
|
|
+ return getLatestUsers({}).then(res => {
|
|
|
|
|
+ if (res.code == 1) {
|
|
|
|
|
+ this.list.data = res.data;
|
|
|
|
|
+ this.list.finished = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.list.loading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 默认头像:接口未返回 avatar 时的占位图 */
|
|
|
|
|
+ getDefaultImage() {
|
|
|
|
|
+ return this.$constant.imgUrl + '/retail/default-img.png';
|
|
|
|
|
+ },
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 会话时间展示规则:今天仅时分;昨天加「昨天」前缀;今年 MM-DD HH:mm;跨年完整串
|
|
|
|
|
+ * @param {string} chatTime 后端返回的 "YYYY-MM-DD HH:mm" 字符串
|
|
|
|
|
+ * @returns {string} 格式化后的展示文案
|
|
|
|
|
+ */
|
|
|
|
|
+ formatChatTime(chatTime) {
|
|
|
|
|
+ if (!chatTime) return '';
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const spaceIndex = chatTime.indexOf(' ');
|
|
|
|
|
+ const dateStr = chatTime.substring(0, spaceIndex);
|
|
|
|
|
+ const timeStr = chatTime.substring(spaceIndex + 1);
|
|
|
|
|
+ const year = parseInt(dateStr.substring(0, 4));
|
|
|
|
|
+ const chatDateStr = dateStr;
|
|
|
|
|
+
|
|
|
|
|
+ if (chatDateStr === this.todayStr) {
|
|
|
|
|
+ return timeStr;
|
|
|
|
|
+ } else if (chatDateStr === this.yesterdayStr) {
|
|
|
|
|
+ return `昨天 ${timeStr}`;
|
|
|
|
|
+ } else if (year === this.currentYear) {
|
|
|
|
|
+ return chatTime.substring(5, 16);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return chatTime;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('时间格式化失败:', error);
|
|
|
|
|
+ return chatTime;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 刷新 today/yesterday/currentYear,供跨日场景使用 */
|
|
|
|
|
+ updateDateStrings() {
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
|
|
|
+
|
|
|
|
|
+ const formatDateToString = (date) => {
|
|
|
|
|
+ const y = date.getFullYear();
|
|
|
|
|
+ const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
+ const d = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
+ return `${y}-${m}-${d}`;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.todayStr = formatDateToString(now);
|
|
|
|
|
+ this.yesterdayStr = formatDateToString(yesterday);
|
|
|
|
|
+ this.currentYear = now.getFullYear();
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.app-content {
|
|
|
|
|
+ padding-bottom: 20upx;
|
|
|
|
|
+}
|
|
|
|
|
+.user-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 12upx 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .user-avatar {
|
|
|
|
|
+ width: 80upx;
|
|
|
|
|
+ height: 80upx;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-left: 16upx;
|
|
|
|
|
+ margin-right: 16upx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+
|
|
|
|
|
+ .avatar-img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ height: 80upx;
|
|
|
|
|
+ padding-right: 150upx;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-main {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ margin-bottom: 4upx;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-name {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ font-size: 34upx;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ margin-right: 10upx;
|
|
|
|
|
+ max-width: calc(100% - 50upx);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .user-meta {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ font-size: 26upx;
|
|
|
|
|
+ color: #9397a4;
|
|
|
|
|
+ width: 495upx;
|
|
|
|
|
+ margin-top: 8upx;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .visit-time {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.unread-badge {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ right: 60upx;
|
|
|
|
|
+ transform: translateY(-50%);
|
|
|
|
|
+ background-color: #ff4d4f;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ font-size: 28upx;
|
|
|
|
|
+ min-width: 42upx;
|
|
|
|
|
+ height: 42upx;
|
|
|
|
|
+ border-radius: 21upx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 0 10upx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+::v-deep .tui-list-cell.tui-cell-arrow:before {
|
|
|
|
|
+ z-index: 10;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|