优化了样式,抽象出了 WXSS/JS 库

This commit is contained in:
2026-06-13 18:19:46 +08:00
parent 6fbae9b32c
commit 3dc03f6fe5
51 changed files with 7154 additions and 9035 deletions

View File

@@ -1,403 +1,394 @@
// pages/messages/messages.js
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getLocalImUserId } from '../../utils/im-user.js';
import { normalizeConversationsList } from '../../utils/conversation-display.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getPrimaryRole } from '../../utils/primary-role.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
Page({
data: {
allConversations: [],
filteredConversations: [],
activeTab: 0,
tabUnread: [0, 0, 0],
displayCount: 10,
searchKeyword: '',
actionPopup: { visible: false, conversation: null },
currentUser: null,
notificationMuted: false,
scrollHeight: 0
},
_permissionSeq: 0,
_peerProfileCache: {},
onLoad() {
this.calculateScrollHeight();
const muted = wx.getStorageSync('notificationMuted') || false;
this.setData({ notificationMuted: muted });
},
onShow() {
if (!this.checkLoginStatus()) return;
this.registerNotificationComponent();
this.checkPermissionAndAutoConnect();
},
onHide() {},
onUnload() {},
// ========== ==== ================ ==== ================ 鉴权检查 ==========
async checkPermissionAndAutoConnect() {
const seq = ++this._permissionSeq;
let quanxian;
try {
quanxian = await jianquanxian(app);
} catch (e) {
return;
}
if (seq !== this._permissionSeq) return;
if (!quanxian.allowed) {
const currentRole = app.globalData.currentRole || 'normal';
let content = '您没有权限使用消息功能';
if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
wx.showModal({
title: '权限不足',
content,
showCancel: false,
confirmText: '我知道了'
});
return;
}
this.autoConnect();
},
// 登录状态检查
checkLoginStatus() {
const uid = wx.getStorageSync('uid');
if (!uid) {
wx.showToast({ title: '请先登录', icon: 'none' });
setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 1500);
return false;
}
let currentUser = app.globalData.currentUser;
if (!currentUser) {
currentUser = {
id: getLocalImUserId(app) || uid,
name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)),
avatar: resolveAvatarFromStorage(app),
};
app.globalData.currentUser = currentUser;
}
this.setData({ currentUser });
return true;
},
registerNotificationComponent() {
const notificationComp = this.selectComponent('#global-notification');
if (notificationComp && notificationComp.showNotification) {
app.globalData.globalNotification = {
show: (data) => notificationComp.showNotification(data),
hide: () => notificationComp.hideNotification(),
};
}
},
autoConnect() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
if (app.ensureConnection) app.ensureConnection();
reconnectForRole(getPrimaryRole(app));
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.setupConversationListener();
this.loadConversations();
return;
}
try {
wx.goEasy.disconnect();
} catch (e) {}
}
this.connectGoEasy(targetUserId);
},
connectGoEasy(userId) {
const { currentUser } = this.data;
if (!currentUser) return;
wx.goEasy.connect({
id: userId,
data: {
name: currentUser.name,
avatar: resolveAvatarUrl(currentUser.avatar, app),
},
onSuccess: () => {
try {
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
userId,
identityType: app.globalData.currentRole,
lastConnectTime: Date.now(),
status: 'connected',
}));
} catch (e) {}
this.setupConversationListener();
this.loadConversations();
if (app.registerNotification) {
}
},
onFailed: (error) => {
if (error.code === 408) {
this.setupConversationListener();
this.loadConversations();
} else {
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
}
},
});
},
setupConversationListener() {
if (!wx.goEasy || !wx.goEasy.im) return;
if (this.conversationsUpdatedListener) {
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
}
this.conversationsUpdatedListener = (result) => {
const content = result?.content || result;
this.renderConversations(content);
};
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
},
loadConversations() {
if (!wx.goEasy || !wx.goEasy.im) return;
wx.goEasy.im.latestConversations({
onSuccess: (result) => {
const content = result?.content || result;
this.renderConversations(content);
},
onFailed: () => {
wx.showToast({ title: '获取会话失败', icon: 'none' });
},
});
},
// 会话渲染与排序
renderConversations(content) {
const conversations = (content && content.conversations) ? content.conversations : [];
if (!conversations.length) {
this.setData({ allConversations: [] }, () => this.applyFilter());
return;
}
let list = normalizeConversationsList(conversations, app, this._peerProfileCache);
list.forEach(item => {
if (!item.lastMessage) {
item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 };
}
if (item.lastMessage.timestamp) {
item.lastMessage.date = formatDate(item.lastMessage.timestamp);
}
});
list.sort((a, b) => {
if (a.top && !b.top) return -1;
if (!a.top && b.top) return 1;
if (a.unread > 0 && b.unread <= 0) return -1;
if (a.unread <= 0 && b.unread > 0) return 1;
const ta = (a.lastMessage && a.lastMessage.timestamp) || 0;
const tb = (b.lastMessage && b.lastMessage.timestamp) || 0;
return tb - ta;
});
this.setData({ allConversations: list, displayCount: 10 }, () => {
this.applyFilter();
});
const unreadTotal = content.unreadTotal !== undefined
? content.unreadTotal
: list.reduce((sum, c) => sum + (c.unread || 0), 0);
if (app.updateUnreadCount) {
app.updateUnreadCount(unreadTotal);
} else if (app.emitEvent) {
app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' });
}
// 重新订阅群组
this.subscribeGroupsIfNeeded(list);
},
/**
* 群组订阅,每次获取群组列表时直接订阅保证消息不丢失
*/
subscribeGroupsIfNeeded(conversations) {
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return;
const groupIds = conversations
.filter(c => c.type === 'group' && c.groupId)
.map(c => c.groupId);
if (groupIds.length === 0) return;
// 不再判断缓存,直接订阅当前所有群组
wx.goEasy.im.subscribeGroup({
groupIds: groupIds,
onSuccess: () => {
// 可选的日志输出,不影响功能
// console.log('群组订阅成功', groupIds);
},
onFailed: (error) => {
console.error('群组订阅失败:', error);
}
});
},
applyFilter() {
const { allConversations, activeTab, searchKeyword } = this.data;
let filtered = [];
if (activeTab === 0) {
filtered = allConversations.filter(c => c.type === 'group' && c.data && c.data.orderId);
} else if (activeTab === 1) {
filtered = allConversations.filter(c => c.type === 'private');
} else if (activeTab === 2) {
filtered = allConversations.filter(c => c.type === 'cs');
}
if (searchKeyword.trim()) {
const kw = searchKeyword.trim().toLowerCase();
filtered = filtered.filter(c => {
const name = (c.data && c.data.name || '').toLowerCase();
const orderId = (c.data && c.data.orderId || '').toLowerCase();
const id = (c.userId || c.groupId || '').toLowerCase();
return name.includes(kw) || id.includes(kw) || orderId.includes(kw);
});
}
const tabUnread = [0, 0, 0];
allConversations.forEach(c => {
if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0;
else if (c.type === 'private') tabUnread[1] += c.unread || 0;
else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
});
this.setData({ filteredConversations: filtered, tabUnread });
},
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index);
this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter());
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
},
onScrollToLower() {
const { displayCount, filteredConversations } = this.data;
if (displayCount < filteredConversations.length) {
this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) });
}
},
onRefresh() {
this.loadConversations();
},
chat(e) {
const conversation = e.currentTarget.dataset.conversation;
if (!conversation) return;
if (conversation.type === 'private') {
const param = {
toUserId: conversation.userId,
toName: conversation.data.name || '用户',
toAvatar: resolveAvatarUrl(conversation.data.avatar, app),
};
wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'group') {
const param = {
groupId: conversation.groupId,
orderId: conversation.data.orderId || '',
groupName: conversation.data.name,
groupAvatar: conversation.data.avatar,
isCross: conversation.data.isCross || 0
};
wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'cs') {
const param = { teamId: conversation.teamId || conversation.id };
wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
}
},
chatKefu() {
const param = { teamId: 'support_team' };
wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
},
showAction(e) {
const conversation = e.currentTarget.dataset.conversation;
this.setData({ ['actionPopup.conversation']: conversation, ['actionPopup.visible']: true });
},
closeMask() {
this.setData({ ['actionPopup.visible']: false });
},
topConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
const top = !conversation.top;
wx.showLoading({ title: '处理中...' });
if (conversation.type === 'private') {
wx.goEasy.im.topPrivateConversation({
userId: conversation.userId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
} else {
wx.goEasy.im.topGroupConversation({
groupId: conversation.groupId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
}
this.closeMask();
},
removeConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
wx.showLoading({ title: '删除中...' });
wx.goEasy.im.removeConversation({
conversation: conversation,
onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); },
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }
});
this.closeMask();
},
toggleNotification() {
const newMuted = !this.data.notificationMuted;
this.setData({ notificationMuted: newMuted });
wx.setStorageSync('notificationMuted', newMuted);
wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' });
},
calculateScrollHeight() {
const systemInfo = wx.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const windowWidth = systemInfo.windowWidth;
const rpxRatio = 750 / windowWidth;
const headerHeight = 150 / rpxRatio;
const tabBarHeight = 130 / rpxRatio;
const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0;
this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - safeAreaBottom });
},
onAvatarError(e) {
const index = e.currentTarget.dataset.index;
const def = getDefaultAvatarUrl(app);
const key = `filteredConversations[${index}].data.avatar`;
this.setData({ [key]: def });
}
});
// pages/messages/messages.js
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getLocalImUserId } from '../../utils/im-user.js';
import { normalizeConversationsList } from '../../utils/conversation-display.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getPrimaryRole } from '../../utils/primary-role.js';
import { createPage } from '../../utils/base-page.js';
const { jianquanxian } = require('../../utils/imAuth/jianquanxian');
Page(createPage({
data: {
allConversations: [],
filteredConversations: [],
activeTab: 0,
tabUnread: [0, 0, 0],
displayCount: 10,
searchKeyword: '',
actionPopup: { visible: false, conversation: null },
currentUser: null,
notificationMuted: false,
scrollHeight: 0
},
_permissionSeq: 0,
_peerProfileCache: {},
onLoad() {
this.calculateScrollHeight();
const muted = wx.getStorageSync('notificationMuted') || false;
this.setData({ notificationMuted: muted });
},
onShow() {
if (!this.checkLoginStatus()) return;
this.registerNotificationComponent();
this.checkPermissionAndAutoConnect();
},
onHide() {},
onUnload() {},
// ========== ==== ================ ==== ================ 鉴权检查 ==========
async checkPermissionAndAutoConnect() {
const seq = ++this._permissionSeq;
let quanxian;
try {
quanxian = await jianquanxian(app);
} catch (e) {
return;
}
if (seq !== this._permissionSeq) return;
if (!quanxian.allowed) {
const currentRole = app.globalData.currentRole || 'normal';
let content = '您没有权限使用消息功能';
if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
wx.showModal({
title: '权限不足',
content,
showCancel: false,
confirmText: '我知道了'
});
return;
}
this.autoConnect();
},
// 登录状态检查
checkLoginStatus() {
const uid = wx.getStorageSync('uid');
if (!uid) {
wx.showToast({ title: '请先登录', icon: 'none' });
setTimeout(() => { wx.redirectTo({ url: '/pages/mine/mine' }); }, 1500);
return false;
}
let currentUser = app.globalData.currentUser;
if (!currentUser) {
currentUser = {
id: getLocalImUserId(app) || uid,
name: wx.getStorageSync('nicheng') || app.globalData.dashouNicheng || app.globalData.nicheng || ('用户' + uid.substring(0, 6)),
avatar: resolveAvatarFromStorage(app),
};
app.globalData.currentUser = currentUser;
}
this.setData({ currentUser });
return true;
},
autoConnect() {
const targetUserId = getLocalImUserId(app);
if (!targetUserId) return;
if (app.ensureConnection) app.ensureConnection();
reconnectForRole(getPrimaryRole(app));
const status = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
if (status === 'connected' || status === 'reconnected') {
const currentUserId = wx.goEasy.im ? wx.goEasy.im.userId : null;
if (currentUserId === targetUserId) {
this.setupConversationListener();
this.loadConversations();
return;
}
try {
wx.goEasy.disconnect();
} catch (e) {}
}
this.connectGoEasy(targetUserId);
},
connectGoEasy(userId) {
const { currentUser } = this.data;
if (!currentUser) return;
wx.goEasy.connect({
id: userId,
data: {
name: currentUser.name,
avatar: resolveAvatarUrl(currentUser.avatar, app),
},
onSuccess: () => {
try {
wx.setStorageSync('savedGoEasyConnection', JSON.stringify({
userId,
identityType: app.globalData.currentRole,
lastConnectTime: Date.now(),
status: 'connected',
}));
} catch (e) {}
this.setupConversationListener();
this.loadConversations();
if (app.registerNotification) {
}
},
onFailed: (error) => {
if (error.code === 408) {
this.setupConversationListener();
this.loadConversations();
} else {
wx.showToast({ title: '连接失败,请重试', icon: 'none' });
}
},
});
},
setupConversationListener() {
if (!wx.goEasy || !wx.goEasy.im) return;
if (this.conversationsUpdatedListener) {
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
}
this.conversationsUpdatedListener = (result) => {
const content = result?.content || result;
this.renderConversations(content);
};
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
},
loadConversations() {
if (!wx.goEasy || !wx.goEasy.im) return;
wx.goEasy.im.latestConversations({
onSuccess: (result) => {
const content = result?.content || result;
this.renderConversations(content);
},
onFailed: () => {
wx.showToast({ title: '获取会话失败', icon: 'none' });
},
});
},
// 会话渲染与排序
renderConversations(content) {
const conversations = (content && content.conversations) ? content.conversations : [];
if (!conversations.length) {
this.setData({ allConversations: [] }, () => this.applyFilter());
return;
}
let list = normalizeConversationsList(conversations, app, this._peerProfileCache);
list.forEach(item => {
if (!item.lastMessage) {
item.lastMessage = { type: 'text', payload: { text: '' }, timestamp: 0 };
}
if (item.lastMessage.timestamp) {
item.lastMessage.date = formatDate(item.lastMessage.timestamp);
}
});
list.sort((a, b) => {
if (a.top && !b.top) return -1;
if (!a.top && b.top) return 1;
if (a.unread > 0 && b.unread <= 0) return -1;
if (a.unread <= 0 && b.unread > 0) return 1;
const ta = (a.lastMessage && a.lastMessage.timestamp) || 0;
const tb = (b.lastMessage && b.lastMessage.timestamp) || 0;
return tb - ta;
});
this.setData({ allConversations: list, displayCount: 10 }, () => {
this.applyFilter();
});
const unreadTotal = content.unreadTotal !== undefined
? content.unreadTotal
: list.reduce((sum, c) => sum + (c.unread || 0), 0);
if (app.updateUnreadCount) {
app.updateUnreadCount(unreadTotal);
} else if (app.emitEvent) {
app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' });
}
// 重新订阅群组
this.subscribeGroupsIfNeeded(list);
},
/**
* 群组订阅,每次获取群组列表时直接订阅保证消息不丢失
*/
subscribeGroupsIfNeeded(conversations) {
if (!wx.goEasy?.im || typeof wx.goEasy.im.subscribeGroup !== 'function') return;
const groupIds = conversations
.filter(c => c.type === 'group' && c.groupId)
.map(c => c.groupId);
if (groupIds.length === 0) return;
// 不再判断缓存,直接订阅当前所有群组
wx.goEasy.im.subscribeGroup({
groupIds: groupIds,
onSuccess: () => {
// 可选的日志输出,不影响功能
// console.log('群组订阅成功', groupIds);
},
onFailed: (error) => {
console.error('群组订阅失败:', error);
}
});
},
applyFilter() {
const { allConversations, activeTab, searchKeyword } = this.data;
let filtered = [];
if (activeTab === 0) {
filtered = allConversations.filter(c => c.type === 'group' && c.data && c.data.orderId);
} else if (activeTab === 1) {
filtered = allConversations.filter(c => c.type === 'private');
} else if (activeTab === 2) {
filtered = allConversations.filter(c => c.type === 'cs');
}
if (searchKeyword.trim()) {
const kw = searchKeyword.trim().toLowerCase();
filtered = filtered.filter(c => {
const name = (c.data && c.data.name || '').toLowerCase();
const orderId = (c.data && c.data.orderId || '').toLowerCase();
const id = (c.userId || c.groupId || '').toLowerCase();
return name.includes(kw) || id.includes(kw) || orderId.includes(kw);
});
}
const tabUnread = [0, 0, 0];
allConversations.forEach(c => {
if (c.type === 'group' && c.data && c.data.orderId) tabUnread[0] += c.unread || 0;
else if (c.type === 'private') tabUnread[1] += c.unread || 0;
else if (c.type === 'cs') tabUnread[2] += c.unread || 0;
});
this.setData({ filteredConversations: filtered, tabUnread });
},
switchTab(e) {
const index = parseInt(e.currentTarget.dataset.index);
this.setData({ activeTab: index, displayCount: 10, searchKeyword: '' }, () => this.applyFilter());
},
onSearchInput(e) {
this.setData({ searchKeyword: e.detail.value, displayCount: 10 }, () => this.applyFilter());
},
onScrollToLower() {
const { displayCount, filteredConversations } = this.data;
if (displayCount < filteredConversations.length) {
this.setData({ displayCount: Math.min(displayCount + 10, filteredConversations.length) });
}
},
onRefresh() {
this.loadConversations();
},
chat(e) {
const conversation = e.currentTarget.dataset.conversation;
if (!conversation) return;
if (conversation.type === 'private') {
const param = {
toUserId: conversation.userId,
toName: conversation.data.name || '用户',
toAvatar: resolveAvatarUrl(conversation.data.avatar, app),
};
wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'group') {
const param = {
groupId: conversation.groupId,
orderId: conversation.data.orderId || '',
groupName: conversation.data.name,
groupAvatar: conversation.data.avatar,
isCross: conversation.data.isCross || 0
};
wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'cs') {
const param = { teamId: conversation.teamId || conversation.id };
wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
}
},
chatKefu() {
const param = { teamId: 'support_team' };
wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
},
showAction(e) {
const conversation = e.currentTarget.dataset.conversation;
this.setData({ ['actionPopup.conversation']: conversation, ['actionPopup.visible']: true });
},
closeMask() {
this.setData({ ['actionPopup.visible']: false });
},
topConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
const top = !conversation.top;
wx.showLoading({ title: '处理中...' });
if (conversation.type === 'private') {
wx.goEasy.im.topPrivateConversation({
userId: conversation.userId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
} else {
wx.goEasy.im.topGroupConversation({
groupId: conversation.groupId, top,
onSuccess: () => {
wx.hideLoading(); this.loadConversations();
wx.showToast({ title: top ? '已置顶' : '已取消置顶' });
},
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '操作失败', icon: 'none' }); }
});
}
this.closeMask();
},
removeConversation() {
let conversation = this.data.actionPopup.conversation;
if (!conversation) return;
wx.showLoading({ title: '删除中...' });
wx.goEasy.im.removeConversation({
conversation: conversation,
onSuccess: () => { wx.hideLoading(); this.loadConversations(); wx.showToast({ title: '已删除' }); },
onFailed: () => { wx.hideLoading(); wx.showToast({ title: '删除失败', icon: 'none' }); }
});
this.closeMask();
},
toggleNotification() {
const newMuted = !this.data.notificationMuted;
this.setData({ notificationMuted: newMuted });
wx.setStorageSync('notificationMuted', newMuted);
wx.showToast({ title: newMuted ? '已静音' : '已开启通知', icon: 'none' });
},
calculateScrollHeight() {
const systemInfo = wx.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const windowWidth = systemInfo.windowWidth;
const rpxRatio = 750 / windowWidth;
const headerHeight = 150 / rpxRatio;
const tabBarHeight = 130 / rpxRatio;
const safeAreaBottom = systemInfo.safeArea ? (windowHeight - systemInfo.safeArea.bottom) : 0;
this.setData({ scrollHeight: windowHeight - headerHeight - tabBarHeight - safeAreaBottom });
},
onAvatarError(e) {
const index = e.currentTarget.dataset.index;
const def = getDefaultAvatarUrl(app);
const key = `filteredConversations[${index}].data.avatar`;
this.setData({ [key]: def });
}
}))

View File

@@ -1,38 +1,254 @@
.msg-page { background: #f5f5f5; }
/* pages/messages/messages.wxss */
.header { display: flex; align-items: center; padding: 20rpx 24rpx; background: #fff; border-bottom: 1rpx solid #e8e8e8; }
.search-bar { flex: 1; }
.search-input { height: 68rpx; }
.search-icon { font-size: 32rpx; margin-left: 10rpx; color: #999; }
.notify-switch { margin-left: 20rpx; }
.notify-text { font-size: 26rpx; color: #666; padding: 10rpx 16rpx; background: #f0f0f0; border-radius: 30rpx; }
.msg-page {
background: #f5f5f5;
min-height: 100vh;
}
.tab-row { border-bottom: 1rpx solid #e8e8e8; }
/* .tab-item 已由全局 .tab-item 覆盖 */
.tab-item.active { color: #07c160; font-weight: 600; }
.tab-item.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 60rpx; height: 6rpx; background: #07c160; border-radius: 3rpx; }
.tab-badge { border-radius: 20rpx; padding: 2rpx 12rpx; margin-left: 8rpx; }
/* 顶部搜索 + 通知 */
.header {
display: flex;
align-items: center;
padding: 20rpx 24rpx;
background: #fff;
}
.conversation-list { background: #fff; }
.conversation-item { display: flex; align-items: center; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; }
.has-unread { background: #fafafa; }
.avatar { width: 96rpx; height: 96rpx; border-radius: 12rpx; margin-right: 20rpx; background: #eee; }
.info { flex: 1; }
.top-line { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
.name { font-size: 30rpx; color: #1a1a1a; font-weight: 500; }
.time { font-size: 22rpx; color: #999; }
.bottom-line { display: flex; justify-content: space-between; align-items: center; }
.last-msg { font-size: 26rpx; color: #888; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unread-dot { background: #fa5151; color: #fff; font-size: 20rpx; border-radius: 50%; min-width: 36rpx; height: 36rpx; line-height: 36rpx; text-align: center; margin-left: 12rpx; }
.load-tip { text-align: center; padding: 24rpx; color: #999; font-size: 24rpx; }
.empty { text-align: center; padding: 200rpx 0; color: #bbb; font-size: 28rpx; }
.search-bar {
flex: 1;
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 36rpx;
padding: 0 24rpx;
height: 68rpx;
}
.search-input {
flex: 1;
height: 68rpx;
font-size: 28rpx;
color: #333;
}
.search-icon {
font-size: 28rpx;
margin-left: 10rpx;
color: #999;
}
.notify-switch {
margin-left: 20rpx;
flex-shrink: 0;
}
.notify-text {
font-size: 24rpx;
color: #666;
padding: 10rpx 20rpx;
background: #f0f0f0;
border-radius: 30rpx;
}
/* TAB 栏 */
.tab-row {
display: flex;
background: #fff;
border-bottom: 1rpx solid #e8e8e8;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
position: relative;
font-size: 28rpx;
color: #666;
}
.tab-item.active {
color: #07c160;
font-weight: 600;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 6rpx;
background: #07c160;
border-radius: 3rpx;
}
.tab-label {
line-height: 1;
}
.tab-badge {
background: #fa5151;
color: #fff;
font-size: 20rpx;
border-radius: 20rpx;
padding: 2rpx 10rpx;
margin-left: 8rpx;
line-height: 1.2;
}
/* 会话列表 */
.conversation-list {
background: #fff;
}
.conversation-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.15s;
}
.conversation-item:active {
background: #f5f5f5;
}
.has-unread {
background: #fafafa;
}
.avatar {
width: 96rpx;
height: 96rpx;
border-radius: 12rpx;
margin-right: 20rpx;
background: #eee;
flex-shrink: 0;
}
.info {
flex: 1;
min-width: 0;
}
.top-line {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.name {
font-size: 30rpx;
color: #1a1a1a;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 22rpx;
color: #999;
flex-shrink: 0;
margin-left: 16rpx;
}
.bottom-line {
display: flex;
justify-content: space-between;
align-items: center;
}
.last-msg {
font-size: 26rpx;
color: #888;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unread-dot {
background: #fa5151;
color: #fff;
font-size: 20rpx;
border-radius: 50%;
min-width: 36rpx;
height: 36rpx;
line-height: 36rpx;
text-align: center;
margin-left: 12rpx;
flex-shrink: 0;
}
.load-tip {
text-align: center;
padding: 24rpx;
color: #999;
font-size: 24rpx;
}
.empty {
text-align: center;
padding: 200rpx 0;
color: #bbb;
font-size: 28rpx;
}
/* 客服入口 */
.kefu-entry { text-align: center; padding: 100rpx 0; }
.kefu-btn { width: 300rpx; background: #07c160; color: #fff; border-radius: 40rpx; font-size: 28rpx; }
.kefu-entry {
text-align: center;
padding: 100rpx 0;
}
.kefu-btn {
width: 300rpx;
background: #07c160;
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
}
/* 操作弹窗 */
.action-mask { background: rgba(0,0,0,0.4); z-index: 10000; }
.action-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 24rpx 24rpx 0 0; padding: 20rpx 20rpx 150rpx 20rpx; }
.action-btn { text-align: center; padding: 28rpx 0; font-size: 32rpx; border-bottom: 1rpx solid #eee; }
.cancel { color: #999; margin-top: 12rpx; border-bottom: none; }
.action-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10000;
display: flex;
align-items: flex-end;
animation: fadeIn 0.2s ease-out;
}
.action-sheet {
width: 100%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 20rpx 20rpx 60rpx;
animation: slideInUp 0.25s ease-out;
}
.action-btn {
text-align: center;
padding: 28rpx 0;
font-size: 32rpx;
color: #333;
border-bottom: 1rpx solid #eee;
}
.action-btn:active {
background: #f5f5f5;
}
.cancel {
color: #999;
margin-top: 12rpx;
border-bottom: none;
}