diff --git a/app.js b/app.js
index 18fdaef..be37b63 100644
--- a/app.js
+++ b/app.js
@@ -7,6 +7,7 @@ import { setClubId, getConfiguredClubId, buildClubHeaders, getClubId } from './u
import { CLUB_ID, WX_APP_ID } from './config/club-config';
import { applyMiniappAssetsFromConfig, warmupPindaoConfig } from './utils/miniapp-icons.js';
import { refreshDashouMembership } from './utils/dashou-profile.js';
+const { getDefaultAvatarUrl, resolveAvatarFromStorage } = require('./utils/avatar.js');
// 三端固定首页
const roleDefaultPage = PRIMARY_DEFAULT_PAGES;
@@ -163,11 +164,21 @@ App({
debugMode: false,
currentUser: null,
currentRole: 'normal',
- primaryRole: 'normal'
- },
+ primaryRole: 'normal',
+ /** 客服浮钮:full | mini,onLaunch 重置为 full */
+ kefuFloatMode: 'full',
+ /** 本次启动内隐藏客服浮钮(不持久化,杀进程后恢复) */
+ kefuFloatSessionHidden: false,
+ kefuFloatY: null,
+ /** 从「我的」跳抢单页时高亮指定单 */
+ _acceptOrderHighlight: '',
+ },
// 核心启动流程
async onLaunch() {
+ this.globalData.kefuFloatMode = 'full';
+ this.globalData.kefuFloatSessionHidden = false;
+ try { wx.removeStorageSync('kefuFloatPermanentHidden'); } catch (e) {}
setClubId(getConfiguredClubId(), this);
this.globalData.clubId = CLUB_ID;
// ① 隐藏返回首页按钮
@@ -258,8 +269,13 @@ App({
async onShow() {
if (!wx.getStorageSync('token')) return;
await ensurePhoneAuth({ redirect: true });
- if (this.globalData.chatEnabled && typeof this.ensureConnection === 'function') {
+ if (this.globalData.chatEnabled && typeof this.startImWhenReady === 'function') {
+ this.startImWhenReady();
+ } else if (this.globalData.chatEnabled && typeof this.ensureConnection === 'function') {
this.globalData.goEasyConnection.autoReconnect = true;
+ if (typeof this.restoreTabBarBadge === 'function') {
+ this.restoreTabBarBadge();
+ }
this.ensureConnection();
if (typeof this.syncConnectionStatus === 'function') {
setTimeout(() => this.syncConnectionStatus(), 800);
@@ -386,6 +402,13 @@ App({
} catch (e) {
console.warn('miniapp assets apply failed', e);
}
+
+ if (this.emitEvent) {
+ this.emitEvent('configApplied', {
+ ossImageUrl: this.globalData.ossImageUrl,
+ morentouxiang: this.globalData.morentouxiang,
+ });
+ }
},
// 仅在有效 appkey 时初始化
@@ -422,10 +445,12 @@ App({
initCurrentUser() {
const uid = wx.getStorageSync('uid');
if (uid) {
+ const def = getDefaultAvatarUrl(this);
+ const avatar = resolveAvatarFromStorage(this) || def;
this.globalData.currentUser = {
id: uid,
- name: '用户' + uid.substring(0, 6),
- avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang
+ name: wx.getStorageSync('nicheng') || ('用户' + uid.substring(0, 6)),
+ avatar: avatar || def,
};
}
},
diff --git a/components/chenghao-tag/chenghao-tag.js b/components/chenghao-tag/chenghao-tag.js
index 2edff62..37fcf1d 100644
--- a/components/chenghao-tag/chenghao-tag.js
+++ b/components/chenghao-tag/chenghao-tag.js
@@ -6,7 +6,7 @@ const PILL_SHAPES = ['pill', 'rectangle', 'rounded'];
Component({
properties: {
mingcheng: { type: String, value: '' },
- texiaoJson: { type: Object, value: {} },
+ texiaoJson: { type: null, value: null },
},
data: {
inlineStyle: '',
@@ -29,7 +29,20 @@ Component({
},
methods: {
_applyConfig(raw) {
- const cfg = raw || {};
+ let cfg = raw || {};
+ if (typeof cfg === 'string') {
+ try {
+ cfg = JSON.parse(cfg);
+ } catch (e) {
+ cfg = {};
+ }
+ }
+ if (Array.isArray(cfg)) {
+ cfg = cfg[0] || {};
+ }
+ if (!cfg || typeof cfg !== 'object') {
+ cfg = {};
+ }
const ossImageUrl = app.globalData.ossImageUrl || '';
// 无 shape:身份装饰标签 / 自定义色圆角标;有 shape:考核称号等特殊形状
diff --git a/components/global-notification/global-notification.js b/components/global-notification/global-notification.js
index 29820d6..139fce1 100644
--- a/components/global-notification/global-notification.js
+++ b/components/global-notification/global-notification.js
@@ -1,4 +1,4 @@
-const { resolveAvatarUrl } = require('../../utils/avatar.js');
+const { resolveAvatarUrl, getDefaultAvatarUrl } = require('../../utils/avatar.js');
Component({
properties: {
@@ -53,7 +53,8 @@ Component({
// 静音状态
isMuted: false,
-
+ formattedTime: '',
+
// 自动隐藏计时器
autoHideTimeout: null
},
@@ -123,7 +124,7 @@ Component({
let avatar = resolveAvatarUrl(
data.avatar || data.message?.senderData?.avatar,
app
- );
+ ) || getDefaultAvatarUrl(app);
this.setData({
show: true,
@@ -131,6 +132,7 @@ Component({
message: data.content || '[消息内容]',
avatar: avatar,
notificationData: data,
+ formattedTime: this.formatTime(data.timestamp),
progress: 100,
swipingClass: '',
swipeStyle: ''
@@ -149,6 +151,7 @@ Component({
this.clearTimers();
this.setData({
show: false,
+ formattedTime: '',
progress: 100,
swipingClass: '',
swipeStyle: ''
@@ -265,6 +268,13 @@ Component({
this.triggerEvent('close');
this.hideNotification();
},
+
+ onAvatarError() {
+ const def = getDefaultAvatarUrl(getApp());
+ if (def && this.data.avatar !== def) {
+ this.setData({ avatar: def });
+ }
+ },
onMute(e) {
e.stopPropagation();
diff --git a/components/global-notification/global-notification.wxml b/components/global-notification/global-notification.wxml
index d76b58c..3f7cea1 100644
--- a/components/global-notification/global-notification.wxml
+++ b/components/global-notification/global-notification.wxml
@@ -15,14 +15,15 @@
-
+
+ 新消息
{{title}}
{{message}}
-
- {{formatTime(notificationData ? notificationData.timestamp : '')}}
+
+ {{formattedTime}}
diff --git a/components/global-notification/global-notification.wxss b/components/global-notification/global-notification.wxss
index fc67be6..80e8f3a 100644
--- a/components/global-notification/global-notification.wxss
+++ b/components/global-notification/global-notification.wxss
@@ -1,29 +1,40 @@
.global-notification {
position: fixed;
- left: 20rpx;
- right: 20rpx;
- top: 0rpx;
+ left: 24rpx;
+ right: 24rpx;
+ top: calc(env(safe-area-inset-top) + 108rpx);
z-index: 99999;
opacity: 0;
- transform: translateY(-120%);
- transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+ transform: translateY(-160%);
+ transition: all 0.42s cubic-bezier(0.34, 1.2, 0.64, 1);
pointer-events: none;
overflow: hidden;
- border-radius: 24rpx;
- background: linear-gradient(135deg,
- rgba(41, 47, 61, 0.98) 0%,
- rgba(31, 36, 48, 0.98) 100%);
- backdrop-filter: blur(30rpx) saturate(180%);
- -webkit-backdrop-filter: blur(30rpx) saturate(180%);
- border: 1rpx solid rgba(255, 255, 255, 0.08);
- box-shadow:
- 0 20rpx 60rpx rgba(0, 0, 0, 0.4),
- 0 0 0 1rpx rgba(255, 255, 255, 0.03),
- inset 0 1rpx 0 rgba(255, 255, 255, 0.1);
- min-height: 140rpx;
+ border-radius: 28rpx;
+ background: linear-gradient(145deg,
+ rgba(28, 32, 42, 0.97) 0%,
+ rgba(22, 26, 36, 0.98) 100%);
+ backdrop-filter: blur(40rpx) saturate(180%);
+ -webkit-backdrop-filter: blur(40rpx) saturate(180%);
+ border: 1rpx solid rgba(255, 255, 255, 0.1);
+ box-shadow:
+ 0 24rpx 64rpx rgba(0, 0, 0, 0.45),
+ 0 0 0 1rpx rgba(255, 255, 255, 0.04),
+ inset 0 1rpx 0 rgba(255, 255, 255, 0.12);
+ min-height: 148rpx;
touch-action: pan-y;
}
+.global-notification::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 24rpx;
+ bottom: 24rpx;
+ width: 6rpx;
+ border-radius: 0 6rpx 6rpx 0;
+ background: linear-gradient(180deg, #07c160 0%, #D4AF37 100%);
+}
+
.global-notification.show {
opacity: 1;
transform: translateY(0);
@@ -58,8 +69,8 @@
.notification-content {
display: flex;
align-items: center;
- padding: 32rpx;
- min-height: 140rpx;
+ padding: 28rpx 28rpx 28rpx 36rpx;
+ min-height: 148rpx;
position: relative;
}
@@ -84,6 +95,18 @@
justify-content: center;
}
+.notification-tag {
+ display: inline-block;
+ align-self: flex-start;
+ font-size: 20rpx;
+ color: #07c160;
+ background: rgba(7, 193, 96, 0.15);
+ padding: 4rpx 14rpx;
+ border-radius: 20rpx;
+ margin-bottom: 8rpx;
+ font-weight: 500;
+}
+
.notification-title {
font-size: 34rpx;
font-weight: 600;
diff --git a/components/kefu-float/kefu-float.js b/components/kefu-float/kefu-float.js
new file mode 100644
index 0000000..e9dc0ee
--- /dev/null
+++ b/components/kefu-float/kefu-float.js
@@ -0,0 +1,126 @@
+import { openCustomerServiceChat } from '../../utils/kefu-nav.js';
+
+Component({
+ properties: {
+ bottom: { type: String, value: '200rpx' },
+ },
+
+ data: {
+ mode: 'full',
+ sessionHidden: false,
+ csUnread: 0,
+ floatY: 520,
+ },
+
+ lifetimes: {
+ attached() {
+ this._syncFromGlobal();
+ this._initFloatY();
+ this._convHandler = () => this.syncCsUnread();
+ const app = getApp();
+ if (app.on) app.on('conversationsUpdated', this._convHandler);
+ if (app.on) app.on('unreadCountChanged', this._convHandler);
+ this.syncCsUnread();
+ },
+ detached() {
+ const app = getApp();
+ if (this._convHandler && app.off) {
+ app.off('conversationsUpdated', this._convHandler);
+ app.off('unreadCountChanged', this._convHandler);
+ }
+ },
+ },
+
+ pageLifetimes: {
+ show() {
+ this._syncFromGlobal();
+ this.syncCsUnread();
+ },
+ },
+
+ methods: {
+ _initFloatY() {
+ const app = getApp();
+ if (app.globalData.kefuFloatY != null) {
+ this.setData({ floatY: app.globalData.kefuFloatY });
+ return;
+ }
+ try {
+ const sys = wx.getSystemInfoSync();
+ const defaultY = Math.floor((sys.windowHeight || 600) * 0.55);
+ this.setData({ floatY: defaultY });
+ } catch (e) {
+ this.setData({ floatY: 520 });
+ }
+ },
+
+ _syncFromGlobal() {
+ const app = getApp();
+ const sessionHidden = !!app.globalData.kefuFloatSessionHidden;
+ const mode = app.globalData.kefuFloatMode || 'full';
+ this.setData({ sessionHidden, mode });
+ },
+
+ onFloatMove(e) {
+ const y = e.detail && e.detail.y;
+ if (y == null) return;
+ const app = getApp();
+ app.globalData.kefuFloatY = y;
+ },
+
+ syncCsUnread() {
+ if (this.data.sessionHidden) return;
+ if (!wx.goEasy?.im?.latestConversations) return;
+ wx.goEasy.im.latestConversations({
+ onSuccess: (res) => {
+ const list = res.content?.conversations || res.conversations || [];
+ let unread = 0;
+ list.forEach((c) => {
+ if (c.type === 'cs' || c.teamId === 'support_team') {
+ unread += c.unread || 0;
+ }
+ });
+ if (unread !== this.data.csUnread) {
+ this.setData({ csUnread: unread });
+ }
+ },
+ onFailed: () => {},
+ });
+ },
+
+ onContact() {
+ openCustomerServiceChat();
+ },
+
+ onHideTap(e) {
+ if (e && e.stopPropagation) e.stopPropagation();
+ const app = getApp();
+ app.globalData.kefuFloatMode = 'mini';
+ this.setData({ mode: 'mini' });
+ },
+
+ onExpand(e) {
+ if (e && e.stopPropagation) e.stopPropagation();
+ const app = getApp();
+ app.globalData.kefuFloatMode = 'full';
+ this.setData({ mode: 'full' });
+ },
+
+ onSessionDismiss(e) {
+ if (e && e.stopPropagation) e.stopPropagation();
+ wx.showModal({
+ title: '隐藏联系客服',
+ content: '本次使用期间不再显示此按钮。清空后台重新进入小程序后会再次出现。',
+ confirmText: '确定隐藏',
+ cancelText: '取消',
+ success: (res) => {
+ if (res.confirm) {
+ const app = getApp();
+ app.globalData.kefuFloatSessionHidden = true;
+ this.setData({ sessionHidden: true });
+ }
+ },
+ });
+ },
+ },
+});
diff --git a/components/kefu-float/kefu-float.json b/components/kefu-float/kefu-float.json
new file mode 100644
index 0000000..a89ef4d
--- /dev/null
+++ b/components/kefu-float/kefu-float.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
diff --git a/components/kefu-float/kefu-float.wxml b/components/kefu-float/kefu-float.wxml
new file mode 100644
index 0000000..48ce5ee
--- /dev/null
+++ b/components/kefu-float/kefu-float.wxml
@@ -0,0 +1,47 @@
+
+
+
+
+
+ {{csUnread > 99 ? '99+' : csUnread}}
+
+ 💬
+
+ 联系客服
+ 客服有新消息
+ 咨询订单 / 账号问题
+
+
+
+ ×
+
+
+
+ 本次不再显示
+ ×
+
+
+
+
+
+
+
+ {{csUnread > 99 ? '99+' : csUnread}}
+ 💬
+ 联系客服
+
+
+ ×
+ 本次隐藏
+
+
+
+
+
diff --git a/components/kefu-float/kefu-float.wxss b/components/kefu-float/kefu-float.wxss
new file mode 100644
index 0000000..852b86e
--- /dev/null
+++ b/components/kefu-float/kefu-float.wxss
@@ -0,0 +1,179 @@
+.kefu-movable-area {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 900;
+}
+
+.kefu-movable-view {
+ width: auto;
+ height: auto;
+ pointer-events: auto;
+ position: absolute;
+ right: 24rpx;
+ top: 0;
+}
+
+.kefu-wrap {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ pointer-events: auto;
+}
+
+.kefu-float {
+ position: relative;
+}
+
+.kefu-float--full {
+ display: flex;
+ align-items: stretch;
+ background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
+ border-radius: 20rpx;
+ box-shadow: 0 8rpx 28rpx rgba(7, 193, 96, 0.35);
+ overflow: visible;
+}
+
+.kefu-unread-badge {
+ position: absolute;
+ top: -12rpx;
+ right: -8rpx;
+ min-width: 36rpx;
+ height: 36rpx;
+ line-height: 36rpx;
+ padding: 0 8rpx;
+ background: #fa5151;
+ color: #fff;
+ font-size: 20rpx;
+ text-align: center;
+ border-radius: 18rpx;
+ border: 2rpx solid #fff;
+ z-index: 2;
+}
+
+.kefu-unread-badge--mini {
+ top: -8rpx;
+ right: -4rpx;
+}
+
+.kefu-float-main {
+ display: flex;
+ align-items: center;
+ padding: 20rpx 24rpx;
+ flex: 1;
+}
+
+.kefu-float-main:active {
+ opacity: 0.88;
+}
+
+.kefu-float-icon {
+ font-size: 36rpx;
+ margin-right: 14rpx;
+ flex-shrink: 0;
+}
+
+.kefu-text-col {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+}
+
+.kefu-float-text {
+ font-size: 28rpx;
+ color: #fff;
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.kefu-float-sub {
+ font-size: 20rpx;
+ color: rgba(255, 255, 255, 0.95);
+ margin-top: 4rpx;
+}
+
+.kefu-float-sub--muted {
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.kefu-float-close {
+ width: 64rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.12);
+ color: rgba(255, 255, 255, 0.95);
+ font-size: 38rpx;
+ line-height: 1;
+}
+
+.kefu-float-close:active {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.kefu-permanent-row {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ margin-top: 10rpx;
+ padding: 8rpx 12rpx;
+ background: rgba(255, 255, 255, 0.92);
+ border-radius: 24rpx;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+}
+
+.kefu-permanent-row:active {
+ opacity: 0.85;
+}
+
+.kefu-permanent-row--mini {
+ margin-top: 8rpx;
+ padding: 6rpx 14rpx;
+}
+
+.kefu-permanent-label {
+ font-size: 22rpx;
+ color: #888;
+ margin-right: 8rpx;
+}
+
+.kefu-permanent-x {
+ font-size: 28rpx;
+ color: #bbb;
+ line-height: 1;
+}
+
+.kefu-wrap--mini {
+ align-items: flex-end;
+}
+
+.kefu-float--mini {
+ width: auto;
+ min-width: 160rpx;
+ padding: 16rpx 24rpx;
+ border-radius: 48rpx;
+ background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
+ box-shadow: 0 6rpx 20rpx rgba(7, 193, 96, 0.3);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.kefu-float--mini:active {
+ transform: scale(0.96);
+}
+
+.kefu-float-mini-icon {
+ font-size: 36rpx;
+}
+
+.kefu-mini-label {
+ font-size: 22rpx;
+ color: #fff;
+ font-weight: 500;
+ margin-top: 4rpx;
+ white-space: nowrap;
+}
diff --git a/components/order-sender/order-sender.js b/components/order-sender/order-sender.js
index f4c9444..5e0ce70 100644
--- a/components/order-sender/order-sender.js
+++ b/components/order-sender/order-sender.js
@@ -1,16 +1,87 @@
+import request from '../../utils/request';
+
const app = getApp();
+function mapIdentityForApi() {
+ const role = app.globalData.currentRole || wx.getStorageSync('currentRole') || 'normal';
+ if (role === 'dashou') return 'dashou';
+ if (role === 'shangjia') return 'shangjia';
+ return 'normal';
+}
+
Component({
properties: {
- visible: Boolean,
- showDetail: { type: Boolean, value: false }
+ visible: { type: Boolean, value: false },
+ groupId: { type: String, value: '' },
+ orderId: { type: String, value: '' },
},
- data: {},
+ data: {
+ keyword: '',
+ loading: false,
+ orders: [],
+ filteredOrders: [],
+ },
+
+ observers: {
+ visible(v) {
+ if (v) {
+ this.setData({ keyword: '' });
+ this.loadOrders('');
+ }
+ },
+ },
methods: {
close() {
this.triggerEvent('close');
- }
- }
-});
\ No newline at end of file
+ },
+
+ onSearchInput(e) {
+ const keyword = (e.detail.value || '').trim();
+ this.setData({ keyword });
+ if (this._searchTimer) clearTimeout(this._searchTimer);
+ this._searchTimer = setTimeout(() => {
+ this.loadOrders(keyword);
+ }, 350);
+ },
+
+ async loadOrders(keyword) {
+ const { groupId, orderId } = this.properties;
+ if (!groupId && !orderId) return;
+
+ this.setData({ loading: true });
+ try {
+ const res = await request({
+ url: '/dingdan/ltpddd',
+ method: 'POST',
+ data: {
+ groupId,
+ dingdan_id: orderId,
+ keyword: keyword || '',
+ identityType: mapIdentityForApi(),
+ },
+ });
+ const body = res?.data || {};
+ const list = body.data?.list || [];
+ this.setData({
+ orders: list,
+ filteredOrders: list,
+ loading: false,
+ });
+ } catch (e) {
+ console.warn('加载历史订单失败', e);
+ this.setData({ loading: false, orders: [], filteredOrders: [] });
+ wx.showToast({ title: '加载订单失败', icon: 'none' });
+ }
+ },
+
+ onSelectOrder(e) {
+ const index = e.currentTarget.dataset.index;
+ const order = this.data.filteredOrders[index];
+ if (!order) return;
+ this.triggerEvent('send', { order });
+ this.close();
+ },
+ },
+});
diff --git a/components/order-sender/order-sender.wxml b/components/order-sender/order-sender.wxml
index ae53ed5..3e5dd08 100644
--- a/components/order-sender/order-sender.wxml
+++ b/components/order-sender/order-sender.wxml
@@ -1,12 +1,28 @@
-
- 📋
- 此功能暂未开放
- 敬请期待
+
+
-
\ No newline at end of file
+
+ 加载中...
+
+
+
+ {{item.dingdan_id}}
+ {{item.zhuangtaiText}}
+
+ {{item.jieshao || '暂无介绍'}}
+
+ ¥{{item.jine || '0'}}
+ {{item.create_time}}
+
+ 点击发送订单卡片
+
+
+ 暂无相关订单
+
+
diff --git a/components/order-sender/order-sender.wxss b/components/order-sender/order-sender.wxss
index c0ffc48..304f2cd 100644
--- a/components/order-sender/order-sender.wxss
+++ b/components/order-sender/order-sender.wxss
@@ -1,45 +1,140 @@
.order-sender-mask {
- position: fixed; top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0,0,0,0.5);
- z-index: 2000;
- }
- .order-sender {
- position: fixed; bottom: 0; left: 0; right: 0;
- height: 360rpx;
- background: #fff;
- border-radius: 24rpx 24rpx 0 0;
- z-index: 2001;
- transform: translateY(100%);
- transition: 0.25s;
- display: flex;
- flex-direction: column;
- }
- .order-sender.show { transform: translateY(0); }
-
- .header {
- display: flex; justify-content: space-between;
- padding: 24rpx 30rpx;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .title { font-size: 32rpx; font-weight: 600; }
- .close { font-size: 44rpx; color: #999; padding: 0 12rpx; }
-
- .content {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding-bottom: 40rpx;
- }
- .icon { font-size: 80rpx; margin-bottom: 20rpx; }
- .notice {
- font-size: 34rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 10rpx;
- }
- .sub {
- font-size: 24rpx;
- color: #999;
- }
\ No newline at end of file
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 2000;
+}
+
+.order-sender {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 72vh;
+ max-height: 900rpx;
+ background: #fff;
+ border-radius: 24rpx 24rpx 0 0;
+ z-index: 2001;
+ transform: translateY(100%);
+ transition: transform 0.25s;
+ display: flex;
+ flex-direction: column;
+}
+
+.order-sender.show {
+ transform: translateY(0);
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 24rpx 30rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+ flex-shrink: 0;
+}
+
+.title {
+ font-size: 32rpx;
+ font-weight: 600;
+}
+
+.close {
+ font-size: 44rpx;
+ color: #999;
+ padding: 0 12rpx;
+}
+
+.search-row {
+ padding: 16rpx 24rpx;
+ flex-shrink: 0;
+}
+
+.search-input {
+ background: #f5f5f5;
+ border-radius: 32rpx;
+ padding: 16rpx 28rpx;
+ font-size: 28rpx;
+}
+
+.order-list {
+ flex: 1;
+ height: 0;
+ padding: 0 24rpx 24rpx;
+ box-sizing: border-box;
+}
+
+.loading-tip,
+.empty-tip {
+ text-align: center;
+ color: #999;
+ font-size: 28rpx;
+ padding: 60rpx 0;
+}
+
+.order-item {
+ background: #fafafa;
+ border-radius: 16rpx;
+ padding: 20rpx 24rpx;
+ margin-bottom: 16rpx;
+ border: 1rpx solid #eee;
+}
+
+.order-item:active {
+ background: #f0f7ff;
+}
+
+.order-item-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8rpx;
+}
+
+.order-id {
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #333;
+}
+
+.order-status {
+ font-size: 22rpx;
+ color: #07c160;
+ background: #e8f8ee;
+ padding: 4rpx 14rpx;
+ border-radius: 20rpx;
+}
+
+.order-desc {
+ font-size: 26rpx;
+ color: #666;
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ margin-bottom: 8rpx;
+}
+
+.order-item-bottom {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.order-price {
+ font-size: 28rpx;
+ color: #ff6b00;
+ font-weight: 600;
+}
+
+.order-time {
+ font-size: 22rpx;
+ color: #aaa;
+}
+
+.order-send-hint {
+ display: block;
+ font-size: 22rpx;
+ color: #007aff;
+ margin-top: 10rpx;
+}
diff --git a/components/pindao-modal/pindao-modal.wxml b/components/pindao-modal/pindao-modal.wxml
index 628c411..9e7d8a3 100644
--- a/components/pindao-modal/pindao-modal.wxml
+++ b/components/pindao-modal/pindao-modal.wxml
@@ -9,7 +9,7 @@
{{channelNo}}
- 暂无频道内容
+ 暂无频道内容
o.dingdan_id === highlight)) {
+ this.setData({ highlightOrderId: highlight });
+ this._pendingHighlight = '';
+ }
+ } catch (e) {
+ if (!silent) console.warn('loadMyZhidingOrders', e);
+ }
+ },
+
+ openZhidingDetail(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ this.setData({ showZhidingDetail: true, zhidingDetailOrder: item });
+ },
+
+ closeZhidingDetail() {
+ this.setData({ showZhidingDetail: false, zhidingDetailOrder: null });
+ },
+
+ async onZhidingReject(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ wx.showModal({
+ title: '确认婉拒',
+ content: '婉拒后订单将退回抢单大厅,确定不想接此单吗?',
+ confirmText: '不想接',
+ confirmColor: '#e64340',
+ success: async (res) => {
+ if (!res.confirm) return;
+ wx.showLoading({ title: '提交中...', mask: true });
+ try {
+ const body = await submitZhidingResponse(item.dingdan_id, 2);
+ wx.hideLoading();
+ if (body && (body.code === 200 || body.code === 0)) {
+ wx.showToast({ title: body.msg || '已婉拒', icon: 'none' });
+ this.closeZhidingDetail();
+ await this.loadMyZhidingOrders(true);
+ await this.loadDingdanList(true, true);
+ } else {
+ wx.showToast({ title: body?.msg || '操作失败', icon: 'none' });
+ }
+ } catch (err) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+ });
+ },
+
+ onZhidingLater(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ this.setData({
+ showZhidingDetail: false,
+ zhidingDetailOrder: null,
+ showLaterModal: true,
+ laterOrderId: item.dingdan_id,
+ laterNote: item.zhiding_dhj_sm || '',
+ });
+ },
+
+ closeLaterModal() {
+ this.setData({ showLaterModal: false, laterOrderId: '', laterNote: '' });
+ },
+
+ onLaterNoteInput(e) {
+ this.setData({ laterNote: e.detail.value });
+ },
+
+ async confirmZhidingLater() {
+ const { laterOrderId, laterNote } = this.data;
+ if (!laterNote.trim()) {
+ wx.showToast({ title: '请填写预计接待时间', icon: 'none' });
+ return;
+ }
+ wx.showLoading({ title: '提交中...', mask: true });
+ try {
+ const body = await submitZhidingResponse(laterOrderId, 3, laterNote.trim());
+ wx.hideLoading();
+ if (body && (body.code === 200 || body.code === 0)) {
+ const note = laterNote.trim();
+ wx.showToast({ title: `已记录:${note}`, icon: 'none', duration: 3500 });
+ this.closeLaterModal();
+ this.closeZhidingDetail();
+ await this.loadMyZhidingOrders(true);
+ await this.loadDingdanList(true, true);
+ } else {
+ wx.showToast({ title: body?.msg || '操作失败', icon: 'none' });
+ }
+ } catch (e) {
+ wx.hideLoading();
+ wx.showToast({ title: '网络错误', icon: 'none' });
+ }
+ },
+
+ async onZhidingAccept(e) {
+ const item = e.currentTarget.dataset.item;
+ if (!item) return;
+ wx.showLoading({ title: '处理中...', mask: true });
+ try {
+ await submitZhidingResponse(item.dingdan_id, 1);
+ wx.hideLoading();
+ } catch (e) {
+ wx.hideLoading();
+ }
+ const processed = this.processDingdanItem(item);
+ this.onQiangdanTap({ currentTarget: { dataset: { item: processed } } });
+ },
+
+ noop() {},
+
onReachBottom() {
if (this.data.isLoading || this.data.isLoadingMore) return;
if (this.data.hasMore && !this.data.isLoading && this.data.xuanzhongLeixingId) {
@@ -744,6 +902,7 @@ Page({
await this.loadBankuaiBiaoqian();
await this.loadDingdanList(true);
}
+ await this.loadMyZhidingOrders(true);
this.loadGlobalStatus();
this.persistPageCache(banner);
} catch (e) {
diff --git a/pages/accept-order/accept-order.json b/pages/accept-order/accept-order.json
index 024a2e6..fc97bc8 100644
--- a/pages/accept-order/accept-order.json
+++ b/pages/accept-order/accept-order.json
@@ -9,6 +9,7 @@
"backgroundColor": "#f5f3ff",
"usingComponents": {
"chenghao-tag": "/components/chenghao-tag/chenghao-tag",
- "global-notification": "/components/global-notification/global-notification"
+ "global-notification": "/components/global-notification/global-notification",
+ "kefu-float": "/components/kefu-float/kefu-float"
}
}
\ No newline at end of file
diff --git a/pages/accept-order/accept-order.wxml b/pages/accept-order/accept-order.wxml
index d0a2cc6..5bad74b 100644
--- a/pages/accept-order/accept-order.wxml
+++ b/pages/accept-order/accept-order.wxml
@@ -44,6 +44,32 @@
{{gonggao}}
+
+
+
+
+ 指定给您
+ {{myZhidingCount || myZhidingList.length}}单
+
+ 请确认是否接待
+
+
+
+
+ 单号 {{item.dingdan_id}}
+ ¥{{item.dashou_fencheng || 0}}
+
+ {{item.jieshao || '暂无介绍'}}
+ 您的回复:{{item.zhiding_hf_text}}{{item.zhiding_dhj_sm ? ' · ' + item.zhiding_dhj_sm : ''}}
+
+
+ 不想接
+ 等会接
+ 立即接待
+
+
+
+
@@ -182,7 +208,7 @@
-
+
@@ -212,7 +238,7 @@
指定
-
+
{{item.zhiding_nicheng || '指定接单员'}}
@@ -258,7 +284,7 @@
平台派单
- 抢单
+ {{item.isMyZhiding ? '立即接待' : '抢单'}}
@@ -271,7 +297,7 @@
-
+
@@ -292,7 +318,7 @@
指定
-
+
{{item.zhiding_nicheng || '指定接单员'}}
@@ -336,7 +362,7 @@
发布于 {{item.creat_time}}
- 抢单
+ {{item.isMyZhiding ? '立即接待' : '抢单'}}
@@ -70,8 +71,17 @@
+
+
+
+ 指定
+ {{myZhidingCount}}单待接待
+
+ 去处理 ›
+
+
-
+
@@ -253,6 +263,7 @@
+
diff --git a/pages/fighter/fighter.wxss b/pages/fighter/fighter.wxss
index b70fa4f..29ede51 100644
--- a/pages/fighter/fighter.wxss
+++ b/pages/fighter/fighter.wxss
@@ -652,3 +652,39 @@ page {
@keyframes fighterSpin {
to { transform: rotate(360deg); }
}
+
+.my-zhiding-mine-strip {
+ margin: 0 24rpx 16rpx;
+ padding: 20rpx 24rpx;
+ border-radius: 16rpx;
+ background: linear-gradient(90deg, #ff6b00, #ff4500);
+ border: 2rpx solid #ff3300;
+ box-shadow: 0 8rpx 24rpx rgba(255, 69, 0, 0.4);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.my-zhiding-mine-strip:active { opacity: 0.92; }
+.my-zhiding-mine-strip-left {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+}
+.my-zhiding-mine-badge {
+ font-size: 22rpx;
+ font-weight: 800;
+ color: #ff4500;
+ background: #fff;
+ padding: 4rpx 14rpx;
+ border-radius: 999rpx;
+}
+.my-zhiding-mine-count {
+ font-size: 28rpx;
+ color: #fff;
+ font-weight: 700;
+}
+.my-zhiding-mine-arrow {
+ font-size: 26rpx;
+ color: #fff;
+ font-weight: 700;
+}
diff --git a/pages/group-chat/group-chat.js b/pages/group-chat/group-chat.js
index 348cae3..6930909 100644
--- a/pages/group-chat/group-chat.js
+++ b/pages/group-chat/group-chat.js
@@ -1,10 +1,16 @@
const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { sendGroupMessage } from '../../utils/message-sender';
-import { showConfirmByScene } from '../../utils/scriptService.js';
-import { normalizeGroupMessage, getZhuangtaiText } from '../../utils/group-chat.js';
-import { persistGroupMeta } from '../../utils/im-user.js';
-import { getFreshImUser } from '../../utils/im-user.js';
+import { getLocalImUserId } from '../../utils/im-user.js';
+import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
+import { getZhuangtaiText, isPairGroupId, normalizeGroupMessage, recordConversationOpen } from '../../utils/group-chat.js';
+import request from '../../utils/request';
+import {
+ fetchHistoryMessages,
+ getOldestHistoryTimestamp,
+ mergeHistoryMessages,
+ waitImConnected,
+} from '../../utils/chat-history.js';
Page({
data: {
@@ -15,8 +21,8 @@ Page({
isCross: 0,
orderZhuangtai: null,
orderZhuangtaiText: '',
- orderJine: '',
orderJieshao: '',
+ orderJine: '',
currentUser: null,
messages: [],
inputText: '',
@@ -34,8 +40,10 @@ Page({
lastTapMsgId: '',
emojiList: ['😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','😗','😙','😚','🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹','🙁','😖','😞','😟','😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','🥵','🥶','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾'],
pendingImage: '',
+ pendingImageFile: null,
keyboardHeight: 0,
- bottomSafeHeight: 10
+ bottomSafeHeight: 140,
+ defaultAvatarUrl: '',
},
onLoad(options) {
let p = { groupId: '', groupName: '订单群聊', groupAvatar: '', orderId: '', isCross: 0,
@@ -46,182 +54,278 @@ Page({
p = { ...p, ...d };
} catch (e) {}
}
+ const orderIdStr = String(p.orderId || '').replace(/^group_/, '') ||
+ (p.groupId && !isPairGroupId(p.groupId) ? String(p.groupId).replace(/^group_/, '') : '');
+ const groupId = p.groupId
+ ? String(p.groupId)
+ : (orderIdStr ? `group_${orderIdStr}` : '');
+ const zt = p.orderZhuangtai;
+ const defAvatar = getDefaultAvatarUrl(app);
this.setData({
- groupId: p.groupId,
+ groupId,
groupName: p.groupName,
- groupAvatar: p.groupAvatar,
- orderId: p.orderId,
- isCross: p.isCross
+ groupAvatar: resolveAvatarUrl(p.groupAvatar, app) || defAvatar,
+ orderId: orderIdStr,
+ isCross: p.isCross,
+ orderJieshao: p.orderDesc || p.orderJieshao || '',
+ orderJine: p.orderJine || '',
+ orderZhuangtai: zt,
+ orderZhuangtaiText: zt != null ? getZhuangtaiText(zt) : '',
+ defaultAvatarUrl: defAvatar,
});
wx.setNavigationBarTitle({ title: this.data.groupName });
- // 如果有外部传入的 currentUser 信息,直接用,不再依赖全局状态
- if (p.currentUserId) {
- this.setData({
- currentUser: {
- id: p.currentUserId,
- name: p.currentUserName || `用户${p.currentUserId.replace(/^[A-Za-z]+/,'')}`,
- avatar: p.currentUserAvatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
- }
- });
- } else {
+ this.ensureCurrentUser(p);
+ },
+
+ ensureCurrentUser(p) {
+ p = p || {};
+ const localImId = getLocalImUserId(app);
+ const uid = wx.getStorageSync('uid') || '';
+ const userId = localImId || p.currentUserId || '';
+ if (!userId) {
this.initCurrentUser();
- }
-
- const meta = (app.globalData.groupInfoMap && app.globalData.groupInfoMap[p.groupId]) || {};
- const initZt = p.orderZhuangtai != null ? p.orderZhuangtai : meta.orderZhuangtai;
- if (initZt != null) {
- this.applyOrderStatus(initZt, {
- orderId: p.orderId || meta.orderId,
- jine: p.orderJine || meta.orderJine,
- jieshao: p.orderDesc || meta.orderDesc,
- });
- }
- },
-
- onShow() {
- app.globalData.pageState.isInChatPage = true;
- app.globalData.pageState.currentChatId = this.data.groupId;
- app.globalData.pageState.isInGroupChat = true;
- this.ensureChatConnection();
- },
-
- onHide() {
- app.globalData.pageState.isInChatPage = false;
- app.globalData.pageState.currentChatId = '';
- app.globalData.pageState.isInGroupChat = false;
- this.clearPageListeners();
- },
-
- applyOrderStatus(zhuangtai, extra) {
- if (zhuangtai == null && zhuangtai !== 0) return;
- const patch = {
- orderZhuangtai: zhuangtai,
- orderZhuangtaiText: getZhuangtaiText(zhuangtai),
- };
- if (extra) {
- if (extra.jine) patch.orderJine = extra.jine;
- if (extra.jieshao) patch.orderJieshao = extra.jieshao;
- if (extra.orderId) patch.orderId = extra.orderId;
- }
- this.setData(patch);
-
- const groupId = this.data.groupId;
- if (!groupId) return;
- if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
- const meta = { ...(app.globalData.groupInfoMap[groupId] || {}), orderZhuangtai: zhuangtai };
- if (extra && extra.orderId) meta.orderId = extra.orderId;
- app.globalData.groupInfoMap[groupId] = meta;
- persistGroupMeta(app, groupId, meta);
- },
-
- applyOrderCardPayload(payload) {
- if (!payload) return;
- const cardOrderId = payload.orderId || payload.dingdan_id;
- if (cardOrderId && this.data.orderId && cardOrderId !== this.data.orderId) return;
- const zt = payload.zhuangtai != null ? payload.zhuangtai : this.data.orderZhuangtai;
- payload.zhuangtaiText = getZhuangtaiText(zt);
- this.applyOrderStatus(zt, {
- orderId: payload.orderId || payload.dingdan_id || this.data.orderId,
- jine: payload.jine,
- jieshao: payload.jieshao,
- });
- this.updateOrderBubblesInList(payload);
- },
-
- updateOrderBubblesInList(payload) {
- if (!payload) return;
- const orderId = payload.orderId || payload.dingdan_id;
- if (!orderId) return;
- const messages = this.data.messages.map((m) => {
- if (m.type !== 'order' || !m.payload) return m;
- const oid = m.payload.orderId || m.payload.dingdan_id;
- if (oid !== orderId) return m;
- return { ...m, payload: { ...m.payload, ...payload, orderId: oid } };
- });
- this.setData({ messages });
- },
-
- mergeServerMessage(localId, status, serverMsg) {
- if (!serverMsg) {
- this.updateMsg(localId, status);
return;
}
- normalizeGroupMessage(serverMsg);
- serverMsg.formattedTime = formatDate(serverMsg.timestamp);
- if (serverMsg.type === 'order') {
- this.applyOrderCardPayload(serverMsg.payload);
+ const defAvatar = getDefaultAvatarUrl(app);
+ const rawAvatar = p.currentUserAvatar
+ || wx.getStorageSync('touxiang')
+ || app.globalData.currentUser?.avatar
+ || '';
+ const name = p.currentUserName
+ || app.globalData.currentUser?.name
+ || wx.getStorageSync('nicheng')
+ || app.globalData.dashouNicheng
+ || app.globalData.nicheng
+ || `用户${uid}`;
+ const avatar = resolveAvatarUrl(rawAvatar, app) || defAvatar;
+ const currentUser = { id: userId, name, avatar: avatar || defAvatar };
+ app.globalData.currentUser = { ...app.globalData.currentUser, ...currentUser };
+ this.setData({ currentUser });
+ },
+
+ getMyChatProfile() {
+ const cu = this.data.currentUser;
+ const defAvatar = getDefaultAvatarUrl(app);
+ if (cu && cu.id) {
+ return {
+ id: cu.id,
+ name: cu.name || `用户${wx.getStorageSync('uid') || ''}`,
+ avatar: resolveAvatarUrl(cu.avatar, app) || defAvatar,
+ };
+ }
+ const imId = getLocalImUserId(app);
+ const uid = wx.getStorageSync('uid') || '';
+ return {
+ id: imId || '',
+ name: wx.getStorageSync('nicheng') || app.globalData.nicheng || `用户${uid}`,
+ avatar: resolveAvatarFromStorage(app) || defAvatar,
+ };
+ },
+
+ initCurrentUser() {
+ const profile = this.getMyChatProfile();
+ if (!profile.id) return;
+ this.setData({ currentUser: profile });
+ app.globalData.currentUser = { ...app.globalData.currentUser, ...profile };
+ },
+
+ buildImageFile(tempPath, size) {
+ return {
+ path: tempPath,
+ tempFilePath: tempPath,
+ size: size || 0,
+ };
+ },
+
+ normalizeMessage(msg) {
+ if (!msg) return msg;
+ msg = normalizeGroupMessage(msg);
+ if (!msg.payload) msg.payload = {};
+ const defAvatar = getDefaultAvatarUrl(app);
+ const me = this.getMyChatProfile();
+ if (me.id && msg.senderId === me.id) {
+ msg.senderData = {
+ name: me.name,
+ avatar: me.avatar || defAvatar,
+ };
+ } else if (msg.senderData?.avatar) {
+ msg.senderData.avatar = resolveAvatarUrl(msg.senderData.avatar, app) || defAvatar;
+ } else if (msg.senderData) {
+ msg.senderData.avatar = defAvatar;
+ }
+ if (msg.type === 'image' && msg.payload?.url && !String(msg.payload.url).startsWith('http')) {
+ msg.payload.url = resolveAvatarUrl(msg.payload.url, app) || msg.payload.url;
+ }
+ return msg;
+ },
+
+ mapIdentityForApi() {
+ const role = app.globalData.currentRole || wx.getStorageSync('currentRole') || 'normal';
+ if (role === 'dashou') return 'dashou';
+ if (role === 'shangjia') return 'shangjia';
+ return 'normal';
+ },
+
+ async refreshLatestOrderBar() {
+ const { groupId, orderId } = this.data;
+ if (!groupId && !orderId) return;
+ try {
+ const res = await request({
+ url: '/dingdan/ltpddd',
+ method: 'POST',
+ data: {
+ groupId,
+ dingdan_id: orderId,
+ identityType: this.mapIdentityForApi(),
+ },
+ });
+ const body = res?.data || {};
+ const latest = body.data?.latest;
+ if (!latest) return;
+ this.setData({
+ orderId: latest.dingdan_id,
+ orderJieshao: latest.jieshao || '',
+ orderJine: latest.jine || '',
+ orderZhuangtai: latest.zhuangtai,
+ orderZhuangtaiText: getZhuangtaiText(latest.zhuangtai),
+ });
+ } catch (e) {
+ console.warn('刷新最新订单条失败', e);
+ }
+ },
+
+ async refreshOrderStatus() {
+ const { orderId } = this.data;
+ if (!orderId) return;
+ try {
+ const res = await request({
+ url: '/dingdan/ltddzt',
+ method: 'POST',
+ data: {
+ dingdan_id: orderId,
+ identityType: this.mapIdentityForApi(),
+ },
+ });
+ const body = res?.data || {};
+ const st = body.data;
+ if (!st) return;
+ this.setData({
+ orderJieshao: st.jieshao || this.data.orderJieshao,
+ orderJine: st.jine || this.data.orderJine,
+ orderZhuangtai: st.zhuangtai,
+ orderZhuangtaiText: getZhuangtaiText(st.zhuangtai),
+ });
+ } catch (e) {
+ console.warn('刷新订单状态失败', e);
+ }
+ },
+
+ startOrderStatusPoll() {
+ this.stopOrderStatusPoll();
+ this._orderPollTimer = setInterval(() => this.refreshOrderStatus(), 15000);
+ },
+
+ stopOrderStatusPoll() {
+ if (this._orderPollTimer) {
+ clearInterval(this._orderPollTimer);
+ this._orderPollTimer = null;
+ }
+ },
+
+ mergeSentMessage(localId, sent) {
+ if (!sent) {
+ this.updateMsg(localId, 'success');
+ return;
}
const msgs = this.data.messages.map((m) => {
if (m.messageId !== localId) return m;
- return {
- ...serverMsg,
- showTime: m.showTime,
- status: status || 'success',
- formattedTime: serverMsg.formattedTime || m.formattedTime,
- };
+ return this.normalizeMessage({
+ ...m,
+ ...sent,
+ status: 'success',
+ messageId: sent.messageId || m.messageId,
+ payload: sent.payload || m.payload,
+ senderData: m.senderData || sent.senderData,
+ });
});
this.setData({ messages: msgs });
},
- async ensureChatConnection() {
- const role = app.globalData.currentRole || 'normal';
- const uid = wx.getStorageSync('uid');
- const prefixMap = { normal:'Boss', dashou:'Ds', shangjia:'Sj', guanshi:'Gs', zuzhang:'Zz', kaoheguan:'Kh' };
- const pageUserId = this.data.currentUser?.id;
- const targetUserId = pageUserId || (prefixMap[role] || 'Boss') + uid;
-
- const onReady = () => {
- this.subscribeGroupIfNeeded()
- .then(() => {
- this.setupAllListeners();
- return this.loadHistory(true);
- })
- .then(() => this.markGroupMessageAsRead());
- };
-
- const connectedId = app.globalData?.goEasyConnection?.userId || wx.goEasy?.im?.userId;
- const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
- const imOk = status === 'connected' || status === 'reconnected';
-
- if (imOk && connectedId === targetUserId) {
- onReady();
- return;
- }
-
- try {
- if (app.ensureImForRole) {
- await app.ensureImForRole(role);
- } else if (app.connectWithIdentity) {
- await app.connectWithIdentity(role, targetUserId, true);
- }
- onReady();
- } catch (e) {
- const s = wx.goEasy?.getConnectionStatus?.();
- if (s === 'connected' || s === 'reconnected') onReady();
- else wx.showToast({ title: '连接失败,请重试', icon: 'none' });
+ onShow() {
+ const defAvatar = getDefaultAvatarUrl(app);
+ this.setData({
+ defaultAvatarUrl: defAvatar,
+ groupAvatar: resolveAvatarUrl(this.data.groupAvatar, app) || defAvatar,
+ });
+ this.ensureCurrentUser({});
+ if (this.data.groupId) {
+ if (!app.globalData.pageState) app.globalData.pageState = {};
+ app.globalData.pageState.lastOpenedConvId = this.data.groupId;
+ recordConversationOpen({ groupId: this.data.groupId });
}
+ this.refreshLatestOrderBar();
+ this.startOrderStatusPoll();
+ this.autoConnect();
},
- clearPageListeners() {
+ onHide() {
+ if (this.data.groupId) {
+ recordConversationOpen({ groupId: this.data.groupId });
+ }
+ this.stopOrderStatusPoll();
this.clearAllListeners();
},
+ onUnload() {
+ this.stopOrderStatusPoll();
+ },
+
+ autoConnect() {
+ const targetUserId = this.data.currentUser?.id || getLocalImUserId(app);
+ if (!targetUserId) return;
+
+ const afterReady = () => {
+ this.subscribeGroupIfNeeded().then(() => {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ this.markGroupMessageAsRead();
+ });
+ };
+
+ if (app.ensureConnection) {
+ app.ensureConnection();
+ setTimeout(afterReady, 400);
+ return;
+ }
+
+ 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) {
+ afterReady();
+ return;
+ }
+ wx.goEasy.disconnect();
+ }
+ this.connectGoEasy(targetUserId);
+ },
+
connectGoEasy(userId) {
- const { currentUser } = this.data;
- if (!currentUser) return;
+ const me = this.getMyChatProfile();
+ if (!me.id) return;
+ const defAvatar = getDefaultAvatarUrl(app);
wx.goEasy.connect({
id: userId,
data: {
- name: currentUser.name,
- avatar: currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang)
+ name: me.name,
+ avatar: me.avatar || defAvatar,
},
onSuccess: () => {
- this.subscribeGroupIfNeeded()
- .then(() => {
- this.setupAllListeners();
- return this.loadHistory(true);
- })
- .then(() => this.markGroupMessageAsRead());
+ this.subscribeGroupIfNeeded().then(() => {
+ this.setupAllListeners();
+ this.loadHistory(true);
+ this.markGroupMessageAsRead();
+ });
},
onFailed: (error) => {
if (error.code === 408) {
@@ -238,17 +342,14 @@ Page({
subscribeGroupIfNeeded() {
const { groupId } = this.data;
- if (!groupId || !wx.goEasy?.im) return Promise.resolve();
+ if (!groupId || !wx.goEasy?.im?.subscribeGroup) return Promise.resolve();
return new Promise((resolve) => {
wx.goEasy.im.subscribeGroup({
- groupIds: [groupId],
- onSuccess: () => {
- console.log(`[群聊页] 订阅成功: ${groupId}`);
- resolve();
- },
- onFailed: (err) => {
- console.error(`[群聊页] 订阅失败: ${groupId}`, err);
- resolve();
+ groupIds: [String(groupId)],
+ onSuccess: () => resolve(true),
+ onFailed: (error) => {
+ console.warn('subscribeGroup failed', error);
+ resolve(false);
},
});
});
@@ -267,23 +368,6 @@ Page({
if (this._deleteHandler) { wx.goEasy.im.off(wx.GoEasy.IM_EVENT.MESSAGE_DELETED, this._deleteHandler); this._deleteHandler = null; }
},
- initCurrentUser() {
- const uid = wx.getStorageSync('uid');
- const role = app.globalData.currentRole || 'normal';
- const fresh = getFreshImUser(app, role, uid);
- this.setData({ currentUser: {
- id: fresh.id,
- name: fresh.name,
- avatar: fresh.avatar,
- }});
- },
-
- fixAvatar(url) {
- if (!url) return app.globalData.ossImageUrl + app.globalData.morentouxiang;
- if (url.startsWith('http')) return url;
- return app.globalData.ossImageUrl + url;
- },
-
isConnected() {
const s = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
return s === 'connected' || s === 'reconnected';
@@ -291,78 +375,110 @@ Page({
onPullDownRefresh() {
if (this.data.loadingHistory) return;
+ if (!this.data.hasMore) {
+ wx.showToast({ title: '没有更早的消息了', icon: 'none' });
+ this.setData({ isRefreshing: false });
+ wx.stopPullDownRefresh();
+ return;
+ }
this.setData({ isRefreshing: true });
- this.loadHistory(false).finally(() => { this.setData({ isRefreshing: false }); wx.stopPullDownRefresh(); });
+ this.loadHistory(false).finally(() => {
+ this.setData({ isRefreshing: false });
+ wx.stopPullDownRefresh();
+ });
},
- loadHistory(refresh) {
- if (!refresh && !this.data.hasMore) return Promise.resolve();
- return new Promise((resolve) => {
- this.setData({ loadingHistory: true });
- const { groupId, lastTimestamp } = this.data;
- const ts = refresh ? null : lastTimestamp;
- wx.goEasy.im.history({
- type: wx.GoEasy.IM_SCENE.GROUP,
- id: groupId,
- lastTimestamp: ts,
- limit: 20,
- onSuccess: (res) => {
- let list = res.content || [];
- list.sort((a, b) => a.timestamp - b.timestamp);
- list.forEach((m, idx) => {
- normalizeGroupMessage(m);
- m.formattedTime = formatDate(m.timestamp);
- if (idx === 0) m.showTime = true;
- else m.showTime = (m.timestamp - list[idx-1].timestamp) / 60000 > 5;
- if (!m.senderData) m.senderData = { name: '未知用户', avatar: '' };
- if (m.type === 'order' && m.payload) {
- this.applyOrderCardPayload(m.payload);
- }
- });
- let final = refresh ? list : [...list, ...this.data.messages];
- if (!refresh) {
- for (let i = 0; i < final.length; i++) {
- if (i === 0) final[i].showTime = true;
- else final[i].showTime = (final[i].timestamp - final[i-1].timestamp) / 60000 > 5;
- }
- }
- const ids = new Set();
- const unique = [];
- for (const m of final) { if (!ids.has(m.messageId)) { ids.add(m.messageId); unique.push(m); } }
- const update = {
- messages: unique,
- hasMore: list.length >= 20,
- lastTimestamp: unique.length ? unique[0].timestamp : lastTimestamp,
- loadingHistory: false
- };
- if (refresh) update.scrollToView = 'msg-bottom';
- this.setData(update);
- resolve();
- },
- onFailed: () => { this.setData({ loadingHistory: false }); resolve(); }
- });
+ mapHistoryMessage(m, idx, list) {
+ m = this.normalizeMessage(m);
+ m.formattedTime = formatDate(m.timestamp);
+ if (idx === 0) m.showTime = true;
+ else m.showTime = (m.timestamp - list[idx - 1].timestamp) / 60000 > 5;
+ if (!m.senderData) m.senderData = { name: '未知用户', avatar: '' };
+ return m;
+ },
+
+ async fetchGroupHistoryPage(gid, lastTimestamp) {
+ return fetchHistoryMessages({
+ scene: wx.GoEasy.IM_SCENE.GROUP,
+ id: gid,
+ lastTimestamp,
+ limit: 20,
});
},
+ async loadHistory(refresh) {
+ if (!refresh && !this.data.hasMore) return;
+ if (this.data.loadingHistory) return;
+ if (!this.data.groupId) return;
+
+ this.setData({ loadingHistory: true });
+ try {
+ const ready = await waitImConnected(app);
+ if (!ready) {
+ wx.showToast({ title: '消息服务未连接,请稍后重试', icon: 'none' });
+ return;
+ }
+
+ await this.subscribeGroupIfNeeded();
+
+ const ts = refresh
+ ? null
+ : getOldestHistoryTimestamp(this.data.messages, this.data.lastTimestamp);
+ if (!refresh && ts == null) {
+ this.setData({ hasMore: false });
+ wx.showToast({ title: '没有更早的消息了', icon: 'none' });
+ return;
+ }
+
+ let raw = await this.fetchGroupHistoryPage(this.data.groupId, ts);
+
+ if (refresh && raw.length === 0 && isPairGroupId(this.data.groupId) && this.data.orderId) {
+ const legacyId = `group_${this.data.orderId}`;
+ if (legacyId !== this.data.groupId) {
+ raw = await this.fetchGroupHistoryPage(legacyId, ts);
+ }
+ }
+
+ const merged = mergeHistoryMessages({
+ incoming: raw,
+ existing: this.data.messages,
+ refresh,
+ limit: 20,
+ mapMessage: (m, idx, list) => this.mapHistoryMessage(m, idx, list),
+ });
+
+ const update = {
+ messages: merged.messages,
+ hasMore: merged.hasMore,
+ lastTimestamp: merged.lastTimestamp,
+ };
+ if (refresh) update.scrollToView = 'msg-bottom';
+ this.setData(update);
+ } catch (e) {
+ console.warn('[群聊] 历史消息加载失败', e);
+ if (!refresh) {
+ wx.showToast({ title: '加载历史消息失败', icon: 'none' });
+ }
+ } finally {
+ this.setData({ loadingHistory: false });
+ }
+ },
+
listenNewMsg() {
if (this._msgHandler) wx.goEasy.im.off(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this._msgHandler);
this._msgHandler = (msg) => {
if (msg.groupId !== this.data.groupId) return;
- normalizeGroupMessage(msg);
- if (msg.type === 'order' && msg.payload) {
- this.applyOrderCardPayload(msg.payload);
- }
- if (msg.senderId === this.data.currentUser.id && msg.type !== 'order') return;
+ if (msg.senderId === this.data.currentUser.id) return;
+ msg = this.normalizeMessage(msg);
msg.formattedTime = formatDate(msg.timestamp);
const msgs = this.data.messages;
- if (msgs.some((m) => m.messageId === msg.messageId)) return;
const last = msgs.length ? msgs[msgs.length-1] : null;
msg.showTime = last ? (msg.timestamp - last.timestamp) / 60000 > 5 : true;
this.setData({ messages: [...msgs, msg], scrollToView: 'msg-bottom' });
this.markGroupMessageAsRead();
};
wx.goEasy.im.on(wx.GoEasy.IM_EVENT.GROUP_MESSAGE_RECEIVED, this._msgHandler);
- this.markGroupMessageAsRead(); // ← 收到新消息就标记已读
+ this.markGroupMessageAsRead();
},
markGroupMessageAsRead() {
@@ -397,21 +513,31 @@ Page({
chooseImage() {
const that = this;
- wx.chooseImage({
- count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'],
- success(res) { that.setData({ pendingImage: res.tempFilePaths[0] }); }
+ wx.chooseMedia({
+ count: 1,
+ mediaType: ['image'],
+ sizeType: ['compressed'],
+ sourceType: ['album', 'camera'],
+ success(res) {
+ const file = (res.tempFiles && res.tempFiles[0]) || null;
+ if (!file || !file.tempFilePath) return;
+ that.setData({
+ pendingImage: file.tempFilePath,
+ pendingImageFile: that.buildImageFile(file.tempFilePath, file.size),
+ });
+ },
+ fail() {
+ wx.showToast({ title: '选择图片失败', icon: 'none' });
+ },
});
},
- clearPendingImage() { this.setData({ pendingImage: '' }); },
+ clearPendingImage() { this.setData({ pendingImage: '', pendingImageFile: null }); },
sendMessage() {
if (this.data.pendingImage) {
- const file = this.data.pendingImage;
- showConfirmByScene('chat_image_confirm', () => {
- this.sendImageMsg(file);
- this.setData({ pendingImage: '' });
- });
+ this.sendImageMsg(this.data.pendingImage, this.data.pendingImageFile);
+ this.setData({ pendingImage: '', pendingImageFile: null });
return;
}
const text = this.data.inputText.trim();
@@ -442,18 +568,61 @@ Page({
}
that.setData({ messages: [...msgs, localMsg], scrollToView: 'msg-bottom' });
},
- onSuccess: (messageId, status, serverMsg) => {
- that.mergeServerMessage(messageId, status, serverMsg);
+ onSuccess: (messageId, status) => {
+ that.updateMsg(messageId, status);
}
});
},
- sendImageMsg(file) {
+ sendImageMsg(filePath, fileObj) {
const that = this;
+ const { currentUser, groupId, orderId, isCross, groupName, groupAvatar } = that.data;
+ if (!filePath || !currentUser || !groupId) return;
+
+ if (isCross === 0) {
+ if (!wx.goEasy || !wx.goEasy.im) {
+ wx.showToast({ title: '聊天未连接', icon: 'none' });
+ return;
+ }
+ const avatar = resolveAvatarUrl(currentUser.avatar, app);
+ const name = currentUser.name || '用户';
+ const localId = `image-${Date.now()}`;
+ const imageFile = fileObj || this.buildImageFile(filePath, 0);
+ const localMsg = {
+ messageId: localId,
+ type: 'image',
+ timestamp: Date.now(),
+ senderId: currentUser.id,
+ groupId,
+ senderData: { name, avatar },
+ payload: { url: filePath },
+ status: 'sending',
+ formattedTime: formatDate(Date.now()),
+ showTime: this.needShow(Date.now()),
+ };
+ that.setData({ messages: [...that.data.messages, localMsg], scrollToView: 'msg-bottom' });
+ const toData = { name: groupName || '订单群聊', avatar: groupAvatar || avatar, isCross: 0 };
+ if (orderId) toData.orderId = orderId;
+ const imgMsg = wx.goEasy.im.createImageMessage({
+ file: imageFile,
+ to: { type: wx.GoEasy.IM_SCENE.GROUP, id: groupId, data: toData },
+ });
+ wx.goEasy.im.sendMessage({
+ message: imgMsg,
+ onSuccess: (sent) => that.mergeSentMessage(localId, sent),
+ onFailed: (error) => {
+ console.error('[群聊] 图片发送失败', error);
+ that.updateMsg(localId, 'failed');
+ wx.showToast({ title: '图片发送失败', icon: 'none' });
+ },
+ });
+ return;
+ }
+
sendGroupMessage({
msgData: {
type: 'image',
- filePath: file,
+ filePath,
currentUser: that.data.currentUser,
groupId: that.data.groupId,
orderId: that.data.orderId,
@@ -470,8 +639,11 @@ Page({
}
that.setData({ messages: [...msgs, localMsg], scrollToView: 'msg-bottom' });
},
- onSuccess: (messageId, status, serverMsg) => {
- that.mergeServerMessage(messageId, status, serverMsg);
+ onSuccess: (messageId, status) => {
+ that.updateMsg(messageId, status);
+ if (status === 'failed') {
+ wx.showToast({ title: '图片发送失败', icon: 'none' });
+ }
}
});
},
@@ -506,7 +678,7 @@ Page({
const msgId = e.currentTarget.dataset.messageid;
if (!msgId) return;
const msg = this.data.messages.find(m => m.messageId === msgId);
- if (!msg || String(msg.messageId).startsWith('local-')) return;
+ if (!msg || msg.messageId.startsWith('local-')) return;
const itemList = ['复制'];
if (msg.senderId === this.data.currentUser.id && (Date.now() - msg.timestamp < 120000) && !msg.recalled) {
itemList.push('撤回');
@@ -575,7 +747,9 @@ Page({
orderId: order.dingdan_id,
jieshao: order.jieshao,
jine: order.jine,
- beizhu: order.beizhu
+ beizhu: order.beizhu,
+ zhuangtai: order.zhuangtai,
+ nicheng: order.nicheng,
};
sendGroupMessage({
msgData: {
@@ -599,6 +773,10 @@ Page({
},
onSuccess: (messageId, status) => {
that.updateMsg(messageId, status);
+ that.closeOrderSender();
+ if (status === 'success') {
+ wx.showToast({ title: '订单已发送', icon: 'success' });
+ }
}
});
},
@@ -610,5 +788,24 @@ Page({
this.setData({ detailText: text, showDetailModal: true });
},
- onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); }
+ onKeyboardHeightChange(e) { this.setData({ keyboardHeight: e.detail.height }); },
+
+ onAvatarError(e) {
+ const def = getDefaultAvatarUrl(app);
+ const idx = e.currentTarget.dataset.index;
+ if (idx != null && idx !== '') {
+ this.setData({ [`messages[${idx}].senderData.avatar`]: def });
+ }
+ },
+
+ onSelfAvatarError() {
+ const def = getDefaultAvatarUrl(app);
+ if (this.data.currentUser) {
+ this.setData({ 'currentUser.avatar': def });
+ }
+ },
+
+ tapOrderBar() {
+ this.openOrderSender();
+ },
});
\ No newline at end of file
diff --git a/pages/group-chat/group-chat.json b/pages/group-chat/group-chat.json
index d5bfa0e..3e7741a 100644
--- a/pages/group-chat/group-chat.json
+++ b/pages/group-chat/group-chat.json
@@ -1,6 +1,7 @@
{
"navigationBarTitleText": "群聊",
"usingComponents": {
- "order-card": "/components/order-card/order-card"
+ "order-card": "/components/order-card/order-card",
+ "order-sender": "/components/order-sender/order-sender"
}
}
\ No newline at end of file
diff --git a/pages/group-chat/group-chat.wxml b/pages/group-chat/group-chat.wxml
index 2abee15..4e6555a 100644
--- a/pages/group-chat/group-chat.wxml
+++ b/pages/group-chat/group-chat.wxml
@@ -1,18 +1,28 @@
+
+
+
+ 订单 {{orderId}}
+ ¥{{orderJine}}
+
+ {{orderZhuangtaiText}}
+
+ {{orderJieshao}}
+ 点击查看历史订单并发送
+
+ style="top: {{orderId ? '120rpx' : '20rpx'}}; bottom: calc({{bottomSafeHeight}}rpx + {{keyboardHeight}}px + env(safe-area-inset-bottom) + 24rpx);">
加载更早消息...
-
+
{{item.formattedTime}}
-
-
+
{{item.senderData.name || item.senderId}}
ID: {{item.payload.orderId}}
内容: {{item.payload.jieshao}}
金额: ¥{{item.payload.jine}}
+ 备注: {{item.payload.beizhu}}
点击查看详情
-
-
+
-
+
@@ -88,5 +98,5 @@
-
+
diff --git a/pages/group-chat/group-chat.wxss b/pages/group-chat/group-chat.wxss
index dab11fa..81654bb 100644
--- a/pages/group-chat/group-chat.wxss
+++ b/pages/group-chat/group-chat.wxss
@@ -1,5 +1,67 @@
/* 使用全局 chat-* 样式 */
+.order-status-bar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 11;
+ background: #fff;
+ padding: 16rpx 24rpx;
+ border-bottom: 1rpx solid #e8e8e8;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
+}
+
+.order-status-main {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.order-status-id {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: 600;
+}
+
+.order-status-tag {
+ font-size: 22rpx;
+ color: #07c160;
+ background: #e8f8ee;
+ padding: 4rpx 16rpx;
+ border-radius: 20rpx;
+}
+
+.order-status-left {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-width: 0;
+}
+
+.order-status-price {
+ font-size: 24rpx;
+ color: #ff6b00;
+ margin-top: 4rpx;
+}
+
+.order-status-hint {
+ font-size: 22rpx;
+ color: #007aff;
+ margin-top: 6rpx;
+ display: block;
+}
+
+.order-status-desc {
+ font-size: 24rpx;
+ color: #888;
+ margin-top: 8rpx;
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
/* 发送者昵称样式(群聊特有) */
.sender-info {
display: flex;
diff --git a/pages/merchant-home/merchant-home.js b/pages/merchant-home/merchant-home.js
index b6e59a7..e5d2ffb 100644
--- a/pages/merchant-home/merchant-home.js
+++ b/pages/merchant-home/merchant-home.js
@@ -114,6 +114,7 @@ Page(createPage({
if (this.data.isShangjia) {
ensureRoleOnCenterPage(this, 'shangjia');
}
+ if (app.startImWhenReady) app.startImWhenReady();
if (this._skipShowRefresh) {
this._skipShowRefresh = false;
diff --git a/pages/merchant-orders/merchant-orders.js b/pages/merchant-orders/merchant-orders.js
index 65e3e32..a1a899a 100644
--- a/pages/merchant-orders/merchant-orders.js
+++ b/pages/merchant-orders/merchant-orders.js
@@ -5,8 +5,6 @@ import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { getOrderStatusText } from '../../utils/api-helper.js';
import { STAFF_API, isStaffMode, refreshStaffContext, syncStaffUi } from '../../utils/staff-api.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
-import { normalizeOrderTags } from '../../utils/order-tags.js';
-import { ICON_KEYS, resolveMiniappIcon, MERCHANT_GOLD_BANNER_FALLBACK } from '../../utils/miniapp-icons.js';
import { fetchMerchantOrderStats } from '../../utils/merchant-order-stats.js';
const EMPTY_TAB = { list: [], page: 1, hasMore: true, isLoading: false };
@@ -14,6 +12,8 @@ const EMPTY_TAB = { list: [], page: 1, hasMore: true, isLoading: false };
function buildEmptyStatusData() {
return {
all: { ...EMPTY_TAB },
+ daifuwu: { ...EMPTY_TAB },
+ zhidingzhong: { ...EMPTY_TAB },
jinxingzhong: { ...EMPTY_TAB },
tuikuanzhong: { ...EMPTY_TAB },
yituikuan: { ...EMPTY_TAB },
@@ -23,6 +23,17 @@ function buildEmptyStatusData() {
};
}
+const STATUS_TAG_STYLE = {
+ 1: { color: '#E65100', bg: '#FFF3E0' },
+ 2: { color: '#1565C0', bg: '#E3F2FD' },
+ 3: { color: '#2E7D32', bg: '#E8F5E9' },
+ 4: { color: '#EF6C00', bg: '#FFF8E1' },
+ 5: { color: '#616161', bg: '#F5F5F5' },
+ 6: { color: '#C62828', bg: '#FFEBEE' },
+ 7: { color: '#BF360C', bg: '#FFE0B2' },
+ 8: { color: '#D84315', bg: '#FBE9E7' },
+};
+
Page(createPage({
data: {
shangpinleixing: [],
@@ -35,6 +46,8 @@ Page(createPage({
statusList: [
{ name: '全部', key: 'all', zhuangtaiList: [1, 2, 3, 4, 5, 6, 7, 8], color: '#333333' },
+ { name: '待服务', key: 'daifuwu', zhuangtaiList: [1], color: '#E65100' },
+ { name: '指定中', key: 'zhidingzhong', zhuangtaiList: [7], color: '#BF360C' },
{ name: '进行中', key: 'jinxingzhong', zhuangtaiList: [2], color: '#2196F3' },
{ name: '退款中', key: 'tuikuanzhong', zhuangtaiList: [4], color: '#FF9800' },
{ name: '已退款', key: 'yituikuan', zhuangtaiList: [5], color: '#9E9E9E' },
@@ -56,7 +69,6 @@ Page(createPage({
ossImageUrl: app.globalData.ossImageUrl || '',
pendingCount: 0,
- goldBannerUrl: '',
staffList: [],
selectedStaffMemberId: '',
selectedStaffLabel: '全部客服',
@@ -77,9 +89,6 @@ Page(createPage({
this._listInitialized = false;
this._savedScrollTop = 0;
this._skipShowRefresh = false;
- this.setData({
- goldBannerUrl: resolveMiniappIcon(app, ICON_KEYS.MERCHANT_GOLD_BANNER, MERCHANT_GOLD_BANNER_FALLBACK),
- });
wx.setNavigationBarTitle({ title: isStaffMode() ? '客服派单' : '我的派单' });
syncStaffUi(this);
if (isStaffMode()) {
@@ -476,33 +485,15 @@ Page(createPage({
},
processOrderItem(item, zhuangtaiColor) {
- const oss = this.data.ossImageUrl || '';
- const leixing = this.data.shangpinleixing.find((l) => l.id === item.leixing_id);
- const leixingIconUrl = leixing ? leixing.full_tupian_url : '/images/default-type.png';
-
- let sjAvatarFull = '/images/default-avatar.png';
- if (item.sj_avatar) {
- sjAvatarFull = item.sj_avatar.startsWith('http') ? item.sj_avatar : oss + item.sj_avatar;
- }
-
- let zhidingAvatarFull = '';
- if (item.zhiding_avatar) {
- zhidingAvatarFull = item.zhiding_avatar.startsWith('http') ? item.zhiding_avatar : oss + item.zhiding_avatar;
- }
-
- const tags = normalizeOrderTags(item);
+ const st = Number(item.zhuangtai);
+ const tagStyle = STATUS_TAG_STYLE[st] || { color: zhuangtaiColor || '#333', bg: '#F5F5F5' };
return {
...item,
- zhuangtaiZh: getOrderStatusText(item.zhuangtai),
- zhuangtaiColor: zhuangtaiColor || '#333333',
- leixing_icon_url: leixingIconUrl,
- sj_avatar_full: sjAvatarFull,
- zhiding_avatar_full: zhidingAvatarFull,
- isZhiding: item.zhuangtai === 7,
+ zhuangtaiZh: getOrderStatusText(st),
+ zhuangtaiColor: tagStyle.color,
+ zhuangtaiBg: tagStyle.bg,
creat_time: item.creat_time || item.create_time || '',
- ...tags,
- shangjia_youzhi: !!item.shangjia_youzhi,
};
},
diff --git a/pages/merchant-orders/merchant-orders.wxml b/pages/merchant-orders/merchant-orders.wxml
index f438e51..254b813 100644
--- a/pages/merchant-orders/merchant-orders.wxml
+++ b/pages/merchant-orders/merchant-orders.wxml
@@ -65,8 +65,9 @@
-
-
+
+
+
{{item.name}}
@@ -75,7 +76,8 @@
{{pendingCount}}
-
+
+
@@ -113,123 +115,20 @@
快去派发订单吧
-
+
-
-
-
-
-
-
+
+
+ 单号 {{item.dingdan_id}}
+ {{item.zhuangtaiZh}}
-
-
-
-
- 指定
-
- {{item.zhiding_nicheng || '指定接单员'}}
-
-
-
-
-
-
-
-
-
- {{item.jieshao || '暂无介绍'}}
-
- 优质商家
-
-
-
-
- ¥
- {{item.jine || '0.00'}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{item.sjnicheng || '未知商家'}}
- ID:{{item.shangjia_id}} 发布于 {{item.creat_time}}
-
-
-
-
-
-
-
-
-
-
-
-
-
- 指定
-
- {{item.zhiding_nicheng || '指定接单员'}}
-
-
-
-
-
-
-
-
-
- {{item.jieshao || '暂无介绍'}}
-
-
-
- ¥
- {{item.jine || '0.00'}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{item.sjnicheng || '未知商家'}}
- ID:{{item.shangjia_id}} 发布于 {{item.creat_time}}
-
-
-
-
+ {{item.jieshao || '暂无介绍'}}
+
+ ¥{{item.jine || '0.00'}}
+ {{item.creat_time}}
+
diff --git a/pages/merchant-orders/merchant-orders.wxss b/pages/merchant-orders/merchant-orders.wxss
index 2f7e06c..dc68610 100644
--- a/pages/merchant-orders/merchant-orders.wxss
+++ b/pages/merchant-orders/merchant-orders.wxss
@@ -13,6 +13,8 @@ page {
display: flex;
flex-direction: column;
background: #f5f3ff;
+ overflow: hidden;
+ box-sizing: border-box;
}
/* ========== 商品类型区 ========== */
@@ -267,29 +269,38 @@ page {
flex: 1;
display: flex;
overflow: hidden;
+ min-height: 0;
}
/* 左侧状态栏 */
.left-status {
width: 180rpx;
+ height: 100%;
background: #fff;
border-right: 1rpx solid #f0f0f0;
- padding: 28rpx 0;
flex-shrink: 0;
+ box-sizing: border-box;
+ }
+ .left-status-inner {
+ padding: 12rpx 0 48rpx;
}
.status-item {
- padding: 28rpx 20rpx;
+ padding: 22rpx 16rpx;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
+ flex-wrap: wrap;
+ gap: 6rpx;
}
.status-item.status-active {
background: linear-gradient(to right, #e3f2fd, #fff);
}
.status-name {
- font-size: 28rpx;
+ font-size: 26rpx;
color: #555;
+ flex: 1;
+ min-width: 0;
}
.status-active .status-name {
color: #1976D2;
@@ -564,4 +575,72 @@ page {
color: #6d28d9;
border: 2rpx solid #fff;
box-shadow: 0 6rpx 16rpx rgba(245, 213, 99, 0.3);
+ }
+
+ /* 简洁订单卡片 */
+ .sj-order-card {
+ background: #fff;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ margin-bottom: 16rpx;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+ border: 1rpx solid #eee;
+ }
+ .sj-order-card:active {
+ opacity: 0.92;
+ }
+ .sj-order-card-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12rpx;
+ gap: 16rpx;
+ }
+ .sj-order-id {
+ font-size: 24rpx;
+ color: #888;
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .sj-status-tag {
+ flex-shrink: 0;
+ font-size: 24rpx;
+ font-weight: 700;
+ padding: 8rpx 20rpx;
+ border-radius: 999rpx;
+ }
+ .sj-order-desc {
+ font-size: 30rpx;
+ color: #222;
+ line-height: 1.5;
+ font-weight: 500;
+ margin-bottom: 12rpx;
+ }
+ .sj-order-meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+ .sj-order-amount {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #e53935;
+ }
+ .sj-order-time {
+ font-size: 22rpx;
+ color: #aaa;
+ }
+ .sj-order-remark {
+ margin-top: 10rpx;
+ font-size: 24rpx;
+ color: #666;
+ line-height: 1.4;
+ }
+ .line2 {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
}
\ No newline at end of file
diff --git a/pages/merchant/merchant.js b/pages/merchant/merchant.js
index 76fb870..54ff719 100644
--- a/pages/merchant/merchant.js
+++ b/pages/merchant/merchant.js
@@ -36,6 +36,7 @@ Page(createPage({
iconCopy: '',
avatarFrame: '',
iconClear: '',
+ iconKefuList: '',
},
// 商家状态
@@ -139,18 +140,22 @@ Page(createPage({
async onShow() {
this.registerNotificationComponent();
+ if (!this._ossImagesReady && app.globalData.ossImageUrl) {
+ this.setupImageUrls();
+ this._ossImagesReady = true;
+ }
this.syncRoleStatusFromStorage();
syncStaffUi(this);
- if (app.globalData.ossImageUrl) {
- this.setupImageUrls();
- }
- // 每次进入刷新经营/处罚统计(避免罚单数量卡住不更新)
- if (this.data.isShangjia || isMerchantPortalUser()) {
- if (!isStaffMode()) {
- this.loadDashboardStats();
+
+ if (this._showRefreshTimer) clearTimeout(this._showRefreshTimer);
+ this._showRefreshTimer = setTimeout(() => {
+ if (this.data.isShangjia || isMerchantPortalUser()) {
+ if (!isStaffMode()) {
+ this.loadDashboardStats();
+ }
}
- }
- this.loadMerchantDataIfNeeded();
+ this.loadMerchantDataIfNeeded();
+ }, 200);
},
syncRoleStatusFromStorage() {
@@ -159,8 +164,8 @@ Page(createPage({
const portal = isMerchantPortalUser();
const staffFlag = Number(wx.getStorageSync('staffstatus')) === 1;
if (portal || staffFlag) {
- this.setData({ isShangjia: portal || staffFlag, uid: uid || '', staffBooting: false, isLoading: false });
- } else {
+ this.setData({ isShangjia: true, uid: uid || '', staffBooting: false, isLoading: false });
+ } else if (!this.data.isShangjia) {
this.setData({ isShangjia: false, staffBooting: false, isLoading: false });
}
} catch (error) {
diff --git a/pages/merchant/merchant.wxml b/pages/merchant/merchant.wxml
index 0e3586d..0d35507 100644
--- a/pages/merchant/merchant.wxml
+++ b/pages/merchant/merchant.wxml
@@ -56,10 +56,11 @@
{{nicheng || '商家'}}
- {{isStaffMode ? (staffRoleName || '商家客服') : '商家'}}
+ {{staffRoleName || '商家客服'}}
+ 商家
修改昵称
-
+
@@ -75,7 +76,7 @@
-
+
diff --git a/pages/messages/messages.js b/pages/messages/messages.js
index b9db0ba..9cfcf94 100644
--- a/pages/messages/messages.js
+++ b/pages/messages/messages.js
@@ -3,8 +3,9 @@ const app = getApp();
import { formatDate } from '../../static/lib/utils';
import { resolveAvatarUrl, getDefaultAvatarUrl, resolveAvatarFromStorage } from '../../utils/avatar.js';
import { getLocalImUserId, getLocalImIdentity, getImUserIdForRole, loadGroupMetaCache, getDefaultAvatar } from '../../utils/im-user.js';
-import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen } from '../../utils/group-chat.js';
-import { reconnectForRole } from '../../utils/role-tab-bar.js';
+import { enrichGroupConversation, isOrderGroupConversation, sortConversations, recordConversationOpen, getZhuangtaiText } from '../../utils/group-chat.js';
+import request from '../../utils/request';
+import { openCustomerServiceChat } from '../../utils/kefu-nav.js';
import { getPrimaryRole } from '../../utils/primary-role.js';
import { isStaffMode } from '../../utils/staff-api.js';
import { createPage } from '../../utils/base-page.js';
@@ -21,14 +22,23 @@ Page(createPage({
actionPopup: { visible: false, conversation: null },
currentUser: null,
notificationMuted: false,
- scrollHeight: 0
+ scrollHeight: 0,
+ imStatus: 'connecting',
+ imStatusText: '',
+ defaultAvatarUrl: '',
},
_permissionSeq: 0,
_peerProfileCache: {},
+ _lastConvSignature: '',
+ _lastRefreshTs: 0,
+ _showRefreshTimer: null,
+ _statusRefreshTimer: null,
+ _chatAllowed: true,
onLoad() {
this.calculateScrollHeight();
+ this.setData({ defaultAvatarUrl: getDefaultAvatarUrl(app) });
const muted = wx.getStorageSync('notificationMuted') || false;
this.setData({ notificationMuted: muted });
if (app.globalData.messageManager) {
@@ -42,22 +52,61 @@ Page(createPage({
app.loadConversations();
}
};
+ this._onUnreadChanged = (data) => {
+ const unreadTotal = data?.unreadTotal ?? app.globalData.messageManager?.unreadTotal ?? 0;
+ if (app.globalData.messageManager) {
+ app.globalData.messageManager.unreadTotal = unreadTotal;
+ }
+ if (app.emitEvent) {
+ app.emitEvent('tabBarBadgeChanged', {
+ badgeText: unreadTotal > 0 ? String(unreadTotal) : '',
+ });
+ }
+ };
+ this._onConnectionChanged = (data) => {
+ const status = data?.status || 'disconnected';
+ const textMap = {
+ connected: '',
+ connecting: '正在连接消息服务…',
+ reconnecting: '正在重新连接…',
+ disconnected: '消息未连接,点击重试',
+ disabled: '消息服务未配置',
+ };
+ this.setData({
+ imStatus: status,
+ imStatusText: textMap[status] || (status === 'connected' ? '' : '消息未连接,点击重试'),
+ });
+ if (status === 'connected') {
+ this.setupConversationListener();
+ this.loadConversations();
+ }
+ };
app.on('conversationsUpdated', this._onGlobalConvUpdated);
+ app.on('unreadCountChanged', this._onUnreadChanged);
+ app.on('connectionChanged', this._onConnectionChanged);
},
onUnload() {
if (this._onGlobalConvUpdated) {
app.off('conversationsUpdated', this._onGlobalConvUpdated);
}
+ if (this._onUnreadChanged) {
+ app.off('unreadCountChanged', this._onUnreadChanged);
+ }
+ if (this._onConnectionChanged) {
+ app.off('connectionChanged', this._onConnectionChanged);
+ }
if (this.conversationsUpdatedListener && wx.goEasy?.im) {
wx.goEasy.im.off(wx.GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.conversationsUpdatedListener);
this.conversationsUpdatedListener = null;
}
},
- onShow() {
+ async onShow() {
if (!this.checkLoginStatus()) return;
+ this.setData({ defaultAvatarUrl: getDefaultAvatarUrl(app) });
loadGroupMetaCache(app);
+ if (app.restoreTabBarBadge) app.restoreTabBarBadge();
this.registerNotificationComponent();
if (app.globalData.messageManager?.unreadTotal != null && app.emitEvent) {
app.emitEvent('tabBarBadgeChanged', {
@@ -65,13 +114,61 @@ Page(createPage({
? String(app.globalData.messageManager.unreadTotal) : '',
});
}
- this.checkPermissionAndAutoConnect();
- if (app.ensureConnection) app.ensureConnection();
- this.setupConversationListener();
- setTimeout(() => {
+ this.patchLocalReadState();
+ await this.checkPermissionAndAutoConnect();
+ if (this._chatAllowed) {
+ if (app.ensureConnection) app.ensureConnection();
+ this.setupConversationListener();
+ if (app.syncConnectionStatus) app.syncConnectionStatus();
+ this.scheduleSoftRefresh();
+ }
+ },
+
+ scheduleSoftRefresh() {
+ if (this._showRefreshTimer) clearTimeout(this._showRefreshTimer);
+ if (this._statusRefreshTimer) clearTimeout(this._statusRefreshTimer);
+ this._showRefreshTimer = setTimeout(() => {
+ this._showRefreshTimer = null;
+ const now = Date.now();
+ if (now - this._lastRefreshTs < 1800 && this.data.allConversations.length) {
+ return;
+ }
+ this._lastRefreshTs = now;
if (app.loadConversations) app.loadConversations();
else if (wx.goEasy?.im) this.loadConversations();
- }, 200);
+ }, 350);
+ },
+
+ patchLocalReadState() {
+ const openId = app.globalData.pageState?.lastOpenedConvId;
+ if (!openId || !this.data.allConversations.length) return;
+
+ const list = this.data.allConversations.map((c) => {
+ const id = c.groupId || c.userId;
+ if (id === openId && (c.unread || 0) > 0) {
+ return { ...c, unread: 0 };
+ }
+ return c;
+ });
+
+ const openTimes = wx.getStorageSync('conversationOpenTimes') || {};
+ sortConversations(list, openTimes);
+ this.setData({ allConversations: list }, () => this.applyFilter());
+ },
+
+ retryImConnection() {
+ if (!app.globalData.chatEnabled) {
+ wx.showToast({ title: '消息服务未就绪', icon: 'none' });
+ return;
+ }
+ this.setData({ imStatus: 'connecting', imStatusText: '正在连接消息服务…' });
+ app.globalData.goEasyConnection.autoReconnect = true;
+ app.globalData.goEasyConnection.reconnectAttempts = 0;
+ if (app.ensureConnection) app.ensureConnection();
+ setTimeout(() => {
+ if (app.syncConnectionStatus) app.syncConnectionStatus();
+ if (app.loadConversations) app.loadConversations();
+ }, 2000);
},
onHide() {},
@@ -81,24 +178,30 @@ Page(createPage({
const seq = ++this._permissionSeq;
let quanxian;
try {
- quanxian = await jianquanxian(app);
+ quanxian = await jianquanxian(app, { silent: true });
} catch (e) {
+ this._chatAllowed = true;
return;
}
if (seq !== this._permissionSeq) return;
if (!quanxian.allowed) {
+ this._chatAllowed = false;
const currentRole = app.globalData.currentRole || 'normal';
- let content = '您没有权限使用消息功能';
- if (currentRole === 'dashou') content = '您需要先充值会员才能使用消息功能';
- wx.showModal({
- title: '权限不足',
- content,
- showCancel: false,
- confirmText: '我知道了'
- });
+ let hint = '消息连接暂不可用,列表可正常浏览';
+ if (currentRole === 'dashou') {
+ hint = '开通会员后可收发消息(列表可正常浏览)';
+ }
+ const prev = this.data.imStatusText;
+ if (prev !== hint) {
+ this.setData({ imStatusText: hint });
+ }
return;
}
+ this._chatAllowed = true;
+ if (this.data.imStatusText && this.data.imStatusText.includes('会员')) {
+ this.setData({ imStatusText: '' });
+ }
this.autoConnect();
},
@@ -221,56 +324,157 @@ Page(createPage({
// 会话渲染与排序
renderConversations(content) {
- const conversations = (content && content.conversations) ? content.conversations : [];
- if (!conversations.length) {
+ if (!content || !content.conversations) {
this.setData({ allConversations: [] }, () => this.applyFilter());
return;
}
- const myGoEasyId = wx.goEasy?.im?.userId || getLocalImUserId(app);
+ let list = content.conversations;
const defaultAvatar = getDefaultAvatar(app);
+ const myGoEasyId = wx.goEasy?.im?.userId || getLocalImUserId(app);
const ossBase = app.globalData.ossImageUrl || '';
+ const groupMeta = app.globalData.groupInfoMap || {};
- let list = conversations.map((item) => {
+ list.forEach((item, idx) => {
+ item.key = item.groupId || item.userId || item.teamId || `${item.type || 'c'}_${idx}`;
if (item.type === 'group') {
- return enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase);
+ if (!item.data) item.data = {};
+ if (!item.data.orderId && item.groupId) {
+ const m = /^group_(.+)$/.exec(String(item.groupId));
+ if (m) item.data.orderId = m[1];
+ }
+ const meta = groupMeta[item.groupId];
+ if (meta) {
+ if (!item.data) item.data = {};
+ Object.assign(item.data, meta);
+ }
+ if (item.data.orderJieshao && !item.data.orderDesc) {
+ item.data.orderDesc = item.data.orderJieshao;
+ }
+ if (item.data && item.data.dashouGoEasyId) {
+ if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
+ app.globalData.groupInfoMap[item.groupId] = {
+ ...app.globalData.groupInfoMap[item.groupId],
+ ...item.data,
+ };
+ }
+ enrichGroupConversation(item, myGoEasyId, defaultAvatar, ossBase);
+ } else {
+ if (!item.data) item.data = {};
+ item.data.avatar = resolveAvatarUrl(item.data.avatar, app) || defaultAvatar;
}
- if (!item.data) item.data = {};
- if (item.data.avatar) {
- item.data.avatar = resolveAvatarUrl(item.data.avatar, app);
- }
- return item;
- });
-
- 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);
}
- if (item.type === 'group' && item.displayLastMsg) {
- item.lastMessage.preview = item.displayLastMsg;
- }
});
const openTimes = wx.getStorageSync('conversationOpenTimes') || {};
- list = sortConversations(list, openTimes);
-
- this.setData({ allConversations: list, displayCount: 10 }, () => {
- this.applyFilter();
- });
+ sortConversations(list, openTimes);
const unreadTotal = content.unreadTotal !== undefined
? content.unreadTotal
: list.reduce((sum, c) => sum + (c.unread || 0), 0);
+
+ const signature = list.map((c) => {
+ const id = c.groupId || c.userId || c.teamId || '';
+ const unread = c.unread || 0;
+ const ts = c.lastMessage?.timestamp || 0;
+ const preview = c.displayLastMsg || c.lastMessage?.payload?.text || '';
+ return `${id}:${unread}:${ts}:${preview.slice(0, 20)}`;
+ }).join('|');
+
+ if (signature === this._lastConvSignature && this.data.allConversations.length) {
+ if (app.globalData.messageManager) {
+ app.globalData.messageManager.unreadTotal = unreadTotal;
+ }
+ if (app.updateUnreadCount) {
+ app.updateUnreadCount(unreadTotal);
+ } else if (app.emitEvent) {
+ app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' });
+ }
+ this.applyFilter();
+ return;
+ }
+ this._lastConvSignature = signature;
+
+ if (app.globalData.messageManager) {
+ app.globalData.messageManager.unreadTotal = unreadTotal;
+ }
if (app.updateUnreadCount) {
app.updateUnreadCount(unreadTotal);
} else if (app.emitEvent) {
app.emitEvent('tabBarBadgeChanged', { badgeText: unreadTotal > 0 ? String(unreadTotal) : '' });
}
- // 重新订阅群组
+ const prevDisplay = this.data.displayCount;
+ this.setData({
+ allConversations: list,
+ displayCount: prevDisplay > 10 ? prevDisplay : 10,
+ }, () => {
+ this.applyFilter();
+ });
+
this.subscribeGroupsIfNeeded(list);
+ this.scheduleOrderStatusRefresh(list);
+ },
+
+ scheduleOrderStatusRefresh(conversations) {
+ if (this._statusRefreshTimer) clearTimeout(this._statusRefreshTimer);
+ this._statusRefreshTimer = setTimeout(() => {
+ this._statusRefreshTimer = null;
+ this.refreshOrderStatuses(conversations);
+ }, 400);
+ },
+
+ async refreshOrderStatuses(conversations) {
+ const orderIds = [];
+ (conversations || this.data.allConversations).forEach((c) => {
+ if (!isOrderGroupConversation(c)) return;
+ const oid = c.data?.orderId;
+ if (oid && !orderIds.includes(oid)) orderIds.push(oid);
+ });
+ if (!orderIds.length) return;
+
+ try {
+ const res = await request({
+ url: '/dingdan/ltddztpl',
+ method: 'POST',
+ data: { order_ids: orderIds.slice(0, 20) },
+ });
+ const body = res?.data || {};
+ const map = body.data || {};
+ if (!Object.keys(map).length) return;
+
+ let changed = false;
+ const list = this.data.allConversations.map((c) => {
+ const oid = c.data?.orderId;
+ if (!oid || !map[oid]) return c;
+ const st = map[oid];
+ changed = true;
+ const data = {
+ ...c.data,
+ orderId: oid,
+ orderDesc: st.jieshao || c.data.orderDesc,
+ orderJine: st.jine != null ? st.jine : c.data.orderJine,
+ orderZhuangtai: st.zhuangtai,
+ orderZhuangtaiText: getZhuangtaiText(st.zhuangtai),
+ };
+ if (c.groupId && app.globalData.groupInfoMap) {
+ app.globalData.groupInfoMap[c.groupId] = {
+ ...(app.globalData.groupInfoMap[c.groupId] || {}),
+ ...data,
+ };
+ }
+ return { ...c, data };
+ });
+ if (changed) {
+ this.setData({ allConversations: list }, () => this.applyFilter());
+ }
+ } catch (e) {
+ console.warn('刷新订单状态失败', e);
+ }
},
/**
@@ -352,7 +556,16 @@ Page(createPage({
chat(e) {
const conversation = e.currentTarget.dataset.conversation;
if (!conversation) return;
+ if (this._chatAllowed === false) {
+ wx.showToast({ title: '请先开通会员后再聊天', icon: 'none' });
+ return;
+ }
recordConversationOpen(conversation);
+ const convId = conversation.groupId || conversation.userId;
+ if (convId) {
+ if (!app.globalData.pageState) app.globalData.pageState = {};
+ app.globalData.pageState.lastOpenedConvId = convId;
+ }
if (conversation.type === 'private') {
const param = {
@@ -361,17 +574,23 @@ Page(createPage({
toAvatar: resolveAvatarUrl(conversation.data.avatar, app),
};
wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
- } else if (conversation.type === 'group') {
+ } else if (conversation.type === 'group' || (conversation.groupId && isOrderGroupConversation(conversation))) {
const d = conversation.data || {};
+ const rawGid = conversation.groupId || '';
+ const oid = String(d.orderId || rawGid).replace(/^group_/, '');
+ const groupId = rawGid.startsWith('group_') ? rawGid : (oid ? `group_${oid}` : rawGid);
const param = {
- groupId: conversation.groupId,
- orderId: d.orderId || '',
+ groupId,
+ orderId: oid,
groupName: d.name || '订单群聊',
groupAvatar: d.avatar,
isCross: d.isCross || 0,
orderZhuangtai: d.orderZhuangtai,
orderJine: d.orderJine,
orderDesc: d.orderDesc || d.orderJieshao || '',
+ currentUserId: wx.goEasy?.im?.userId || getLocalImUserId(app),
+ currentUserName: this.data.currentUser?.name || '',
+ currentUserAvatar: this.data.currentUser?.avatar || '',
};
wx.navigateTo({ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
} else if (conversation.type === 'cs') {
@@ -381,8 +600,7 @@ Page(createPage({
},
chatKefu() {
- const param = { teamId: 'support_team' };
- wx.navigateTo({ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)) });
+ openCustomerServiceChat();
},
showAction(e) {
diff --git a/pages/messages/messages.wxml b/pages/messages/messages.wxml
index 1b0373b..1aecc48 100644
--- a/pages/messages/messages.wxml
+++ b/pages/messages/messages.wxml
@@ -10,41 +10,62 @@
+
+ ⚠
+ {{imStatusText}},点击重连
+
+
订单消息
- {{tabUnread[0]}}
+ {{tabUnread[0]}}
用户消息
- {{tabUnread[1]}}
+ {{tabUnread[1]}}
客服消息
- {{tabUnread[2]}}
+ {{tabUnread[2]}}
-
-
+
+
- {{item.data.name}}
+
+ {{item.data.name || '用户'}}
+ ID {{item.data.counterpartYonghuid}}
+
{{item.lastMessage.date}}
-
+
+
+ {{item.displayLastMsg || item.lastMessage.payload.text}}
+ {{item.unread > 99 ? '99+' : item.unread}}
+
+
+
+ 单号
+ {{item.data.orderId}}
+ {{item.data.orderZhuangtaiText}}
+
+ {{item.data.orderDesc}}
+
+
+
{{item.lastMessage.type === 'text' ? item.lastMessage.payload.text : (item.lastMessage.type === 'image' ? '[图片]' : '[其他消息]')}}
{{item.unread > 99 ? '99+' : item.unread}}
-
-
+
@@ -54,7 +75,6 @@
-
{{actionPopup.conversation.top ? '取消置顶' : '置顶聊天'}}
@@ -63,7 +83,6 @@
-
-
\ No newline at end of file
+
diff --git a/pages/messages/messages.wxss b/pages/messages/messages.wxss
index 99eb09f..2ab93ed 100644
--- a/pages/messages/messages.wxss
+++ b/pages/messages/messages.wxss
@@ -11,6 +11,31 @@
align-items: center;
padding: 20rpx 24rpx;
background: #fff;
+ border-bottom: 1rpx solid #e8e8e8;
+}
+
+.im-status-bar {
+ display: flex;
+ align-items: center;
+ padding: 16rpx 24rpx;
+ background: #fff1f0;
+ border-bottom: 1rpx solid #ffccc7;
+}
+
+.im-status-bar--warn {
+ background: linear-gradient(90deg, #fff7e6 0%, #fff1f0 100%);
+}
+
+.im-status-icon {
+ font-size: 28rpx;
+ margin-right: 12rpx;
+ color: #fa541c;
+}
+
+.im-status-text {
+ font-size: 26rpx;
+ color: #cf1322;
+ font-weight: 500;
}
.search-bar {
@@ -105,12 +130,61 @@
.conversation-item {
display: flex;
- align-items: center;
+ align-items: flex-start;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: background 0.15s;
}
+.order-group-item {
+ padding: 22rpx 24rpx;
+}
+
+.order-compact {
+ margin-top: 8rpx;
+ padding-top: 8rpx;
+ border-top: 1rpx solid #f3f3f3;
+}
+
+.order-compact-row {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8rpx;
+ margin-bottom: 4rpx;
+}
+
+.order-id-tag {
+ font-size: 20rpx;
+ color: #999;
+ background: #f0f0f0;
+ padding: 2rpx 10rpx;
+ border-radius: 6rpx;
+}
+
+.order-id-text {
+ font-size: 22rpx;
+ color: #576b95;
+}
+
+.order-status-inline {
+ font-size: 20rpx;
+ color: #07c160;
+ background: #e8f8ef;
+ padding: 2rpx 10rpx;
+ border-radius: 6rpx;
+}
+
+.order-desc-compact {
+ display: block;
+ font-size: 24rpx;
+ color: #888;
+ line-height: 1.4;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.conversation-item:active {
background: #f5f5f5;
}
@@ -128,6 +202,56 @@
flex-shrink: 0;
}
+.avatar-round {
+ border-radius: 50%;
+}
+
+.user-meta-row {
+ margin-bottom: 4rpx;
+}
+
+.user-meta {
+ font-size: 24rpx;
+ color: #333;
+}
+
+.user-meta.sub {
+ color: #666;
+ font-size: 22rpx;
+}
+
+.order-block {
+ background: #f7f8fa;
+ border-radius: 12rpx;
+ padding: 16rpx 18rpx;
+ margin-bottom: 10rpx;
+}
+
+.order-id {
+ display: block;
+ font-size: 24rpx;
+ color: #576b95;
+ margin-bottom: 6rpx;
+}
+
+.order-desc {
+ display: block;
+ font-size: 26rpx;
+ color: #333;
+ line-height: 1.5;
+ word-break: break-all;
+}
+
+.order-status {
+ display: inline-block;
+ font-size: 22rpx;
+ color: #07c160;
+ margin-top: 8rpx;
+ padding: 2rpx 12rpx;
+ background: #e8f8ef;
+ border-radius: 8rpx;
+}
+
.info {
flex: 1;
min-width: 0;
@@ -140,11 +264,23 @@
margin-bottom: 8rpx;
}
+.name-col {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.uid-tag {
+ font-size: 22rpx;
+ color: #999;
+ margin-top: 2rpx;
+}
+
.name {
font-size: 30rpx;
color: #1a1a1a;
font-weight: 500;
- flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
diff --git a/tab-bar/index.js b/tab-bar/index.js
index dca25af..f16bc59 100644
--- a/tab-bar/index.js
+++ b/tab-bar/index.js
@@ -50,6 +50,7 @@ Component({
lifetimes: {
attached() {
this.refresh();
+ this._restoreBadgeFromCache();
this._badgeListener = (data) => {
if (data.badgeText !== undefined) {
this.setData({ badgeText: data.badgeText });
@@ -68,20 +69,48 @@ Component({
pageLifetimes: {
show() {
this.refresh();
- const globalBadge = app.globalData.messageManager?.tabBarBadgeText || '';
- if (this.data.badgeText !== globalBadge) {
- this.setData({ badgeText: globalBadge });
- }
+ this._restoreBadgeFromCache();
this._syncUnreadWithDebounce();
},
},
methods: {
+ _restoreBadgeFromCache() {
+ const mm = app.globalData.messageManager || {};
+ let badgeText = mm.tabBarBadgeText || '';
+ if (!badgeText) {
+ try {
+ badgeText = wx.getStorageSync('tabBarMessageBadge') || '';
+ } catch (e) {}
+ }
+ if (!badgeText && mm.unreadTotal > 0) {
+ badgeText = mm.unreadTotal > 99 ? '99+' : String(mm.unreadTotal);
+ }
+ if (!badgeText) {
+ try {
+ const saved = wx.getStorageSync('messageUnreadTotal');
+ if (saved > 0) badgeText = saved > 99 ? '99+' : String(saved);
+ } catch (e) {}
+ }
+ if (badgeText && badgeText !== this.data.badgeText) {
+ this.setData({ badgeText });
+ }
+ if (app.restoreTabBarBadge) app.restoreTabBarBadge();
+ },
+
_syncUnreadWithDebounce() {
if (this._syncTimer) clearTimeout(this._syncTimer);
this._syncTimer = setTimeout(() => {
- if (app && app.loadConversations) app.loadConversations();
- }, 300);
+ if (app.globalData.goEasyConnection) {
+ app.globalData.goEasyConnection.autoReconnect = true;
+ }
+ if (app.startImWhenReady) {
+ app.startImWhenReady();
+ } else {
+ if (app.ensureConnection) app.ensureConnection();
+ if (app.loadConversations) app.loadConversations();
+ }
+ }, 200);
},
refresh() {
diff --git a/utils/avatar.js b/utils/avatar.js
index c5efd6b..0c0c725 100644
--- a/utils/avatar.js
+++ b/utils/avatar.js
@@ -1,4 +1,4 @@
-/** 统一头像 URL:无有效头像时始终返回 ossImageUrl + morentouxiang */
+/** 统一头像 URL:无有效头像时始终返回 ossImageUrl + morentouxiang(后端配置) */
function isBlankPath(p) {
if (p == null) return true;
@@ -7,28 +7,71 @@ function isBlankPath(p) {
return !s || s === 'null' || s === 'undefined' || s === 'none';
}
+function readConfigFallback(app) {
+ if (!app || typeof app.readConfigFromStorage !== 'function') return null;
+ try {
+ return app.readConfigFromStorage();
+ } catch (e) {
+ return null;
+ }
+}
+
+function getOssBase(app) {
+ let oss = app.globalData?.ossImageUrl || '';
+ if (!oss) {
+ const cfg = readConfigFallback(app);
+ oss = cfg?.cos?.ossImageUrl || '';
+ }
+ return oss;
+}
+
+function getDefaultRelative(app) {
+ let def = app.globalData?.morentouxiang || '';
+ if (!def) {
+ const cfg = readConfigFallback(app);
+ def = cfg?.otherConfig?.morentouxiang || '';
+ }
+ return def || 'avatar/default.jpg';
+}
+
export function getDefaultAvatarUrl(app) {
return resolveAvatarUrl('', app);
}
export function resolveAvatarUrl(rawPath, app) {
app = app || getApp();
- const oss = app.globalData.ossImageUrl || '';
- const def = app.globalData.morentouxiang || 'avatar/default.jpg';
+ const oss = getOssBase(app);
+ const def = getDefaultRelative(app);
if (!isBlankPath(rawPath)) {
if (rawPath.startsWith('http://') || rawPath.startsWith('https://')) {
return rawPath;
}
- const path = rawPath.startsWith('/') ? rawPath : '/' + rawPath;
- return oss + path;
+ const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
+ if (oss) return oss + path;
+ return path;
}
if (def.startsWith('http://') || def.startsWith('https://')) {
return def;
}
- const defPath = def.startsWith('/') ? def : '/' + def;
- return oss + defPath;
+ const defPath = def.startsWith('/') ? def : `/${def}`;
+ if (oss) return oss + defPath;
+ // 配置未就绪时仍返回相对路径,由 binderror 回退;有 oss 时必须拼完整 URL
+ return defPath;
+}
+
+/** 是否已是可加载的完整头像 URL */
+export function isUsableAvatarUrl(url) {
+ return url && (url.startsWith('http://') || url.startsWith('https://'));
+}
+
+export function ensureAvatarUrl(rawPath, app) {
+ app = app || getApp();
+ const resolved = resolveAvatarUrl(rawPath, app);
+ if (isUsableAvatarUrl(resolved)) return resolved;
+ const def = getDefaultAvatarUrl(app);
+ return isUsableAvatarUrl(def) ? def : resolved;
}
export function resolveAvatarFromStorage(app) {
@@ -36,3 +79,19 @@ export function resolveAvatarFromStorage(app) {
const tx = wx.getStorageSync('touxiang') || '';
return resolveAvatarUrl(tx, app);
}
+
+/** 配置加载后刷新页面头像(聊天页等订阅 configApplied) */
+export function subscribeConfigAvatarRefresh(page, refreshFn) {
+ const app = getApp();
+ if (!app || typeof refreshFn !== 'function') return null;
+ const handler = () => refreshFn();
+ if (app.on) app.on('configApplied', handler);
+ return () => { if (app.off) app.off('configApplied', handler); };
+}
+
+/** 消息列表/聊天页 image 加载失败时回退默认头像 */
+export function onAvatarImageError(page, dataKey, app) {
+ if (!page || !dataKey) return;
+ const def = getDefaultAvatarUrl(app || getApp());
+ page.setData({ [dataKey]: def });
+}
diff --git a/utils/base-page.js b/utils/base-page.js
index 6531b92..bb13c82 100644
--- a/utils/base-page.js
+++ b/utils/base-page.js
@@ -272,6 +272,10 @@ const BASE_METHODS = {
const result = {};
Object.keys(urlMap).forEach((key) => {
const path = urlMap[key];
+ if (!path || typeof path !== 'string') {
+ result[key] = '';
+ return;
+ }
result[key] = path.startsWith('http') || path.startsWith('/') ? path : ossImageUrl + path;
});
return result;
diff --git a/utils/chat-core.js b/utils/chat-core.js
index fd0192d..a75548a 100644
--- a/utils/chat-core.js
+++ b/utils/chat-core.js
@@ -42,6 +42,8 @@ function initGlobalMessageSystem(app) {
app.handleNewMessage = (message) => handleNewMessage(app, message);
app.ensureImForRole = (role) => ensureImForRole(app, role);
app.syncConnectionStatus = () => syncConnectionStatus(app);
+ app.restoreTabBarBadge = () => restoreCachedBadge(app);
+ app.startImWhenReady = () => startImWhenReady(app);
loadUserSettings(app);
initNetworkListener(app);
@@ -132,6 +134,7 @@ function initAppStateListener(app) {
wx.onAppShow(() => {
updateCurrentPageState(app);
app.globalData.goEasyConnection.autoReconnect = true;
+ restoreCachedBadge(app);
ensureConnection(app);
setTimeout(() => loadConversations(app), 400);
});
@@ -205,6 +208,9 @@ function syncConnectionStatus(app) {
app.globalData.goEasyConnection.status = 'connected';
app.globalData.goEasyConnection.userId = cur || expectedId;
app.globalData.goEasyConnection.autoReconnect = true;
+ setupMessageListeners(app);
+ loadConversations(app);
+ restoreCachedBadge(app);
app.emitEvent('connectionChanged', { status: 'connected' });
return;
}
@@ -607,6 +613,7 @@ function handleNewMessage(app, message) {
}
cacheMessage(app, message);
+ loadConversations(app);
if (shouldShowNotification(app, message)) {
let notificationType = 'private';
@@ -687,8 +694,57 @@ function showNotificationDirect(app, data) {
app.emitEvent('showNotification', data);
}
+function startImWhenReady(app) {
+ if (!wx.getStorageSync('uid')) return;
+ app.globalData.goEasyConnection.autoReconnect = true;
+ restoreCachedBadge(app);
+ ensureConnection(app);
+ setTimeout(() => {
+ if (isSdkConnected()) {
+ setupMessageListeners(app);
+ loadConversations(app);
+ }
+ restoreCachedBadge(app);
+ }, 450);
+}
+
+function restoreCachedBadge(app) {
+ const { messageManager } = app.globalData;
+ let unread = messageManager.unreadTotal;
+ if (unread == null || unread === '') {
+ try {
+ const saved = wx.getStorageSync(messageManager.cacheKeys.unreadTotal);
+ if (saved !== '' && saved != null) unread = Number(saved) || 0;
+ } catch (e) {}
+ }
+ if (!unread) {
+ try {
+ const badgeStr = wx.getStorageSync('tabBarMessageBadge');
+ if (badgeStr && badgeStr !== '0') {
+ unread = badgeStr === '99+' ? 100 : (parseInt(badgeStr, 10) || 0);
+ }
+ } catch (e) {}
+ }
+ if (unread > 0) {
+ updateTabBarBadge(app, unread);
+ } else if (messageManager.tabBarBadgeText) {
+ app.emitEvent('tabBarBadgeChanged', {
+ index: messageManager.tabBarIndex,
+ badgeText: messageManager.tabBarBadgeText,
+ showRedDot: true,
+ forceUpdate: true,
+ });
+ updateTabBarDirectly(app, messageManager.tabBarBadgeText);
+ }
+}
+
function applyUnreadTotal(app, unreadTotal) {
const prev = app.globalData.messageManager.unreadTotal;
+ const connected = isSdkConnected();
+ if (unreadTotal === 0 && !connected && prev > 0) {
+ restoreCachedBadge(app);
+ return;
+ }
app.globalData.messageManager.unreadTotal = unreadTotal;
updateTabBarBadge(app, unreadTotal);
wx.setStorageSync(app.globalData.messageManager.cacheKeys.unreadTotal, unreadTotal);
@@ -789,7 +845,13 @@ function loadConversations(app) {
function loadConversationsNow(app) {
if (_loadConvInFlight) return;
- if (!isSdkConnected() || !wx.goEasy?.im?.latestConversations) return;
+ if (!isSdkConnected() || !wx.goEasy?.im?.latestConversations) {
+ restoreCachedBadge(app);
+ if (app.globalData.goEasyConnection.autoReconnect) {
+ ensureConnection(app);
+ }
+ return;
+ }
_loadConvInFlight = true;
wx.goEasy.im.latestConversations({
onSuccess: (result) => {
@@ -815,6 +877,7 @@ function loadConversationsNow(app) {
onFailed: (error) => {
_loadConvInFlight = false;
console.error('加载会话列表失败:', error);
+ restoreCachedBadge(app);
if (!isSdkConnected() && app.globalData.goEasyConnection.autoReconnect) {
attemptReconnect(app);
}
@@ -842,7 +905,14 @@ function updateCurrentPageState(app) {
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
- const chatPages = ['pages/liaotian/liaotian', 'pages/qunliaotian/qunliaotian', 'pages/kefuliaotian/kefuliaotian'];
+ const chatPages = [
+ 'pages/chat/chat',
+ 'pages/group-chat/group-chat',
+ 'pages/cs-chat/cs-chat',
+ 'pages/liaotian/liaotian',
+ 'pages/qunliaotian/qunliaotian',
+ 'pages/kefuliaotian/kefuliaotian',
+ ];
app.globalData.pageState.currentPage = currentPage.route;
app.globalData.pageState.isInChatPage = chatPages.indexOf(currentPage.route) >= 0;
app.globalData.pageState.lastPageUpdate = Date.now();
@@ -907,7 +977,8 @@ function updateTabBarDirectly(app, badgeText) {
try {
const pages = getCurrentPages();
for (let i = pages.length - 1; i >= 0; i--) {
- const tabBar = pages[i].selectComponent('#custom-tab-bar');
+ const tabBar = pages[i].selectComponent('#custom-tab-bar')
+ || pages[i].selectComponent('tab-bar');
if (tabBar && tabBar.setData) {
tabBar.setData({ badgeText });
break;
diff --git a/utils/chat-history.js b/utils/chat-history.js
new file mode 100644
index 0000000..4946c9e
--- /dev/null
+++ b/utils/chat-history.js
@@ -0,0 +1,106 @@
+/**
+ * 聊天历史消息加载(私聊 / 群聊 / 客服共用)
+ * - 排除本地/欢迎语等合成消息的 timestamp,避免下拉加载错位
+ * - 统一 IM 连接等待与 history API 封装
+ */
+
+const SYNTHETIC_MSG_ID = /^(local-|img-|order-|welcome-)/;
+
+export function isHistorySyntheticMessage(msg) {
+ return !!(msg && msg.messageId && SYNTHETIC_MSG_ID.test(String(msg.messageId)));
+}
+
+/** 取当前列表里最早一条「真实」服务端消息的时间戳,供 GoEasy history 分页 */
+export function getOldestHistoryTimestamp(messages, fallback = null) {
+ if (!Array.isArray(messages)) return fallback;
+ let oldest = null;
+ for (const m of messages) {
+ if (!m || m.timestamp == null) continue;
+ if (isHistorySyntheticMessage(m)) continue;
+ if (oldest == null || m.timestamp < oldest) oldest = m.timestamp;
+ }
+ return oldest != null ? oldest : fallback;
+}
+
+export function isImConnected() {
+ const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
+ return (status === 'connected' || status === 'reconnected') && !!wx.goEasy?.im;
+}
+
+/** 等待 IM 连接就绪(下拉加载历史前必须先连上) */
+export function waitImConnected(app, timeoutMs = 8000) {
+ return new Promise((resolve) => {
+ if (isImConnected()) {
+ resolve(true);
+ return;
+ }
+ if (app?.ensureConnection) app.ensureConnection();
+ const start = Date.now();
+ const timer = setInterval(() => {
+ if (isImConnected()) {
+ clearInterval(timer);
+ resolve(true);
+ } else if (Date.now() - start >= timeoutMs) {
+ clearInterval(timer);
+ resolve(false);
+ }
+ }, 200);
+ });
+}
+
+export function fetchHistoryMessages({ scene, id, lastTimestamp, limit = 20 }) {
+ return new Promise((resolve, reject) => {
+ if (!wx.goEasy?.im?.history) {
+ reject(new Error('IM未初始化'));
+ return;
+ }
+ if (!id) {
+ reject(new Error('会话ID为空'));
+ return;
+ }
+ wx.goEasy.im.history({
+ type: scene,
+ id: String(id),
+ lastTimestamp: lastTimestamp ?? null,
+ limit,
+ onSuccess: (res) => resolve(res?.content || []),
+ onFailed: (err) => reject(err || new Error('加载历史消息失败')),
+ });
+ });
+}
+
+/**
+ * 合并一页历史消息
+ * @returns {{ messages, hasMore, lastTimestamp }}
+ */
+export function mergeHistoryMessages({ incoming, existing, refresh, limit = 20, mapMessage }) {
+ let list = Array.isArray(incoming) ? [...incoming] : [];
+ list.sort((a, b) => a.timestamp - b.timestamp);
+ if (typeof mapMessage === 'function') {
+ list = list.map((m, idx) => mapMessage(m, idx, list));
+ }
+
+ const prev = Array.isArray(existing) ? existing : [];
+ let final = refresh ? list : [...list, ...prev];
+
+ if (!refresh) {
+ for (let i = 0; i < final.length; i += 1) {
+ if (i === 0) final[i].showTime = true;
+ else final[i].showTime = (final[i].timestamp - final[i - 1].timestamp) / 60000 > 5;
+ }
+ }
+
+ const ids = new Set();
+ const unique = [];
+ for (const m of final) {
+ if (!m?.messageId || ids.has(m.messageId)) continue;
+ ids.add(m.messageId);
+ unique.push(m);
+ }
+
+ return {
+ messages: unique,
+ hasMore: list.length >= limit,
+ lastTimestamp: getOldestHistoryTimestamp(unique, null),
+ };
+}
diff --git a/utils/group-chat.js b/utils/group-chat.js
index f259133..d354a4a 100644
--- a/utils/group-chat.js
+++ b/utils/group-chat.js
@@ -4,8 +4,9 @@
export const ORDER_CARD_PREFIX = '[ORDER_CARD]';
const ZHUANGTAI_MAP = {
- 1: '已下单', 2: '进行中', 3: '已完成', 4: '退款中',
+ 1: '待抢单', 2: '进行中', 3: '已完成', 4: '退款中',
5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中',
+ 9: '未支付', 14: '支付失败', 25: '已废弃',
};
export function getZhuangtaiText(zhuangtai) {
@@ -38,6 +39,12 @@ export function resolveLocalGroupId(identityType, myUid, partnerUid, fadanPingta
}
return buildDashouBossGroupId(myUid, partnerUid);
}
+ if (identityType === 'boss' || identityType === 'normal') {
+ if (partnerUid && myUid) {
+ return buildDashouBossGroupId(partnerUid, myUid);
+ }
+ return orderId ? `group_${orderId}` : null;
+ }
return orderId ? `group_${orderId}` : null;
}
@@ -105,6 +112,32 @@ export function applyPairMetaToConversation(item, myGoEasyId, defaultAvatar, oss
return;
}
+ const parsed = parsePairGroupId(item.groupId);
+ if (parsed && myGoEasyId) {
+ const isDashou = myGoEasyId === parsed.dashouId;
+ const isPartner = myGoEasyId === parsed.partnerId;
+ if (isDashou) {
+ d.counterpartId = parsed.partnerId;
+ d.counterpartYonghuid = parsed.partnerId.replace(/^(Sj|Boss)/, '');
+ d.partnerRoleLabel = parsed.partnerRole === 'boss' ? '老板' : '商家';
+ if (!d.name || d.name === item.groupId) {
+ d.name = d.partnerNicheng || `${d.partnerRoleLabel}${d.counterpartYonghuid.slice(-6)}`;
+ }
+ if (!d.avatar || d.avatar === defaultAvatar) {
+ d.avatar = d.partnerAvatar || defaultAvatar;
+ }
+ } else if (isPartner) {
+ d.counterpartId = parsed.dashouId;
+ d.counterpartYonghuid = parsed.dashouId.replace(/^Ds/, '');
+ if (!d.name || d.name === item.groupId) {
+ d.name = d.dashouNicheng || `打手${d.counterpartYonghuid.slice(-6)}`;
+ }
+ if (!d.avatar || d.avatar === defaultAvatar) {
+ d.avatar = d.dashouAvatar || defaultAvatar;
+ }
+ }
+ }
+
const counterpartId = getCounterpartGoEasyId(item.groupId, myGoEasyId);
if (counterpartId) {
d.counterpartId = counterpartId;
@@ -215,6 +248,7 @@ export function getConversationSortTime(conversation, openTimes) {
const id = conversation.groupId || conversation.userId || '';
const msgTs = conversation.lastMessage?.timestamp || 0;
const openTs = (openTimes && id && openTimes[id]) ? openTimes[id] : 0;
+ // QQ/微信:取「最后消息时间」与「最近进入会话时间」较大值;新消息到达会更新 msgTs 并置顶
return Math.max(msgTs, openTs);
}
@@ -222,8 +256,6 @@ export function sortConversations(list, openTimes) {
return 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 = getConversationSortTime(a, openTimes);
const tb = getConversationSortTime(b, openTimes);
return tb - ta;
diff --git a/utils/im-user.js b/utils/im-user.js
index 29a2c56..20bcc83 100644
--- a/utils/im-user.js
+++ b/utils/im-user.js
@@ -24,9 +24,10 @@ export const IM_ROLE_PREFIX = {
kaoheguan: 'Ds',
};
+import { getDefaultAvatarUrl } from './avatar.js';
+
export function getDefaultAvatar(app) {
- const oss = app.globalData.ossImageUrl || '';
- return oss + (app.globalData.morentouxiang || '');
+ return getDefaultAvatarUrl(app || getApp());
}
export function getImPrefixForRole(role) {
diff --git a/utils/imAuth/dashoujianquan.js b/utils/imAuth/dashoujianquan.js
index b1fc2be..025cc03 100644
--- a/utils/imAuth/dashoujianquan.js
+++ b/utils/imAuth/dashoujianquan.js
@@ -4,9 +4,9 @@ const imRequest = require('./imRequest.js');
/**
* 打手 IM 权限检测
* POST /yonghu/dshqltqx
- * 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
*/
-async function dashoujianquan(app) {
+async function dashoujianquan(app, options = {}) {
+ const silent = options && options.silent === true;
const uid = wx.getStorageSync('uid');
if (!uid) return { allowed: false, reason: '未登录' };
@@ -21,7 +21,8 @@ async function dashoujianquan(app) {
const allow = res.data.allow;
if (allow === 1) {
return { allowed: true };
- } else {
+ }
+ if (!silent) {
await app.disconnectGoEasy();
wx.showToast({
title: '当前身份已不支持消息聊天功能,请充值会员或联系管理员后尝试',
@@ -30,22 +31,24 @@ async function dashoujianquan(app) {
mask: true
});
await new Promise(r => setTimeout(r, 2100));
- return { allowed: false, reason: '接单员消息权限被限制' };
}
+ return { allowed: false, reason: '接单员消息权限被限制' };
}
throw new Error(res.data?.msg || '鉴权接口异常');
} catch (err) {
console.error('打手鉴权失败:', err);
- await app.disconnectGoEasy();
- wx.showToast({
- title: '消息服务暂不可用,请稍后重试',
- icon: 'none',
- duration: 2000,
- mask: true
- });
- await new Promise(r => setTimeout(r, 2100));
+ if (!silent) {
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '消息服务暂不可用,请稍后重试',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ await new Promise(r => setTimeout(r, 2100));
+ }
return { allowed: false, reason: '鉴权请求失败' };
}
}
-module.exports = { dashoujianquan };
\ No newline at end of file
+module.exports = { dashoujianquan };
diff --git a/utils/imAuth/jianquanxian.js b/utils/imAuth/jianquanxian.js
index f373451..351b338 100644
--- a/utils/imAuth/jianquanxian.js
+++ b/utils/imAuth/jianquanxian.js
@@ -14,21 +14,21 @@ const { dashoujianquan } = require('./dashoujianquan.js');
* @param {Object} app 全局 App 实例
* @returns {Promise<{ allowed: boolean, reason?: string }>}
*/
-async function jianquanxian(app) {
+async function jianquanxian(app, options = {}) {
+ const silent = options && options.silent === true;
// 1. 检测登录态(用 uid 或 token)
const token = wx.getStorageSync('token') || wx.getStorageSync('uid');
if (!token) {
- // 没登录:先强制断开(如果已连接)
- await app.disconnectGoEasy();
- // 弹窗提示,强制阅读 2 秒
- wx.showToast({
- title: '您当前处于未登录状态,消息功能暂不可用',
- icon: 'none',
- duration: 2000,
- mask: true
- });
- // 等待提示结束,确保用户看到
- await new Promise(resolve => setTimeout(resolve, 2100));
+ if (!silent) {
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '您当前处于未登录状态,消息功能暂不可用',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ await new Promise(resolve => setTimeout(resolve, 2100));
+ }
return { allowed: false, reason: '未登录' };
}
@@ -37,17 +37,16 @@ async function jianquanxian(app) {
// 3. 根据角色分发到具体鉴权模块
switch (role) {
- case 'normal': // 点单老板
- return laobanjianquan(app);
+ case 'normal':
+ return laobanjianquan(app, { silent });
- case 'dashou': // 打手
- return dashoujianquan(app);
+ case 'dashou':
+ return dashoujianquan(app, { silent });
- // 其他角色暂未细分,统一放行(后续可新增对应文件)
case 'shangjia':
case 'guanshi':
case 'zuzhang':
- case 'kaoheguan':
+ case 'kaoheguan':
return { allowed: true };
default:
diff --git a/utils/imAuth/laobanjianquan.js b/utils/imAuth/laobanjianquan.js
index a29361a..275358f 100644
--- a/utils/imAuth/laobanjianquan.js
+++ b/utils/imAuth/laobanjianquan.js
@@ -4,9 +4,9 @@ const imRequest = require('./imRequest.js');
/**
* 老板(点单用户)IM 权限检测
* POST /yonghu/lbhqltqx
- * 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
*/
-async function laobanjianquan(app) {
+async function laobanjianquan(app, options = {}) {
+ const silent = options && options.silent === true;
const uid = wx.getStorageSync('uid');
if (!uid) return { allowed: false, reason: '未登录' };
@@ -21,7 +21,8 @@ async function laobanjianquan(app) {
const allow = res.data.allow;
if (allow === 1) {
return { allowed: true };
- } else {
+ }
+ if (!silent) {
await app.disconnectGoEasy();
wx.showToast({
title: '当前身份已不支持使用消息功能',
@@ -30,22 +31,24 @@ async function laobanjianquan(app) {
mask: true
});
await new Promise(r => setTimeout(r, 1100));
- return { allowed: false, reason: '当前身份无消息权限' };
}
+ return { allowed: false, reason: '当前身份无消息权限' };
}
throw new Error(res.data?.msg || '鉴权接口异常');
} catch (err) {
console.error('老板鉴权失败:', err);
- await app.disconnectGoEasy();
- wx.showToast({
- title: '消息服务暂不可用,请稍后重试',
- icon: 'none',
- duration: 2000,
- mask: true
- });
- await new Promise(r => setTimeout(r, 2100));
+ if (!silent) {
+ await app.disconnectGoEasy();
+ wx.showToast({
+ title: '消息服务暂不可用,请稍后重试',
+ icon: 'none',
+ duration: 2000,
+ mask: true
+ });
+ await new Promise(r => setTimeout(r, 2100));
+ }
return { allowed: false, reason: '鉴权请求失败' };
}
}
-module.exports = { laobanjianquan };
\ No newline at end of file
+module.exports = { laobanjianquan };
diff --git a/utils/kefu-nav.js b/utils/kefu-nav.js
new file mode 100644
index 0000000..e5a1e38
--- /dev/null
+++ b/utils/kefu-nav.js
@@ -0,0 +1,12 @@
+/** 与消息页「联系客服」一致:跳转客服会话 */
+export function openCustomerServiceChat() {
+ const app = getApp();
+ if (app.globalData.chatEnabled && app.ensureConnection) {
+ app.globalData.goEasyConnection.autoReconnect = true;
+ app.ensureConnection();
+ }
+ const param = { teamId: 'support_team' };
+ wx.navigateTo({
+ url: '/pages/cs-chat/cs-chat?data=' + encodeURIComponent(JSON.stringify(param)),
+ });
+}
diff --git a/utils/role-tab-bar.js b/utils/role-tab-bar.js
index 8584da4..39f766f 100644
--- a/utils/role-tab-bar.js
+++ b/utils/role-tab-bar.js
@@ -249,8 +249,9 @@ export function reconnectForRole(role) {
const connected = status === 'connected' || status === 'reconnected';
if (connected && currentUserId === targetUserId) {
- if (app.loadConversations) app.loadConversations();
+ if (app.restoreTabBarBadge) app.restoreTabBarBadge();
if (app.ensureConnection) app.ensureConnection();
+ if (app.loadConversations) app.loadConversations();
return;
}
diff --git a/utils/staff-api.js b/utils/staff-api.js
index 24f18c9..b964f86 100644
--- a/utils/staff-api.js
+++ b/utils/staff-api.js
@@ -224,8 +224,9 @@ export function syncStaffUi(page) {
const ctx = staff ? (getStaffContext() || {}) : null;
const wallet = (ctx && ctx.wallet) || {};
const portal = isMerchantPortalUser();
+ const staffFlag = Number(wx.getStorageSync('staffstatus')) === 1;
page.setData({
- isShangjia: portal,
+ isShangjia: portal || staffFlag || !!(page.data && page.data.isShangjia),
isStaffMode: staff,
isMerchantOwner: !staff && Number(wx.getStorageSync('shangjiastatus')) === 1,
staffRoleName: staff ? (ctx.role_name || '客服') : '',
diff --git a/utils/xiaoxilj.js b/utils/xiaoxilj.js
index e4eda3f..a8c7c95 100644
--- a/utils/xiaoxilj.js
+++ b/utils/xiaoxilj.js
@@ -1,269 +1,296 @@
-/**
- * 订单群聊跳转:先确定群 ID,IM 连接失败也尽量能进聊天页
- */
-import request from './request';
-import { persistGroupMeta } from './im-user.js';
-import { resolveLocalGroupId } from './group-chat.js';
-
-const app = getApp();
-
-function formatError(err) {
- if (!err) return '进入聊天失败';
- if (typeof err === 'string') return err;
- if (err.message) return err.message;
- if (err.content) return String(err.content);
- if (err.msg) return String(err.msg);
- if (err.code) return `连接失败(${err.code})`;
- try {
- return JSON.stringify(err);
- } catch (e) {
- return '进入聊天失败';
- }
-}
-
-function withTimeout(promise, ms, errMsg) {
- return new Promise((resolve, reject) => {
- const timer = setTimeout(() => reject(new Error(errMsg || '操作超时')), ms);
- promise
- .then((v) => { clearTimeout(timer); resolve(v); })
- .catch((e) => { clearTimeout(timer); reject(e); });
- });
-}
-
-function getConnectedUserId() {
- const fromState = app.globalData?.goEasyConnection?.userId;
- const fromIm = wx.goEasy?.im?.userId;
- return fromState || fromIm || '';
-}
-
-function isImConnected() {
- const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
- return status === 'connected' || status === 'reconnected';
-}
-
-async function ensureIdentityConnection(identityType, userId) {
- app.globalData.currentRole = identityType;
- wx.setStorageSync('currentRole', identityType);
-
- const uid = userId.replace(/^(Ds|Sj|Boss|Gs|Zz|Kh)/, '');
- const avatar = wx.getStorageSync('touxiang') ||
- (app.globalData.ossImageUrl + app.globalData.morentouxiang);
- app.globalData.currentUser = {
- id: userId,
- name: app.globalData.currentUser?.name || `用户${uid}`,
- avatar: avatar,
- };
-
- if (!app.globalData.chatEnabled || !wx.goEasy?.im) {
- throw new Error('聊天服务未就绪,请稍后重试');
- }
-
- if (isImConnected() && getConnectedUserId() === userId) {
- return;
- }
-
- const connectedId = getConnectedUserId();
- if (connectedId && connectedId !== userId && app.disconnectGoEasy) {
- await app.disconnectGoEasy();
- }
-
- if (!app.connectWithIdentity) {
- throw new Error('聊天功能未初始化');
- }
-
- await withTimeout(
- app.connectWithIdentity(identityType, userId, true),
- 12000,
- 'IM连接超时'
- );
-
- if (!isImConnected()) {
- throw new Error('IM连接失败,请检查网络后重试');
- }
-}
-
-async function prepareGroupChat(identityType, userId, orderId, partnerUid, fadanPingtai, groupName, isCross) {
- let chatData = null;
-
- try {
- const res = await withTimeout(
- request({
- url: '/dingdan/ltdhzb',
- method: 'POST',
- data: {
- dingdan_id: orderId,
- identityType,
- push_order_card: false,
- },
- }),
- 12000,
- '准备群聊超时'
- );
- const body = res?.data || {};
- if (body.code === 0 && body.data?.groupId) {
- chatData = body.data;
- } else if (body.msg) {
- console.warn('ltdhzb:', body.msg);
- }
- } catch (e) {
- console.warn('ltdhzb 请求失败,尝试本地 groupId', e);
- }
-
- if (!chatData?.groupId) {
- const myUid = userId.replace(/^(Ds|Sj|Boss)/, '');
- let localGroupId = resolveLocalGroupId(
- identityType, myUid, partnerUid, fadanPingtai, orderId, isCross
- );
- if (!localGroupId && orderId) {
- localGroupId = `group_${orderId}`;
- }
- if (!localGroupId) {
- throw new Error('无法确定群聊,请确认订单已接单且对方信息完整');
- }
- chatData = {
- groupId: localGroupId,
- orderId,
- groupName: groupName || '订单群聊',
- isCross: isCross || fadanPingtai || 0,
- };
- try {
- await request({
- url: '/dingdan/ltdhzb',
- method: 'POST',
- data: { dingdan_id: orderId, identityType, push_order_card: false },
- });
- } catch (e) { /* ignore */ }
- }
-
- return chatData;
-}
-
-class ConnectionManager {
- async connectAndChat(params) {
- const { identityType, userId, targetUser } = params;
- if (!identityType || !userId || !targetUser || !targetUser.id) {
- throw new Error('参数不完整');
- }
-
- await ensureIdentityConnection(identityType, userId);
-
- const currentUser = {
- id: userId,
- name: app.globalData.currentUser?.name || '用户',
- avatar: app.globalData.currentUser?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
- };
-
- const to = {
- id: targetUser.id,
- name: targetUser.name || '用户',
- avatar: targetUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
- };
-
- const param = { to, currentUser };
- wx.navigateTo({ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify(param)) });
- }
-
- async connectToGroupChat(params) {
- const {
- identityType, userId, orderId, groupName, groupAvatar, isCross,
- partnerUid, fadanPingtai,
- } = params;
-
- if (!identityType || !userId || !orderId) {
- throw new Error('参数不完整');
- }
-
- app.globalData.currentRole = identityType;
- wx.setStorageSync('currentRole', identityType);
-
- wx.showLoading({ title: '建立联系中...', mask: true });
-
- try {
- const chatData = await prepareGroupChat(
- identityType, userId, orderId, partnerUid, fadanPingtai || isCross, groupName, isCross
- );
-
- const realGroupId = chatData.groupId;
- let avatar = chatData.counterpartAvatar || chatData.groupAvatar || groupAvatar || '';
- if (avatar && !avatar.startsWith('http')) {
- avatar = app.globalData.ossImageUrl + avatar.replace(/^\//, '');
- }
-
- if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
- const meta = {
- name: chatData.counterpartName || chatData.groupName || groupName,
- avatar,
- orderId: chatData.orderId || orderId,
- orderDesc: chatData.orderDesc || '',
- orderZhuangtai: chatData.orderZhuangtai,
- dashouGoEasyId: chatData.dashouGoEasyId,
- partnerGoEasyId: chatData.partnerGoEasyId,
- dashouYonghuid: chatData.dashouYonghuid,
- partnerYonghuid: chatData.partnerYonghuid,
- dashouName: chatData.dashouName,
- partnerName: chatData.partnerName,
- dashouAvatar: chatData.dashouAvatar,
- partnerAvatar: chatData.partnerAvatar,
- counterpartId: chatData.counterpartId,
- counterpartYonghuid: chatData.counterpartYonghuid,
- };
- app.globalData.groupInfoMap[realGroupId] = meta;
- persistGroupMeta(app, realGroupId, meta);
-
- let imReady = false;
- try {
- await ensureIdentityConnection(identityType, userId);
- imReady = true;
- try {
- await withTimeout(
- new Promise((resolve, reject) => {
- wx.goEasy.im.subscribeGroup({
- groupIds: [realGroupId],
- onSuccess: () => resolve(),
- onFailed: (error) => reject(error),
- });
- }),
- 8000,
- '订阅群聊超时'
- );
- } catch (subErr) {
- console.warn('订阅群聊失败,仍尝试进入页面', subErr);
- }
- } catch (imErr) {
- console.warn('IM连接失败,仍进入聊天页由页面重连', imErr);
- }
-
- const uid = userId.replace(/^(Ds|Sj|Boss)/, '');
- const param = {
- groupId: realGroupId,
- orderId: chatData.orderId || orderId,
- groupName: chatData.counterpartName || chatData.groupName || groupName || '订单群聊',
- groupAvatar: avatar,
- isCross: chatData.isCross != null ? chatData.isCross : (isCross || 0),
- currentUserId: userId,
- currentUserName: app.globalData.currentUser?.name || `用户${uid}`,
- currentUserAvatar: app.globalData.currentUser?.avatar ||
- (wx.getStorageSync('touxiang') || (app.globalData.ossImageUrl + app.globalData.morentouxiang)),
- orderZhuangtai: chatData.orderZhuangtai,
- orderJine: chatData.orderJine,
- orderDesc: chatData.orderDesc || '',
- imReady,
- };
-
- wx.hideLoading();
- wx.navigateTo({
- url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)),
- fail: (navErr) => {
- wx.showToast({ title: formatError(navErr), icon: 'none', duration: 2500 });
- },
- });
- } catch (err) {
- wx.hideLoading();
- console.error('跳转群聊失败:', err);
- wx.showToast({ title: formatError(err), icon: 'none', duration: 2500 });
- throw err;
- }
- }
-}
-
-export default new ConnectionManager();
+/**
+ * 订单群聊跳转:与文赫一致,新群用打手+商家/老板配对 groupId;旧 group_{订单号} 会话保留
+ */
+import request from './request';
+import { persistGroupMeta } from './im-user.js';
+import { resolveLocalGroupId } from './group-chat.js';
+
+const app = getApp();
+
+function formatError(err) {
+ if (!err) return '进入聊天失败';
+ if (typeof err === 'string') return err;
+ if (err.message) return err.message;
+ if (err.msg) return String(err.msg);
+ if (err.content) return String(err.content);
+ if (err.code) return `连接失败(${err.code})`;
+ try {
+ return JSON.stringify(err);
+ } catch (e) {
+ return '进入聊天失败';
+ }
+}
+
+function withTimeout(promise, ms, errMsg) {
+ return new Promise((resolve, reject) => {
+ const timer = setTimeout(() => reject(new Error(errMsg || '操作超时')), ms);
+ promise
+ .then((v) => { clearTimeout(timer); resolve(v); })
+ .catch((e) => { clearTimeout(timer); reject(e); });
+ });
+}
+
+function mapIdentityForApi(identityType) {
+ if (identityType === 'dashou') return 'dashou';
+ if (identityType === 'shangjia') return 'shangjia';
+ return 'normal';
+}
+
+function validateIdentity(identityType, userId) {
+ if (identityType === 'dashou' && !userId.startsWith('Ds')) {
+ throw new Error('接单员 ID 必须以 Ds 开头');
+ }
+ if (identityType === 'shangjia' && !userId.startsWith('Sj')) {
+ throw new Error('商家 ID 必须以 Sj 开头');
+ }
+ if ((identityType === 'boss' || identityType === 'normal') && !userId.startsWith('Boss')) {
+ throw new Error('老板 ID 必须以 Boss 开头');
+ }
+}
+
+function getConnectedUserId() {
+ const fromState = app.globalData?.goEasyConnection?.userId;
+ const fromIm = wx.goEasy?.im?.userId;
+ return fromState || fromIm || '';
+}
+
+function isImConnected() {
+ const status = wx.goEasy?.getConnectionStatus?.() || 'disconnected';
+ return status === 'connected' || status === 'reconnected';
+}
+
+async function ensureIdentityConnection(identityType, userId) {
+ validateIdentity(identityType, userId);
+
+ app.globalData.currentRole = identityType;
+ wx.setStorageSync('currentRole', identityType);
+
+ const uid = userId.replace(/^(Ds|Sj|Boss)/, '');
+ const avatar = wx.getStorageSync('touxiang') ||
+ (app.globalData.ossImageUrl + app.globalData.morentouxiang);
+ app.globalData.currentUser = {
+ id: userId,
+ name: app.globalData.currentUser?.name || `用户${uid}`,
+ avatar,
+ };
+
+ if (!app.globalData.chatEnabled || !wx.goEasy?.im) {
+ throw new Error('聊天服务未就绪,请稍后重试');
+ }
+
+ if (isImConnected() && getConnectedUserId() === userId) {
+ return;
+ }
+
+ const connectedId = getConnectedUserId();
+ if (connectedId && connectedId !== userId && app.disconnectGoEasy) {
+ await app.disconnectGoEasy();
+ }
+
+ if (!app.connectWithIdentity) {
+ throw new Error('聊天功能未初始化');
+ }
+
+ await withTimeout(
+ app.connectWithIdentity(identityType, userId, true),
+ 12000,
+ 'IM连接超时'
+ );
+
+ if (!isImConnected()) {
+ throw new Error('IM连接失败,请检查网络后重试');
+ }
+}
+
+async function prepareGroupChat(identityType, userId, orderId, partnerUid, fadanPingtai, groupName, isCross) {
+ const orderIdStr = String(orderId || '').replace(/^group_/, '');
+ let chatData = null;
+
+ try {
+ const res = await withTimeout(
+ request({
+ url: '/dingdan/ltdhzb',
+ method: 'POST',
+ data: {
+ dingdan_id: orderIdStr,
+ identityType: mapIdentityForApi(identityType),
+ push_order_card: false,
+ },
+ }),
+ 12000,
+ '准备群聊超时'
+ );
+ const body = res?.data || {};
+ if ((body.code === 0 || body.code === 200) && body.data?.groupId) {
+ chatData = body.data;
+ } else if (body.msg) {
+ console.warn('ltdhzb:', body.msg);
+ }
+ } catch (e) {
+ console.warn('ltdhzb 请求失败,尝试本地配对 groupId', e);
+ }
+
+ if (!chatData?.groupId) {
+ const myUid = userId.replace(/^(Ds|Sj|Boss)/, '');
+ let localGroupId = resolveLocalGroupId(
+ identityType, myUid, partnerUid, fadanPingtai, orderIdStr, isCross
+ );
+ if (!localGroupId && orderIdStr) {
+ localGroupId = `group_${orderIdStr}`;
+ }
+ if (!localGroupId) {
+ throw new Error('无法确定群聊,请确认订单已接单且对方信息完整');
+ }
+ chatData = {
+ groupId: localGroupId,
+ orderId: orderIdStr,
+ groupName: groupName || '订单群聊',
+ isCross: isCross || fadanPingtai || 0,
+ };
+ try {
+ await request({
+ url: '/dingdan/ltdhzb',
+ method: 'POST',
+ data: {
+ dingdan_id: orderIdStr,
+ identityType: mapIdentityForApi(identityType),
+ push_order_card: false,
+ },
+ });
+ } catch (e) { /* ignore */ }
+ }
+
+ return chatData;
+}
+
+class ConnectionManager {
+ async connectAndChat(params) {
+ const { identityType, userId, targetUser } = params;
+ if (!identityType || !userId || !targetUser || !targetUser.id) {
+ throw new Error('参数不完整');
+ }
+
+ await ensureIdentityConnection(identityType, userId);
+
+ const currentUser = {
+ id: userId,
+ name: app.globalData.currentUser?.name || '用户',
+ avatar: app.globalData.currentUser?.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
+ };
+
+ const to = {
+ id: targetUser.id,
+ name: targetUser.name || '用户',
+ avatar: targetUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
+ };
+
+ wx.navigateTo({
+ url: '/pages/chat/chat?data=' + encodeURIComponent(JSON.stringify({ to, currentUser })),
+ });
+ }
+
+ async connectToGroupChat(params) {
+ const {
+ identityType, userId, orderId, groupName, groupAvatar, isCross,
+ partnerUid, fadanPingtai,
+ } = params;
+
+ if (!identityType || !userId || !orderId) {
+ throw new Error('参数不完整');
+ }
+
+ app.globalData.currentRole = identityType;
+ wx.setStorageSync('currentRole', identityType);
+
+ wx.showLoading({ title: '建立联系中...', mask: true });
+
+ try {
+ const chatData = await prepareGroupChat(
+ identityType, userId, orderId, partnerUid, fadanPingtai || isCross, groupName, isCross
+ );
+
+ const realGroupId = chatData.groupId;
+ let avatar = chatData.counterpartAvatar || chatData.groupAvatar || groupAvatar || '';
+ if (avatar && !avatar.startsWith('http')) {
+ avatar = app.globalData.ossImageUrl + avatar.replace(/^\//, '');
+ }
+
+ if (!app.globalData.groupInfoMap) app.globalData.groupInfoMap = {};
+ const meta = {
+ name: chatData.counterpartName || chatData.groupName || groupName,
+ avatar,
+ orderId: chatData.orderId || String(orderId).replace(/^group_/, ''),
+ orderDesc: chatData.orderDesc || '',
+ orderZhuangtai: chatData.orderZhuangtai,
+ orderJine: chatData.orderJine,
+ dashouGoEasyId: chatData.dashouGoEasyId,
+ partnerGoEasyId: chatData.partnerGoEasyId,
+ dashouYonghuid: chatData.dashouYonghuid,
+ partnerYonghuid: chatData.partnerYonghuid,
+ dashouName: chatData.dashouName,
+ partnerName: chatData.partnerName,
+ dashouAvatar: chatData.dashouAvatar,
+ partnerAvatar: chatData.partnerAvatar,
+ counterpartId: chatData.counterpartId,
+ counterpartYonghuid: chatData.counterpartYonghuid,
+ };
+ app.globalData.groupInfoMap[realGroupId] = meta;
+ persistGroupMeta(app, realGroupId, meta);
+
+ let imReady = false;
+ try {
+ await ensureIdentityConnection(identityType, userId);
+ imReady = true;
+ try {
+ await withTimeout(
+ new Promise((resolve, reject) => {
+ wx.goEasy.im.subscribeGroup({
+ groupIds: [realGroupId],
+ onSuccess: () => resolve(),
+ onFailed: (error) => reject(error),
+ });
+ }),
+ 8000,
+ '订阅群聊超时'
+ );
+ } catch (subErr) {
+ console.warn('订阅群聊失败,仍尝试进入页面', subErr);
+ }
+ } catch (imErr) {
+ console.warn('IM连接失败,仍进入聊天页由页面重连', imErr);
+ }
+
+ const uid = userId.replace(/^(Ds|Sj|Boss)/, '');
+ const param = {
+ groupId: realGroupId,
+ orderId: chatData.orderId || String(orderId).replace(/^group_/, ''),
+ groupName: chatData.counterpartName || chatData.groupName || groupName || '订单群聊',
+ groupAvatar: avatar,
+ isCross: chatData.isCross != null ? chatData.isCross : (isCross || 0),
+ currentUserId: userId,
+ currentUserName: app.globalData.currentUser?.name || `用户${uid}`,
+ currentUserAvatar: app.globalData.currentUser?.avatar ||
+ (wx.getStorageSync('touxiang') || (app.globalData.ossImageUrl + app.globalData.morentouxiang)),
+ orderZhuangtai: chatData.orderZhuangtai,
+ orderJine: chatData.orderJine,
+ orderDesc: chatData.orderDesc || '',
+ imReady,
+ };
+
+ wx.hideLoading();
+ wx.navigateTo({
+ url: '/pages/group-chat/group-chat?data=' + encodeURIComponent(JSON.stringify(param)),
+ fail: (navErr) => {
+ wx.showToast({ title: formatError(navErr), icon: 'none', duration: 2500 });
+ },
+ });
+ } catch (err) {
+ wx.hideLoading();
+ console.error('跳转群聊失败:', err);
+ wx.showToast({ title: formatError(err), icon: 'none', duration: 2500 });
+ throw err;
+ }
+ }
+}
+
+export default new ConnectionManager();
diff --git a/utils/zhiding-order.js b/utils/zhiding-order.js
new file mode 100644
index 0000000..e3a8981
--- /dev/null
+++ b/utils/zhiding-order.js
@@ -0,0 +1,53 @@
+import request from './request.js';
+import { getDefaultAvatarUrl } from './avatar.js';
+
+export const ZHIDING_HF_MAP = { 1: '想接', 2: '已婉拒', 3: '等会接' };
+
+export async function fetchMyZhidingOrders() {
+ try {
+ const res = await request({ url: '/dingdan/zdcx', method: 'POST', data: {} });
+ const body = res?.data;
+ if (body && (body.code === 200 || body.code === 0) && body.data) {
+ return body.data.list || [];
+ }
+ } catch (e) {
+ console.warn('fetchMyZhidingOrders failed', e);
+ }
+ return [];
+}
+
+export async function submitZhidingResponse(dingdanId, huifu, dengHuiNote = '') {
+ const res = await request({
+ url: '/dingdan/zdhf',
+ method: 'POST',
+ data: {
+ dingdan_id: dingdanId,
+ huifu,
+ deng_hui_note: dengHuiNote,
+ },
+ });
+ return res?.data;
+}
+
+export function processZhidingList(list, app, uid, highlightOrderId = '') {
+ const defAvatar = getDefaultAvatarUrl(app);
+ const oss = app.globalData?.ossImageUrl || '';
+ return (list || []).map((item) => {
+ let avatar = defAvatar;
+ if (item.zhiding_avatar) {
+ avatar = item.zhiding_avatar.startsWith('http') ? item.zhiding_avatar : oss + item.zhiding_avatar;
+ }
+ return {
+ ...item,
+ isMyZhiding: true,
+ zhiding_avatar_full: avatar,
+ zhiding_hf_text: item.zhiding_hf_text || ZHIDING_HF_MAP[item.zhiding_hf] || '',
+ isHighlighted: highlightOrderId && item.dingdan_id === highlightOrderId,
+ };
+ }).filter((item) => String(item.zhiding_uid) === String(uid));
+}
+
+/** 横幅只展示尚未回复的指定单(已等会接/不想接的不占横幅) */
+export function filterZhidingBannerList(list) {
+ return (list || []).filter((item) => !item.zhiding_hf);
+}