统一排行榜对齐星雀UI,页面资源后台配置实时刷新
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import GoEasy from '../static/lib/goeasy-2.13.24.esm.min';
|
||||
import { jianquanxian } from './imAuth/jianquanxian';
|
||||
import { parseOrderCardText } from './group-chat.js';
|
||||
import { persistGroupMeta } from './im-user.js';
|
||||
import { persistGroupMeta, getFreshImUser } from './im-user.js';
|
||||
|
||||
let _globalPrivateHandler = null;
|
||||
let _globalGroupHandler = null;
|
||||
@@ -28,9 +28,27 @@ function initGlobalMessageSystem(app) {
|
||||
loadUserSettings(app);
|
||||
initNetworkListener(app);
|
||||
initAppStateListener(app);
|
||||
checkAndRestoreConnection(app);
|
||||
bindGoEasyLifecycle(app);
|
||||
setupEventSystem(app);
|
||||
setupMessageListeners(app);
|
||||
// 监听器在连接成功 / onShow 时注册,不在 IM 未就绪时空绑
|
||||
checkAndRestoreConnection(app);
|
||||
}
|
||||
|
||||
function bindGoEasyLifecycle(app) {
|
||||
if (!wx.goEasy || app._goEasyLifecycleBound) return;
|
||||
app._goEasyLifecycleBound = true;
|
||||
try {
|
||||
wx.goEasy.on('connected', () => {
|
||||
app.globalData.goEasyConnection.status = 'connected';
|
||||
setupMessageListeners(app);
|
||||
loadConversations(app);
|
||||
});
|
||||
wx.goEasy.on('disconnected', () => {
|
||||
app.globalData.goEasyConnection.status = 'disconnected';
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('绑定 GoEasy 生命周期失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentGoEasyUserId() {
|
||||
@@ -78,12 +96,8 @@ function initNetworkListener(app) {
|
||||
|
||||
function initAppStateListener(app) {
|
||||
wx.onAppShow(() => {
|
||||
ensureConnection(app);
|
||||
updateCurrentPageState(app);
|
||||
// ✅ 连接正常时,主动刷一次未读角标
|
||||
if (wx.goEasy.getConnectionStatus && wx.goEasy.getConnectionStatus() === 'connected') {
|
||||
loadConversations(app);
|
||||
}
|
||||
ensureConnection(app);
|
||||
});
|
||||
wx.onAppHide(() => {
|
||||
pauseHeartbeat(app);
|
||||
@@ -101,25 +115,34 @@ function checkAndRestoreConnection(app) {
|
||||
const validityHours = app.globalData.goEasyConnection.config.cacheValidityHours || 12;
|
||||
if (hoursDiff > validityHours) {
|
||||
clearSavedConnection(app);
|
||||
} else {
|
||||
ensureConnection(app);
|
||||
}
|
||||
}
|
||||
ensureConnection(app);
|
||||
} catch (error) {
|
||||
console.warn('检查保存的连接信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventSystem(app) {
|
||||
app.globalData.eventListeners = {};
|
||||
if (!app.globalData.eventListeners) {
|
||||
app.globalData.eventListeners = {};
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 修改点:不依赖 saved 缓存,只要连接状态正常就刷新角标
|
||||
function isImConnected() {
|
||||
if (!wx.goEasy?.getConnectionStatus) return false;
|
||||
const s = wx.goEasy.getConnectionStatus();
|
||||
return s === 'connected' || s === 'reconnected';
|
||||
}
|
||||
|
||||
// 进入小程序即尝试连接并监听(不依赖历史缓存)
|
||||
function ensureConnection(app) {
|
||||
const connectionStatus = wx.goEasy.getConnectionStatus ? wx.goEasy.getConnectionStatus() : 'disconnected';
|
||||
if (connectionStatus === 'connected' || connectionStatus === 'reconnected') {
|
||||
if (!wx.goEasy?.connect) return;
|
||||
if (app.globalData.chatEnabled === false) return;
|
||||
|
||||
if (isImConnected()) {
|
||||
setupMessageListeners(app);
|
||||
loadConversations(app); // 每次进入前台都刷新未读
|
||||
loadConversations(app);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,9 +229,21 @@ function disconnectGoEasy(app) {
|
||||
}
|
||||
|
||||
async function connectWithIdentity(app, identityType, userId, isAutoRestore = false) {
|
||||
const quanxian = await jianquanxian(app);
|
||||
if (!quanxian.allowed) {
|
||||
return Promise.reject(new Error(quanxian.reason || '无聊天权限'));
|
||||
if (!wx.goEasy?.connect) {
|
||||
return Promise.reject(new Error('聊天服务未就绪'));
|
||||
}
|
||||
|
||||
// 冷启动/后台恢复:先连上 IM 才能收消息;完整鉴权留给消息页/发消息
|
||||
if (!isAutoRestore) {
|
||||
const quanxian = await jianquanxian(app);
|
||||
if (!quanxian.allowed) {
|
||||
return Promise.reject(new Error(quanxian.reason || '无聊天权限'));
|
||||
}
|
||||
} else {
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (!uid) {
|
||||
return Promise.reject(new Error('未登录'));
|
||||
}
|
||||
}
|
||||
|
||||
app.globalData.goEasyConnection.autoReconnect = true;
|
||||
@@ -302,7 +337,7 @@ function connectForCurrentRole(app) {
|
||||
if (currentUserId) {
|
||||
wx.goEasy.disconnect({ onSuccess: () => {}, onFailed: () => {} });
|
||||
}
|
||||
connectWithIdentity(app, role, targetUserId, false);
|
||||
connectWithIdentity(app, role, targetUserId, true);
|
||||
}
|
||||
|
||||
async function switchRoleAndReconnect(app, newRole) {
|
||||
@@ -577,7 +612,7 @@ function cacheMessage(app, message) {
|
||||
}
|
||||
|
||||
function loadConversations(app) {
|
||||
if (!getCurrentGoEasyUserId()) return;
|
||||
if (!getCurrentGoEasyUserId() || !wx.goEasy?.im?.latestConversations) return;
|
||||
wx.goEasy.im.latestConversations({
|
||||
onSuccess: (result) => {
|
||||
if (result.unreadTotal !== undefined) {
|
||||
|
||||
13296
miniprogram/utils/cos-wx-sdk-v5.js
Normal file
13296
miniprogram/utils/cos-wx-sdk-v5.js
Normal file
File diff suppressed because one or more lines are too long
@@ -8,6 +8,39 @@ const ZHUANGTAI_MAP = {
|
||||
5: '已退款', 6: '退款失败', 7: '指定中', 8: '结算中',
|
||||
};
|
||||
|
||||
export function getZhuangtaiText(zhuangtai) {
|
||||
if (zhuangtai == null || zhuangtai === '') return '';
|
||||
const n = Number(zhuangtai);
|
||||
return ZHUANGTAI_MAP[n] || ZHUANGTAI_MAP[zhuangtai] || '';
|
||||
}
|
||||
|
||||
export function buildDashouShangjiaGroupId(dashouUid, shangjiaUid) {
|
||||
return `group_Ds${dashouUid}_Sj${shangjiaUid}`;
|
||||
}
|
||||
|
||||
export function buildDashouBossGroupId(dashouUid, bossUid) {
|
||||
return `group_Ds${dashouUid}_Boss${bossUid}`;
|
||||
}
|
||||
|
||||
export function resolveLocalGroupId(identityType, myUid, partnerUid, fadanPingtai, orderId, isCross) {
|
||||
const cross = isCross === 1 || isCross === true || String(isCross) === '1';
|
||||
if (cross && orderId) {
|
||||
return `group_${orderId}`;
|
||||
}
|
||||
if (!myUid) return orderId ? `group_${orderId}` : null;
|
||||
if (!partnerUid) return orderId ? `group_${orderId}` : null;
|
||||
if (identityType === 'shangjia') {
|
||||
return buildDashouShangjiaGroupId(partnerUid, myUid);
|
||||
}
|
||||
if (identityType === 'dashou') {
|
||||
if (fadanPingtai === 2 || String(fadanPingtai) === '2') {
|
||||
return buildDashouShangjiaGroupId(myUid, partnerUid);
|
||||
}
|
||||
return buildDashouBossGroupId(myUid, partnerUid);
|
||||
}
|
||||
return orderId ? `group_${orderId}` : null;
|
||||
}
|
||||
|
||||
export function isPairGroupId(groupId) {
|
||||
return /^group_Ds\w+_(Sj|Boss)\w+$/.test(groupId || '');
|
||||
}
|
||||
@@ -108,6 +141,8 @@ export function parseOrderCardText(text) {
|
||||
}
|
||||
}
|
||||
|
||||
import { getChatImageUrl } from '../static/lib/utils.js';
|
||||
|
||||
export function normalizeGroupMessage(msg) {
|
||||
if (!msg) return msg;
|
||||
if (msg.type === 'text' && msg.payload && msg.payload.text) {
|
||||
@@ -117,6 +152,26 @@ export function normalizeGroupMessage(msg) {
|
||||
msg.payload = card;
|
||||
}
|
||||
}
|
||||
if (msg.type === 'custom' && msg.payload) {
|
||||
const inner = msg.payload.payload || msg.payload;
|
||||
if (inner && (inner.orderId || inner.dingdan_id)) {
|
||||
msg.type = 'order';
|
||||
msg.payload = {
|
||||
orderId: inner.orderId || inner.dingdan_id,
|
||||
jieshao: inner.jieshao || '',
|
||||
jine: inner.jine || '',
|
||||
beizhu: inner.beizhu || '',
|
||||
zhuangtai: inner.zhuangtai,
|
||||
nicheng: inner.nicheng || '',
|
||||
create_time: inner.create_time || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (msg.type === 'image') {
|
||||
const url = getChatImageUrl(msg);
|
||||
msg.payload = { ...(msg.payload || {}), url };
|
||||
msg.imageUrl = url;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
51
miniprogram/utils/imAuth/dashoujianquan.js
Normal file
51
miniprogram/utils/imAuth/dashoujianquan.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// utils/imAuth/dashoujianquan.js
|
||||
const imRequest = require('./imRequest.js');
|
||||
|
||||
/**
|
||||
* 打手 IM 权限检测
|
||||
* POST /yonghu/dshqltqx
|
||||
* 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
|
||||
*/
|
||||
async function dashoujianquan(app) {
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (!uid) return { allowed: false, reason: '未登录' };
|
||||
|
||||
try {
|
||||
const res = await imRequest({
|
||||
url: '/yonghu/dshqltqx',
|
||||
method: 'POST',
|
||||
data: { uid }
|
||||
});
|
||||
|
||||
if (res && res.data && res.data.code === 0) {
|
||||
const allow = res.data.allow;
|
||||
if (allow === 1) {
|
||||
return { allowed: true };
|
||||
} else {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '当前身份已不支持消息聊天功能,请充值会员或联系管理员后尝试',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 2100));
|
||||
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));
|
||||
return { allowed: false, reason: '鉴权请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { dashoujianquan };
|
||||
67
miniprogram/utils/imAuth/imRequest.js
Normal file
67
miniprogram/utils/imAuth/imRequest.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// utils/imAuth/imRequest.js
|
||||
// 专用于 IM 鉴权模块的请求封装
|
||||
// 与 utils/request.js 功能完全一致,但不依赖 getApp(),API 域名硬编码
|
||||
const API_BASE_URL = 'https://6ccy.top/hqhd';
|
||||
|
||||
function imRequest(options) {
|
||||
const token = wx.getStorageSync('token');
|
||||
const header = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.header,
|
||||
};
|
||||
if (token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: API_BASE_URL + options.url,
|
||||
method: options.method,
|
||||
data: options.data || {},
|
||||
header,
|
||||
success: async (res) => {
|
||||
if (res.statusCode === 401 || res.statusCode === 403) {
|
||||
// 如果全局有登录 Promise 在等待,则等待完成后重试
|
||||
const app = getApp();
|
||||
if (app && app.globalData && app.globalData.loginPromise) {
|
||||
try {
|
||||
await app.globalData.loginPromise;
|
||||
const retryRes = await imRequest(options);
|
||||
resolve(retryRes);
|
||||
} catch (err) {
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.switchTab({ url: '/pages/wode/wode' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 常规未登录提示
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.switchTab({ url: '/pages/wode/wode' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
return;
|
||||
}
|
||||
// 正常响应
|
||||
resolve(res);
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = imRequest;
|
||||
58
miniprogram/utils/imAuth/jianquanxian.js
Normal file
58
miniprogram/utils/imAuth/jianquanxian.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// utils/imAuth/jianquanxian.js
|
||||
// 原:require('./laobanjianquan')
|
||||
// 改:
|
||||
const { laobanjianquan } = require('./laobanjianquan.js');
|
||||
const { dashoujianquan } = require('./dashoujianquan.js');
|
||||
|
||||
/**
|
||||
* IM 长连接权限统一入口
|
||||
* 按顺序进行:
|
||||
* 1. 检查本地 token/uid 是否存在
|
||||
* 2. 若不存在,强制断开已有连接,弹窗提示未登录(强制阅读 2 秒)
|
||||
* 3. 若存在,根据当前角色调用具体的鉴权模块
|
||||
*
|
||||
* @param {Object} app 全局 App 实例
|
||||
* @returns {Promise<{ allowed: boolean, reason?: string }>}
|
||||
*/
|
||||
async function jianquanxian(app) {
|
||||
// 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));
|
||||
return { allowed: false, reason: '未登录' };
|
||||
}
|
||||
|
||||
// 2. 获取当前选中的角色
|
||||
const role = app.globalData.currentRole || 'normal';
|
||||
|
||||
// 3. 根据角色分发到具体鉴权模块
|
||||
switch (role) {
|
||||
case 'normal': // 点单老板
|
||||
return laobanjianquan(app);
|
||||
|
||||
case 'dashou': // 打手
|
||||
return dashoujianquan(app);
|
||||
|
||||
// 其他角色暂未细分,统一放行(后续可新增对应文件)
|
||||
case 'shangjia':
|
||||
case 'guanshi':
|
||||
case 'zuzhang':
|
||||
case 'kaoheguan':
|
||||
return { allowed: true };
|
||||
|
||||
default:
|
||||
return { allowed: false, reason: '未知角色' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { jianquanxian };
|
||||
51
miniprogram/utils/imAuth/laobanjianquan.js
Normal file
51
miniprogram/utils/imAuth/laobanjianquan.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// utils/imAuth/laobanjianquan.js
|
||||
const imRequest = require('./imRequest.js');
|
||||
|
||||
/**
|
||||
* 老板(点单用户)IM 权限检测
|
||||
* POST /yonghu/lbhqltqx
|
||||
* 后端返回 { code: 0, allow: 1 } 或 { code: 0, allow: 0 }
|
||||
*/
|
||||
async function laobanjianquan(app) {
|
||||
const uid = wx.getStorageSync('uid');
|
||||
if (!uid) return { allowed: false, reason: '未登录' };
|
||||
|
||||
try {
|
||||
const res = await imRequest({
|
||||
url: '/yonghu/lbhqltqx',
|
||||
method: 'POST',
|
||||
data: { uid }
|
||||
});
|
||||
|
||||
if (res && res.data && res.data.code === 0) {
|
||||
const allow = res.data.allow;
|
||||
if (allow === 1) {
|
||||
return { allowed: true };
|
||||
} else {
|
||||
await app.disconnectGoEasy();
|
||||
wx.showToast({
|
||||
title: '当前身份已不支持使用消息功能',
|
||||
icon: 'none',
|
||||
duration: 1000,
|
||||
mask: true
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 1100));
|
||||
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));
|
||||
return { allowed: false, reason: '鉴权请求失败' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { laobanjianquan };
|
||||
74
miniprogram/utils/imageUrl.js
Normal file
74
miniprogram/utils/imageUrl.js
Normal file
@@ -0,0 +1,74 @@
|
||||
// /utils/imageUrl.js - 统一的COS图片路径管理
|
||||
const app = getApp();
|
||||
|
||||
/**
|
||||
* 获取完整的COS图片URL
|
||||
* @param {string} path - 图片相对路径,如 'a_long/images/common/arrow-down.png'
|
||||
* @returns {string} 完整的URL
|
||||
*/
|
||||
export function getImageUrl(path) {
|
||||
if (!app || !app.globalData || !app.globalData.ossImageUrl) {
|
||||
console.warn('⚠️ App.globalData.ossImageUrl 未初始化');
|
||||
return path; // 返回原路径作为fallback
|
||||
}
|
||||
|
||||
// 如果已经是完整URL,直接返回
|
||||
if (path.startsWith('http')) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// 拼接完整URL
|
||||
return app.globalData.ossImageUrl + path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取常用图片的URL
|
||||
*/
|
||||
export const imageUrls = {
|
||||
// 身份相关
|
||||
identityIcon: 'a_long/images/identity/identity-icon.png',
|
||||
arrowDown: 'a_long/images/common/arrow-down.png',
|
||||
bossIcon: 'a_long/images/identity/boss-icon.png',
|
||||
dashouIcon: 'a_long/images/identity/dashou-icon.png',
|
||||
shangjiaIcon: 'a_long/images/identity/shangjia-icon.png',
|
||||
|
||||
// 公共图标
|
||||
actionIcon: 'a_long/images/common/action.png',
|
||||
emptyMessage: 'a_long/images/common/empty-message.png',
|
||||
|
||||
// 通知相关
|
||||
notificationOn: 'a_long/images/notifications/notification-on.png',
|
||||
notificationOff: 'a_long/images/notifications/notification-off.png',
|
||||
|
||||
// TabBar图标
|
||||
tabbarHome: 'a_long/images/tabbar/home.png',
|
||||
tabbarHomeActive: 'a_long/images/tabbar/home-active.png',
|
||||
tabbarCategory: 'a_long/images/tabbar/category.png',
|
||||
tabbarCategoryActive: 'a_long/images/tabbar/category-active.png',
|
||||
tabbarMessage: 'a_long/images/tabbar/message.png',
|
||||
tabbarMessageActive: 'a_long/images/tabbar/message-active.png',
|
||||
tabbarProfile: 'a_long/images/tabbar/profile.png',
|
||||
tabbarProfileActive: 'a_long/images/tabbar/profile-active.png',
|
||||
|
||||
// 用户相关
|
||||
defaultAvatar: 'a_long/images/common/default-avatar.png'
|
||||
};
|
||||
|
||||
/**
|
||||
* 预加载常用图片
|
||||
*/
|
||||
export function preloadCommonImages() {
|
||||
const images = Object.values(imageUrls).map(path => getImageUrl(path));
|
||||
|
||||
images.forEach(src => {
|
||||
wx.getImageInfo({
|
||||
src: src,
|
||||
success: () => {
|
||||
console.log('✅ 图片预加载成功:', src);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.warn('⚠️ 图片预加载失败:', src, err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,11 @@ const app = getApp();
|
||||
import { formatDate } from '../static/lib/utils';
|
||||
import request from './request.js';
|
||||
import { getFreshImUser } from './im-user.js';
|
||||
import { sendGoEasyImage } from './chatImageSend.js';
|
||||
|
||||
function isCrossPlatformOrder(isCross) {
|
||||
return isCross === 1 || isCross === true || String(isCross) === '1';
|
||||
}
|
||||
|
||||
function sendGroupMessage(options) {
|
||||
const { msgData, onSuccess, onSendStart } = options;
|
||||
@@ -20,8 +25,7 @@ function sendGroupMessage(options) {
|
||||
const localMsg = buildLocalMessage(msgData);
|
||||
if (onSendStart) onSendStart(localMsg);
|
||||
|
||||
// 只有 isCross 明确为 0 才走前端 SDK,其余一律走后端
|
||||
if (isCross === 0) {
|
||||
if (!isCrossPlatformOrder(isCross)) {
|
||||
sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross, groupName, groupAvatar);
|
||||
return;
|
||||
}
|
||||
@@ -32,7 +36,7 @@ function buildLocalMessage(data) {
|
||||
const { type, text, filePath, orderPayload, currentUser, groupId } = data;
|
||||
const now = Date.now();
|
||||
const base = {
|
||||
messageId: `${type}-${now}`,
|
||||
messageId: `local-${type}-${now}`,
|
||||
timestamp: now,
|
||||
senderId: currentUser.id,
|
||||
groupId: groupId,
|
||||
@@ -45,7 +49,7 @@ function buildLocalMessage(data) {
|
||||
};
|
||||
|
||||
if (type === 'text') return { ...base, type: 'text', payload: { text } };
|
||||
if (type === 'image') return { ...base, type: 'image', payload: { url: filePath } };
|
||||
if (type === 'image') return { ...base, type: 'image', payload: { url: filePath }, imageUrl: filePath };
|
||||
if (type === 'order') return { ...base, type: 'order', payload: orderPayload };
|
||||
return base;
|
||||
}
|
||||
@@ -66,6 +70,7 @@ function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross,
|
||||
dashouAvatar: groupMeta.dashouAvatar,
|
||||
partnerAvatar: groupMeta.partnerAvatar,
|
||||
orderDesc: groupMeta.orderDesc,
|
||||
orderZhuangtai: groupMeta.orderZhuangtai,
|
||||
};
|
||||
|
||||
const to = {
|
||||
@@ -74,18 +79,35 @@ function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross,
|
||||
data: toData
|
||||
};
|
||||
|
||||
if (localMsg.type === 'image') {
|
||||
sendGoEasyImage({
|
||||
file: localMsg.payload.url,
|
||||
to,
|
||||
onSuccess: (res) => {
|
||||
const serverMsg = res && res.content;
|
||||
if (serverMsg && onSuccess) {
|
||||
onSuccess(localMsg.messageId, 'success', serverMsg);
|
||||
} else if (onSuccess) {
|
||||
onSuccess(localMsg.messageId, 'success');
|
||||
}
|
||||
},
|
||||
onFailed: () => { if (onSuccess) onSuccess(localMsg.messageId, 'failed'); }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let message;
|
||||
if (localMsg.type === 'text') {
|
||||
message = wx.goEasy.im.createTextMessage({ text: localMsg.payload.text, to });
|
||||
} else if (localMsg.type === 'image') {
|
||||
message = wx.goEasy.im.createImageMessage({ file: localMsg.payload.url, to });
|
||||
} else if (localMsg.type === 'order') {
|
||||
message = wx.goEasy.im.createCustomMessage({ type: 'order', payload: localMsg.payload, to });
|
||||
}
|
||||
|
||||
wx.goEasy.im.sendMessage({
|
||||
message,
|
||||
onSuccess: () => { if (onSuccess) onSuccess(localMsg.messageId, 'success'); },
|
||||
onSuccess: (res) => {
|
||||
if (onSuccess) onSuccess(localMsg.messageId, 'success', res && res.content);
|
||||
},
|
||||
onFailed: () => { if (onSuccess) onSuccess(localMsg.messageId, 'failed'); }
|
||||
});
|
||||
}
|
||||
@@ -93,18 +115,17 @@ function sendViaSDK(localMsg, currentUser, groupId, onSuccess, orderId, isCross,
|
||||
function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) {
|
||||
const apiUrl = '/dingdan/kptxxfs';
|
||||
|
||||
// 🔥 实时获取当前身份,不再依赖外部传入的 currentUser.id
|
||||
const uid = wx.getStorageSync('uid');
|
||||
const role = app.globalData.currentRole || 'normal';
|
||||
const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' };
|
||||
const identityType = role === 'dashou' ? 'dashou' : (role === 'shangjia' ? 'shangjia' : 'boss');
|
||||
const prefixMap = { normal: 'Boss', dashou: 'Ds', shangjia: 'Sj', guanshi: 'Gs', zuzhang: 'Zz' };
|
||||
const prefix = prefixMap[role] || 'Boss';
|
||||
const senderId = prefix + uid;
|
||||
const freshUser = getFreshImUser(app, role, uid);
|
||||
const params = {
|
||||
orderId: orderId,
|
||||
groupId: groupId,
|
||||
identityType: identityType, // 告诉后端当前是什么身份
|
||||
identityType: identityType,
|
||||
senderId: senderId,
|
||||
senderName: freshUser.name || currentUser.name || ('用户' + uid),
|
||||
senderAvatar: freshUser.avatar || currentUser.avatar || (app.globalData.ossImageUrl + app.globalData.morentouxiang),
|
||||
@@ -135,7 +156,6 @@ function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 图片消息
|
||||
if (localMsg.type === 'image') {
|
||||
const token = wx.getStorageSync('token');
|
||||
const formData = {
|
||||
@@ -169,4 +189,4 @@ function sendViaBackend(localMsg, currentUser, groupId, orderId, onSuccess) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { sendGroupMessage };
|
||||
module.exports = { sendGroupMessage };
|
||||
|
||||
76
miniprogram/utils/page-assets.js
Normal file
76
miniprogram/utils/page-assets.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/** 从 globalData.pageAssets / paihangAssets 拼 OSS 完整 URL */
|
||||
|
||||
export function buildOssUrl(path) {
|
||||
if (!path) return ''
|
||||
if (String(path).startsWith('http')) return path
|
||||
const g = getApp().globalData
|
||||
const oss = (g.ossImageUrl || '').replace(/\/$/, '')
|
||||
if (!oss) return ''
|
||||
return `${oss}/${String(path).replace(/^\//, '')}`
|
||||
}
|
||||
|
||||
export function getPaihangImgUrls() {
|
||||
const g = getApp().globalData
|
||||
const assets = g.paihangAssets || {}
|
||||
const def = {
|
||||
pageBg: 'beijing/paihangbang/page-bg.jpg',
|
||||
cardBg: 'beijing/paihangbang/list-card-bg.png',
|
||||
emptyIcon: 'beijing/paihangbang/empty.png',
|
||||
refreshIcon: 'beijing/paihangbang/icon-refresh.png',
|
||||
}
|
||||
return {
|
||||
pageBg: buildOssUrl(assets.pageBg || def.pageBg),
|
||||
cardBg: buildOssUrl(assets.cardBg || def.cardBg),
|
||||
emptyIcon: buildOssUrl(assets.emptyIcon || def.emptyIcon),
|
||||
refreshIcon: buildOssUrl(assets.refreshIcon || def.refreshIcon),
|
||||
}
|
||||
}
|
||||
|
||||
/** 按资源组从后台配置读取图标/背景(无配置时用 defaultPaths) */
|
||||
export function getGroupImgUrls(group, defaultPaths) {
|
||||
const cfg = getApp().globalData.pageAssets?.[group] || {}
|
||||
const urls = {}
|
||||
Object.keys(defaultPaths).forEach((key) => {
|
||||
urls[key] = buildOssUrl(cfg[key] || defaultPaths[key])
|
||||
})
|
||||
return urls
|
||||
}
|
||||
|
||||
export const SHANGJIADUAN_DEFAULTS = {
|
||||
pageBg: 'beijing/shangjiaduan/page_bg.png',
|
||||
avatarCardBg: 'beijing/shangjiaduan/avatar_card_bg.png',
|
||||
assetCardBg: 'beijing/shangjiaduan/asset_card_bg.png',
|
||||
statCardBg: 'beijing/shangjiaduan/stat_card_bg.png',
|
||||
funcItemBg: 'beijing/shangjiaduan/func_item_bg.png',
|
||||
primaryFuncBg: 'beijing/shangjiaduan/primary_func_bg.png',
|
||||
highlightFuncBg: 'beijing/shangjiaduan/highlight_func_bg.png',
|
||||
iconRelease: 'beijing/shangjiaduan/icon_release.png',
|
||||
iconFast: 'beijing/shangjiaduan/icon_fast.png',
|
||||
iconOrders: 'beijing/shangjiaduan/icon_orders.png',
|
||||
iconService: 'beijing/shangjiaduan/icon_service.png',
|
||||
iconPunish: 'beijing/shangjiaduan/icon_punish.png',
|
||||
iconRank: 'beijing/shangjiaduan/icon_rank.png',
|
||||
iconRecharge: 'beijing/shangjiaduan/icon_recharge.png',
|
||||
iconWithdraw: 'beijing/shangjiaduan/icon_withdraw.png',
|
||||
iconRefresh: 'beijing/shangjiaduan/icon_refresh.png',
|
||||
iconCopy: 'beijing/shangjiaduan/icon_copy.png',
|
||||
avatarFrame: 'beijing/shangjiaduan/avatar_frame.png',
|
||||
}
|
||||
|
||||
export const GUANSHIDUAN_DEFAULTS = {
|
||||
pageBg: 'beijing/guanshiduan/page-bg.jpg',
|
||||
topDecor: 'beijing/guanshiduan/top-decor.png',
|
||||
cardBg1: 'beijing/guanshiduan/card-bg1.png',
|
||||
cardBg2: 'beijing/guanshiduan/card-bg2.png',
|
||||
iconRefresh: 'beijing/guanshiduan/icon-refresh.png',
|
||||
iconCopy: 'beijing/guanshiduan/icon-copy.png',
|
||||
avatarDefault: 'beijing/morentouxiang.jpg',
|
||||
iconInvite: 'beijing/guanshiduan/icon-invite.png',
|
||||
iconSub: 'beijing/guanshiduan/icon-sub.png',
|
||||
iconRecord: 'beijing/guanshiduan/icon-record.png',
|
||||
iconRank: 'beijing/guanshiduan/icon-rank.png',
|
||||
iconPoster: 'beijing/guanshiduan/icon-poster.png',
|
||||
iconWithdraw: 'beijing/guanshiduan/icon-withdraw.png',
|
||||
iconContact: 'beijing/guanshiduan/icon-contact.png',
|
||||
iconArrow: 'beijing/guanshiduan/icon-arrow.png',
|
||||
}
|
||||
165
miniprogram/utils/phone-auth.js
Normal file
165
miniprogram/utils/phone-auth.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 手机号强制认证 - 完全自包含逻辑模块
|
||||
*
|
||||
* 特点:
|
||||
* 1. 不引用项目中的 request.js / upload.js,避免其顶层 getApp() 带来的加载顺序错误
|
||||
* 2. 所有请求直接使用 wx.request / wx.uploadFile,基础 URL 写死(与 app.globalData.apiBaseUrl 保持一致)
|
||||
* 3. check() 内部捕获所有异常,永远返回 false 或 true,不会抛出错误
|
||||
* 4. submit() 抛出错误由认证页面自己处理,不影响主流程
|
||||
*
|
||||
* 用法:
|
||||
* app.js 中:
|
||||
* try {
|
||||
* const { check } = require('./utils/phone-auth');
|
||||
* if (await check()) { wx.reLaunch(...); return; }
|
||||
* } catch(e) {}
|
||||
*
|
||||
* 认证页面中:
|
||||
* const { submit } = require('../../utils/phone-auth');
|
||||
* await submit(phoneCode, avatarPath);
|
||||
*/
|
||||
|
||||
// ==================== 基础配置 ====================
|
||||
const API_BASE = 'https://6ccy.top/hqhd'; // 与你 app.js 里的 apiBaseUrl 一致
|
||||
const AUTH_CHECK_URL = '/peizhi/yhbdsjh'; // 检查是否需要认证
|
||||
const AUTH_SUBMIT_URL = '/yonghu/xiugai'; // 提交认证(复用修改信息接口)
|
||||
|
||||
// ==================== 内部封装:wx.request ====================
|
||||
function innerRequest(url, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token'); // 运行时获取 token,不提前取
|
||||
const header = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: API_BASE + url,
|
||||
method: 'POST',
|
||||
data,
|
||||
header,
|
||||
success: (res) => {
|
||||
// 正常响应直接 resolve 整个响应对象,让上层自己解析
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 内部封装:wx.uploadFile ====================
|
||||
function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) {
|
||||
reject(new Error('未登录'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 先验证文件是否存在
|
||||
wx.getFileInfo({
|
||||
filePath,
|
||||
success: () => {
|
||||
wx.uploadFile({
|
||||
url: API_BASE + url,
|
||||
filePath,
|
||||
name: fileName,
|
||||
formData,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
success: (res) => {
|
||||
// ✅ 修复:wx.uploadFile 成功时 statusCode 为 200
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
resolve(data); // 直接返回解析后的对象,方便下游使用
|
||||
} catch (e) {
|
||||
reject(new Error('解析响应数据失败'));
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
wx.removeStorageSync('token');
|
||||
reject(new Error('认证失败'));
|
||||
} else {
|
||||
reject(new Error(`上传失败,状态码:${res.statusCode}`));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error('网络请求失败'));
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
reject(new Error('文件不存在或无法访问'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 公开方法 ====================
|
||||
|
||||
/**
|
||||
* 检查是否需要手机号认证
|
||||
* - 无 token 或 任何异常 → 返回 false
|
||||
* - 后端明确 need_auth: true → 返回 true
|
||||
*/
|
||||
async function check() {
|
||||
const token = wx.getStorageSync('token');
|
||||
if (!token) return false;
|
||||
|
||||
try {
|
||||
const res = await innerRequest(AUTH_CHECK_URL, {});
|
||||
// 假设后端返回格式:{ data: { need_auth: true/false } } 或直接在顶层
|
||||
const body = res.data;
|
||||
return body && body.need_auth === true;
|
||||
} catch (e) {
|
||||
// 任何错误(网络、后端异常)都吞掉,不影响主流程
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交认证(手机号 code + 头像文件)
|
||||
* @param {string} phoneCode 微信 getPhoneNumber 返回的 code
|
||||
* @param {string} avatarPath 头像临时路径
|
||||
* @returns {Promise<object>} 后端返回的完整数据
|
||||
*/
|
||||
async function submit(phoneCode, avatarPath) {
|
||||
if (!phoneCode || !avatarPath) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 将手机号 code 放入 formData
|
||||
const formData = {
|
||||
shoujihao_code: phoneCode
|
||||
};
|
||||
|
||||
// 调用上传
|
||||
const data = await innerUpload(AUTH_SUBMIT_URL, avatarPath, formData);
|
||||
|
||||
// ✅ 修复:正确判断后端返回的 code 字段,后端成功时 code === 0
|
||||
if (data && data.code === 0) {
|
||||
// data.data 包含 { touxiang: '相对路径', phone: '...' }
|
||||
const respData = data.data || data;
|
||||
|
||||
// 存储手机号
|
||||
if (respData.phone) {
|
||||
wx.setStorageSync('phone', respData.phone);
|
||||
}
|
||||
|
||||
// ✅ 修复:存储头像相对路径,与 xiugai 页面处理方式一致
|
||||
if (respData.touxiang) {
|
||||
wx.setStorageSync('touxiang', respData.touxiang);
|
||||
}
|
||||
|
||||
return respData;
|
||||
} else {
|
||||
throw new Error((data && data.msg) || '认证失败');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { check, submit };
|
||||
66
miniprogram/utils/request.js
Normal file
66
miniprogram/utils/request.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// utils/request.js
|
||||
const app = getApp();
|
||||
|
||||
function request(options) {
|
||||
const token = wx.getStorageSync('token');
|
||||
const header = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.header,
|
||||
};
|
||||
if (token) {
|
||||
header['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + options.url,
|
||||
method: options.method,
|
||||
data: options.data || {},
|
||||
header,
|
||||
success: async (res) => {
|
||||
if (res.statusCode === 401 || res.statusCode === 403) {
|
||||
// 如果当前正在执行静默登录,等待登录完成后重试一次
|
||||
if (app.globalData.loginPromise) {
|
||||
try {
|
||||
await app.globalData.loginPromise; // 等待登录成功
|
||||
// 登录成功后重新发起原请求,并将结果返回给调用方
|
||||
const retryRes = await request(options);
|
||||
resolve(retryRes);
|
||||
} catch (err) {
|
||||
// 登录失败,按原逻辑弹窗
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.reLaunch({ url: '/pages/wode/wode' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res); // 返回原始响应让调用方知晓
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 无登录流程进行中,直接弹窗(原逻辑)
|
||||
wx.showModal({
|
||||
content: '操作失败,请先登录',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
wx.removeStorageSync('token');
|
||||
wx.reLaunch({ url: '/pages/wode/wode' });
|
||||
}
|
||||
},
|
||||
});
|
||||
resolve(res);
|
||||
return;
|
||||
}
|
||||
// 正常响应
|
||||
resolve(res);
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default request;
|
||||
0
miniprogram/utils/role-manager.js
Normal file
0
miniprogram/utils/role-manager.js
Normal file
147
miniprogram/utils/scriptService.js
Normal file
147
miniprogram/utils/scriptService.js
Normal file
@@ -0,0 +1,147 @@
|
||||
// utils/scriptService.js — 话术拉取、确认弹窗、客服自动回复匹配
|
||||
import request from './request.js';
|
||||
|
||||
const DEFAULT_SCRIPTS = {
|
||||
chat_image_confirm: {
|
||||
title: '温馨提示',
|
||||
content: '平台禁止任何违法违规行为,聊天内容仅限于商家与接单员正常业务对接。严禁赌博、诈骗及其他非法行为,违者后果自负。',
|
||||
confirm_text: '我已知晓,继续发送',
|
||||
cancel_text: '取消',
|
||||
},
|
||||
cs_welcome: {
|
||||
content: '你好,请问有什么可以帮到您的?',
|
||||
},
|
||||
merchant_dispatch_confirm: {
|
||||
title: '派单确认',
|
||||
content: '请确认订单信息无误。派单成功后将扣除相应余额,请确保订单描述合法合规。',
|
||||
confirm_text: '确认派单',
|
||||
cancel_text: '取消',
|
||||
},
|
||||
};
|
||||
|
||||
const textCache = {};
|
||||
const TEXT_CACHE_TTL = 5 * 60 * 1000;
|
||||
|
||||
function mergeScript(sceneKey, remote) {
|
||||
const base = DEFAULT_SCRIPTS[sceneKey] || {};
|
||||
if (!remote) return { ...base };
|
||||
return { ...base, ...remote };
|
||||
}
|
||||
|
||||
export async function fetchScripts(sceneKeys) {
|
||||
if (!sceneKeys || !sceneKeys.length) return {};
|
||||
|
||||
const now = Date.now();
|
||||
const cached = {};
|
||||
const needFetch = [];
|
||||
|
||||
sceneKeys.forEach((key) => {
|
||||
const entry = textCache[key];
|
||||
if (entry && now - entry.time < TEXT_CACHE_TTL) {
|
||||
cached[key] = entry.data;
|
||||
} else {
|
||||
needFetch.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
if (!needFetch.length) {
|
||||
const result = {};
|
||||
sceneKeys.forEach((key) => {
|
||||
result[key] = mergeScript(key, cached[key]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/peizhi/huashuhq',
|
||||
method: 'POST',
|
||||
data: { scene_keys: needFetch },
|
||||
});
|
||||
const payload = (res && res.data && res.data.code === 200) ? res.data.data || {} : {};
|
||||
|
||||
needFetch.forEach((key) => {
|
||||
const raw = payload[key];
|
||||
const normalized = raw && raw.item_type === 'auto_reply' ? raw : raw;
|
||||
textCache[key] = { time: now, data: normalized };
|
||||
cached[key] = normalized;
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('fetchScripts failed', e);
|
||||
}
|
||||
|
||||
const result = {};
|
||||
sceneKeys.forEach((key) => {
|
||||
result[key] = mergeScript(key, cached[key]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function showConfirmByScene(sceneKey, onConfirm, onCancel) {
|
||||
fetchScripts([sceneKey]).then((data) => {
|
||||
const script = data[sceneKey] || DEFAULT_SCRIPTS[sceneKey] || {};
|
||||
wx.showModal({
|
||||
title: script.title || '提示',
|
||||
content: script.content || '',
|
||||
confirmText: script.confirm_text || '确定',
|
||||
cancelText: script.cancel_text || '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
onConfirm && onConfirm();
|
||||
} else {
|
||||
onCancel && onCancel();
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
onCancel && onCancel();
|
||||
},
|
||||
});
|
||||
}).catch(() => {
|
||||
const script = DEFAULT_SCRIPTS[sceneKey] || {};
|
||||
wx.showModal({
|
||||
title: script.title || '提示',
|
||||
content: script.content || '',
|
||||
confirmText: script.confirm_text || '确定',
|
||||
cancelText: script.cancel_text || '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) onConfirm && onConfirm();
|
||||
else onCancel && onCancel();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function matchAutoReply(userText) {
|
||||
if (!userText || !userText.trim()) {
|
||||
return { matched: false, content: '' };
|
||||
}
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/peizhi/huashu_match',
|
||||
method: 'POST',
|
||||
data: {
|
||||
scene_key: 'cs_auto_reply',
|
||||
user_text: userText.trim(),
|
||||
},
|
||||
});
|
||||
if (res && res.data && res.data.code === 200 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('matchAutoReply failed', e);
|
||||
}
|
||||
return { matched: false, content: '' };
|
||||
}
|
||||
|
||||
export async function getWelcomeText() {
|
||||
const data = await fetchScripts(['cs_welcome']);
|
||||
const script = data.cs_welcome || DEFAULT_SCRIPTS.cs_welcome;
|
||||
return script.content || DEFAULT_SCRIPTS.cs_welcome.content;
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchScripts,
|
||||
showConfirmByScene,
|
||||
matchAutoReply,
|
||||
getWelcomeText,
|
||||
};
|
||||
95
miniprogram/utils/upload.js
Normal file
95
miniprogram/utils/upload.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// utils/upload.js
|
||||
import request from './request.js'
|
||||
|
||||
const app = getApp()
|
||||
|
||||
/**
|
||||
* 统一的异步上传方法
|
||||
* @param {object} options 配置项
|
||||
* @returns {Promise} Promise对象
|
||||
*/
|
||||
const upload = async (options) => {
|
||||
const {
|
||||
url,
|
||||
formData = {},
|
||||
filePath = null,
|
||||
fileName = 'file'
|
||||
} = options
|
||||
|
||||
// 获取token
|
||||
const token = wx.getStorageSync('token')
|
||||
|
||||
if (!token) {
|
||||
throw new Error('未登录')
|
||||
}
|
||||
|
||||
// 拼接完整URL
|
||||
const fullUrl = `${app.globalData.apiBaseUrl}${url}`
|
||||
|
||||
if (filePath) {
|
||||
// 有文件上传,使用wx.uploadFile
|
||||
return new Promise((resolve, reject) => {
|
||||
// 先验证文件是否存在
|
||||
wx.getFileInfo({
|
||||
filePath,
|
||||
success: () => {
|
||||
// 文件存在,开始上传
|
||||
wx.uploadFile({
|
||||
url: fullUrl,
|
||||
filePath,
|
||||
name: fileName,
|
||||
formData,
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
resolve({
|
||||
statusCode: res.statusCode,
|
||||
data
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('解析JSON失败:', e, '原始数据:', res.data)
|
||||
reject(new Error('解析响应数据失败'))
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
// token过期或无效
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
reject(new Error('认证失败'))
|
||||
} else {
|
||||
console.error('上传失败,状态码:', res.statusCode, '响应:', res)
|
||||
reject(new Error(`上传失败,状态码:${res.statusCode}`))
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('wx.uploadFile失败:', err)
|
||||
reject(new Error('网络请求失败'))
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('文件不存在或无法访问:', err)
|
||||
reject(new Error('文件不存在或无法访问'))
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 没有文件,使用封装的request
|
||||
// 注意:这里应该使用传入的url,而不是硬编码
|
||||
return request({
|
||||
url, // 使用传入的url
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default upload
|
||||
Reference in New Issue
Block a user