revert(order): 移除订单站内聊天,沟通统一走官方客服

- 删除 pages/order/chat、聊天接口/仓储方法/order_messages 建表与客户端 API

- 买家「联系大神」、大神「联系老板」、大神详情「在线客服」、组局「私聊」统一改为 openSupportSheet(读取后台 customer_service 配置的客服微信/热线)

- 保留派单实时通知与雷达/大厅轮询

- 114 套件/742 用例全绿;聊天接口线上已 404
这个提交包含在:
xcbyy
2026-10-22 09:10:16 +08:00
父节点 aca35db200
当前提交 be1c7be5d3
共有 12 个文件被更改,包括 95 次插入 和 685 次删除

查看文件

@@ -706,21 +706,6 @@ export async function getOrdersPool(options = {}) {
return [];
}
/**
* 订单站内聊天(仅交易双方)
*/
export async function getOrderChat(orderId) {
return http.get(`/orders/${encodeURIComponent(orderId)}/chat`);
}
export async function sendOrderMessage(orderId, content) {
return http.post(`/orders/${encodeURIComponent(orderId)}/chat/messages`, { content });
}
export async function markOrderChatRead(orderId) {
return http.post(`/orders/${encodeURIComponent(orderId)}/chat/read`, {});
}
/**
* 坐标反查行政区/地址(雷达顶部定位文案)
*/

60
pwuniapp/common/support.js 普通文件
查看文件

@@ -0,0 +1,60 @@
// pwuniapp/common/support.js
// 统一客服入口:平台沟通仅通过官方客服微信/热线,不在买卖双方之间直接建立联系。
import { getPlatformConfigs } from '@/common/api.js';
let cachedContact = null;
let cachedAt = 0;
export async function getSupportContact() {
if (cachedContact && Date.now() - cachedAt < 5 * 60 * 1000) {
return cachedContact;
}
let hotline = '';
let wechat = '';
try {
const configs = await getPlatformConfigs();
const cs = (configs && configs.customer_service) || {};
hotline = String(cs.hotline || '').trim();
wechat = String(cs.wechat || '').trim();
} catch (_) {
// 配置拉取失败时使用空值,避免展示写死的客服账号
}
cachedContact = { hotline, wechat };
cachedAt = Date.now();
return cachedContact;
}
export async function openSupportSheet() {
const contact = await getSupportContact();
const actions = [];
if (contact.wechat) actions.push({ key: 'wechat', label: `复制客服微信(${contact.wechat})` });
if (contact.hotline) actions.push({ key: 'hotline', label: `拨打客服热线(${contact.hotline})` });
if (actions.length === 0) {
uni.showToast({ title: '客服信息暂未配置,请稍后再试', icon: 'none' });
return;
}
uni.showActionSheet({
itemList: actions.map((a) => a.label),
success: (res) => {
const picked = actions[res.tapIndex];
if (!picked) return;
if (picked.key === 'wechat') {
uni.setClipboardData({
data: contact.wechat,
success: () => {
uni.showToast({ title: '客服微信已复制,请前往微信添加', icon: 'none', duration: 2200 });
}
});
} else if (picked.key === 'hotline') {
uni.makePhoneCall({
phoneNumber: contact.hotline.replace(/[^0-9+]/g, ''),
fail: () => {
uni.showToast({ title: `客服热线:${contact.hotline}`, icon: 'none' });
}
});
}
}
});
}

查看文件

@@ -102,15 +102,7 @@
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/order/chat",
"style": {
"navigationBarTitleText": "订单沟通",
"navigationBarBackgroundColor": "#0A0F1D",
"navigationBarTextStyle": "white",
"backgroundColor": "#0A0F1D"
}
},
{
"path": "pages/user/index",
"style": {

查看文件

@@ -80,7 +80,7 @@
</view>
<text class="creator-sub">同城组局召集人</text>
</view>
<button class="btn-chat btn-active" @click.stop="chatWithHost">私聊</button>
<button class="btn-chat btn-active" @click.stop="openSupportSheet">联系客服</button>
</view>
<view class="info-list">
@@ -422,6 +422,7 @@ import {
normalizeUserId
} from '@/common/api.js';
import { ICONS } from '@/common/icons.js';
import { openSupportSheet } from '@/common/support.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { ensureLoginOrRedirect } from '@/common/role.js';
import { preloadSubscribeTemplates, requestSubscribeFor } from '@/common/subscribe.js';
@@ -711,34 +712,6 @@ const openHostProfile = () => {
});
};
const chatWithHost = () => {
uni.showActionSheet({
itemList: [
'复制组局信息',
'查看发起人主页'
],
success: (res) => {
if (res.tapIndex === 0) {
const parts = [
`【组局】${activity.value.title || ''}`,
`时间:${activity.value.startTime || '待定'}`,
`地点:${activity.value.location || '待定'}`
];
const contact = String(activity.value.contactPhone || '').trim();
if (contact) parts.push(`联络方式:${contact}`);
uni.setClipboardData({
data: parts.join(' | '),
success: () => {
uni.showToast({ title: '组局信息已复制', icon: 'success' });
}
});
} else if (res.tapIndex === 1) {
openHostProfile();
}
}
});
};
const goToMyTicket = (code) => {
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) {
return;

查看文件

@@ -262,6 +262,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import SkeletonScreen from '@/components/SkeletonScreen.vue';
import { getCompanionDetail, getExpertReviews, replyOrderReview, submitReport as submitReportApi, checkFavorite, addFavorite as addFavApi, removeFavorite as removeFavApi } from '@/common/api.js';
import { ICONS } from '@/common/icons.js';
import { openSupportSheet } from '@/common/support.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { ensureLoginOrRedirect } from '@/common/role.js';
@@ -532,36 +533,7 @@ const toggleVoice = () => {
};
const chatOnline = () => {
uni.showActionSheet({
itemList: [`联系 7x24h 官方在线客服`],
success: (res) => {
if (res.tapIndex === 0) {
uni.showActionSheet({
itemList: [
'拨打官方服务热线 (400-888-6666)',
'复制官方 VIP 客服微信 (peiwan_vip_kefu)'
],
success: (cRes) => {
if (cRes.tapIndex === 0) {
uni.makePhoneCall({
phoneNumber: '4008886666',
fail: () => {
uni.showToast({ title: '服务热线: 400-888-6666', icon: 'none' });
}
});
} else if (cRes.tapIndex === 1) {
uni.setClipboardData({
data: 'peiwan_vip_kefu',
success: () => {
uni.showToast({ title: '官方客服微信号已复制', icon: 'success' });
}
});
}
}
});
}
}
});
void openSupportSheet();
};
const previewImage = (index) => {

查看文件

@@ -466,6 +466,7 @@
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { ICONS } from '@/common/icons.js';
import { openSupportSheet } from '@/common/support.js';
import { isExpertCertified } from '@/common/role.js';
import { getCurrentThemeKey } from '@/common/theme.js';
import {
@@ -1024,42 +1025,8 @@ const handleRejectOrder = (order) => {
};
const chatWithBuyer = (order) => {
const orderId = order && order.id;
if (orderId) {
uni.navigateTo({ url: `/pages/order/chat?orderId=${encodeURIComponent(orderId)}` });
return;
}
uni.showActionSheet({
itemList: ['联系 7x24h 官方客服'],
success: (res) => {
if (res.tapIndex === 0) {
uni.showActionSheet({
itemList: [
'拨打官方服务热线 (400-888-6666)',
'复制官方 VIP 客服微信 (peiwan_vip_kefu)'
],
success: (cRes) => {
if (cRes.tapIndex === 0) {
uni.makePhoneCall({
phoneNumber: '4008886666',
fail: () => {
uni.showToast({ title: '服务热线: 400-888-6666', icon: 'none' });
}
});
} else if (cRes.tapIndex === 1) {
uni.setClipboardData({
data: 'peiwan_vip_kefu',
success: () => {
uni.showToast({ title: '官方客服微信号已复制', icon: 'success' });
}
});
}
}
});
}
}
});
const chatWithBuyer = () => {
void openSupportSheet();
};
const completeOrder = (id) => {

查看文件

@@ -1,320 +0,0 @@
<template>
<view class="chat-page">
<view class="chat-header">
<image class="peer-avatar" :src="peer.avatar || ''" mode="aspectFill" />
<view class="peer-meta">
<text class="peer-name">{{ peer.nickname || '对方' }}</text>
<text class="order-sub">{{ order.title || '订单沟通' }}{{ order.orderNo ? ` · ${order.orderNo}` : '' }}</text>
</view>
</view>
<scroll-view class="chat-body" scroll-y :scroll-into-view="scrollTarget">
<view v-if="loading" class="chat-hint">正在加载会话…</view>
<view v-else-if="messages.length === 0" class="chat-hint">暂无消息,先打个招呼吧</view>
<view
v-for="m in messages"
:key="m.id"
:id="`msg-${m.id}`"
class="msg-row"
:class="{ mine: isMine(m) }"
>
<image v-if="!isMine(m)" class="msg-avatar" :src="peer.avatar || ''" mode="aspectFill" />
<view class="bubble-wrap">
<view class="msg-bubble">{{ m.content }}</view>
<text class="msg-time">{{ formatTime(m.createdAt) }}</text>
</view>
</view>
<view id="chat-bottom" class="chat-bottom-anchor"></view>
</scroll-view>
<view class="chat-input-bar">
<input
class="chat-input"
v-model="draft"
confirm-type="send"
:maxlength="500"
:disabled="!chatEnabled"
placeholder="输入消息(请勿发送手机号/微信号)"
placeholder-class="chat-ph"
@confirm="send"
/>
<button class="chat-send btn-active" :disabled="sending || !draft.trim() || !chatEnabled" @click="send">
{{ sending ? '发送中' : '发送' }}
</button>
</view>
</view>
</template>
<script setup>
import { ref, onUnmounted } from 'vue';
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import { getOrderChat, sendOrderMessage, normalizeUserId } from '@/common/api.js';
const orderId = ref('');
const order = ref({});
const peer = ref({});
const messages = ref([]);
const draft = ref('');
const loading = ref(true);
const sending = ref(false);
const chatEnabled = ref(true);
const scrollTarget = ref('chat-bottom');
let pollTimer = null;
let polling = false;
const isMine = (m) => Number(m.senderId) === Number(normalizeUserId());
const formatTime = (val) => {
if (!val) return '';
const d = new Date(val);
if (isNaN(d.getTime())) return '';
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
return `${hh}:${mm}`;
};
const scrollToBottom = () => {
scrollTarget.value = '';
setTimeout(() => {
scrollTarget.value = 'chat-bottom';
}, 30);
};
const loadChat = async (first = false) => {
if (polling || !orderId.value) return;
polling = true;
try {
const data = await getOrderChat(orderId.value);
if (data) {
order.value = data.order || {};
peer.value = data.peer || {};
const list = Array.isArray(data.messages) ? data.messages : [];
if (list.length !== messages.value.length || first) {
messages.value = list;
scrollToBottom();
}
chatEnabled.value = true;
}
} catch (err) {
if (first) {
chatEnabled.value = false;
uni.showToast({ title: err.message || '会话暂不可用', icon: 'none' });
}
} finally {
polling = false;
if (first) loading.value = false;
}
};
const startPolling = () => {
stopPolling();
pollTimer = setInterval(() => {
void loadChat(false);
}, 3000);
};
const stopPolling = () => {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
};
const send = async () => {
const content = String(draft.value || '').trim();
if (!content || sending.value || !chatEnabled.value) return;
sending.value = true;
try {
const msg = await sendOrderMessage(orderId.value, content);
if (msg && msg.id) {
messages.value = [...messages.value, msg];
draft.value = '';
scrollToBottom();
}
} catch (err) {
uni.showToast({ title: err.message || '发送失败', icon: 'none' });
} finally {
sending.value = false;
}
};
onLoad((query) => {
orderId.value = query && query.orderId ? String(query.orderId) : '';
if (!orderId.value) {
uni.showToast({ title: '缺少订单信息', icon: 'none' });
return;
}
void loadChat(true);
});
onShow(() => {
if (orderId.value) {
void loadChat(false);
startPolling();
}
});
onHide(() => {
stopPolling();
});
onUnmounted(() => {
stopPolling();
});
</script>
<style lang="scss">
page {
height: 100%;
}
.chat-page {
height: 100vh;
display: flex;
flex-direction: column;
background: #0A0F1D;
color: #F8FAFC;
}
.chat-header {
display: flex;
align-items: center;
gap: 16rpx;
padding: 20rpx 28rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
background: #111827;
.peer-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
}
.peer-meta {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.peer-name {
font-size: 28rpx;
font-weight: 600;
}
.order-sub {
font-size: 22rpx;
color: #94A3B8;
}
}
.chat-body {
flex: 1;
padding: 24rpx 24rpx 12rpx;
box-sizing: border-box;
}
.chat-hint {
text-align: center;
font-size: 24rpx;
color: #64748B;
padding: 60rpx 0;
}
.chat-bottom-anchor {
height: 1rpx;
}
.msg-row {
display: flex;
align-items: flex-start;
gap: 12rpx;
margin-bottom: 20rpx;
.msg-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.08);
}
.bubble-wrap {
max-width: 70%;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.msg-bubble {
padding: 16rpx 22rpx;
border-radius: 20rpx;
border-top-left-radius: 6rpx;
background: #1E293B;
color: #E2E8F0;
font-size: 28rpx;
line-height: 1.5;
word-break: break-all;
}
.msg-time {
font-size: 20rpx;
color: #64748B;
}
&.mine {
justify-content: flex-end;
.bubble-wrap {
align-items: flex-end;
}
.msg-bubble {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #FFFFFF;
border-radius: 20rpx;
border-top-right-radius: 6rpx;
}
}
}
.chat-input-bar {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid rgba(255, 255, 255, 0.08);
background: #111827;
.chat-input {
flex: 1;
height: 76rpx;
padding: 0 24rpx;
border-radius: 38rpx;
background: #1E293B;
color: #F8FAFC;
font-size: 28rpx;
}
.chat-ph {
color: #64748B;
}
.chat-send {
margin: 0;
padding: 0 30rpx;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
font-size: 26rpx;
color: #FFFFFF;
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
&[disabled] {
opacity: 0.5;
}
}
}
</style>

查看文件

@@ -359,6 +359,7 @@ import {
normalizeUserId
} from '@/common/api.js';
import { ICONS } from '@/common/icons.js';
import { openSupportSheet } from '@/common/support.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { ensureLoginOrRedirect } from '@/common/role.js';
import { getNavBarInfo } from '@/common/nav.js';
@@ -546,43 +547,8 @@ const payOrder = async (id) => {
}
};
const contactExpert = (order) => {
const orderId = order && order.id;
const chatAvailable = Boolean(orderId) && ['IN_SERVICE', '服务中', '履约中', '待确认', '已完成'].includes(String(order.rawStatus || order.status || ''));
if (chatAvailable) {
uni.navigateTo({ url: `/pages/order/chat?orderId=${encodeURIComponent(orderId)}` });
return;
}
uni.showActionSheet({
itemList: ['联系 7x24h 官方电竞客服'],
success: (res) => {
if (res.tapIndex === 0) {
uni.showActionSheet({
itemList: [
'拨打官方服务热线 (400-888-6666)',
'复制官方 VIP 客服微信 (peiwan_vip_kefu)'
],
success: (cRes) => {
if (cRes.tapIndex === 0) {
uni.makePhoneCall({
phoneNumber: '4008886666',
fail: () => {
uni.showToast({ title: '服务热线: 400-888-6666', icon: 'none' });
}
});
} else if (cRes.tapIndex === 1) {
uni.setClipboardData({
data: 'peiwan_vip_kefu',
success: () => {
uni.showToast({ title: '官方客服微信号已复制', icon: 'success' });
}
});
}
}
});
}
}
});
const contactExpert = () => {
void openSupportSheet();
};
// 15分钟待支付倒计时动态计算