From 681d2b5fe818b82aa3256fcb569d77be2802fd41 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Thu, 2 Apr 2026 16:55:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +- admin/src/views/ChatRecords.vue | 26 +++++++---- admin/src/views/Withdrawals.vue | 21 +++------ miniapp/App.vue | 62 ++++++++++++++++---------- miniapp/pages/message/chat.vue | 25 ++++++++--- miniapp/pages/message/message.vue | 10 +++++ miniapp/pages/message/order-notify.vue | 2 +- miniapp/pages/message/system-msg.vue | 2 +- miniapp/pages/mine/earnings-record.vue | 2 +- miniapp/pages/mine/earnings.vue | 5 +-- miniapp/pages/mine/profile.vue | 44 ++++++++++-------- miniapp/pages/order/complete-order.vue | 26 ++++++++++- miniapp/pages/order/my-orders.vue | 2 +- miniapp/pages/order/my-taken.vue | 2 +- miniapp/pages/order/order-detail.vue | 2 +- miniapp/utils/im.js | 17 ++++++- server/Endpoints/AdminEndpoints.cs | 26 +++++++++++ server/Endpoints/EarningEndpoints.cs | 11 +++-- server/Endpoints/MessageEndpoints.cs | 9 ++-- server/Endpoints/OrderEndpoints.cs | 8 +++- server/Models/Dtos/EarningDtos.cs | 4 -- server/Models/Dtos/OrderDtos.cs | 4 ++ server/Services/TencentIMService.cs | 29 +++++++++--- 23 files changed, 235 insertions(+), 107 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b66410..bf5774d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "git.ignoreLimitWarning": true + "git.ignoreLimitWarning": true, + "kiro.chat.autoSummarize.threshold": 30 } \ No newline at end of file diff --git a/admin/src/views/ChatRecords.vue b/admin/src/views/ChatRecords.vue index 0a54546..bd26297 100644 --- a/admin/src/views/ChatRecords.vue +++ b/admin/src/views/ChatRecords.vue @@ -93,6 +93,7 @@ diff --git a/miniapp/pages/message/chat.vue b/miniapp/pages/message/chat.vue index 338ce23..95d3f6f 100644 --- a/miniapp/pages/message/chat.vue +++ b/miniapp/pages/message/chat.vue @@ -254,6 +254,17 @@ const userStore = useUserStore() return this.orderInfo.ownerId === userStore.userId }, + /** 构建 IM userId -> 头像 的映射 */ + avatarMap() { + const map = {} + if (this.orderInfo.ownerId) { + map[`user_${this.orderInfo.ownerId}`] = this.orderInfo.ownerAvatar || '' + } + if (this.orderInfo.runnerId) { + map[`user_${this.orderInfo.runnerId}`] = this.orderInfo.runnerAvatar || '' + } + return map + }, showGoodsPriceBtn() { const type = this.orderInfo.orderType return type === 'Purchase' || type === 'Food' @@ -329,7 +340,7 @@ for (const msg of msgList) { // 只处理当前群的消息 if (msg.conversationID === `GROUP${this.imGroupId}`) { - const formatted = formatIMMessage(msg, this.imUserId) + const formatted = formatIMMessage(msg, this.imUserId, this.avatarMap) // 收到改价响应消息,更新对应改价卡片状态 if (formatted.type === 'price-change-response') { const card = this.chatMessages.find(m => m.type === 'price-change' && m.priceChangeId === formatted.priceChangeId) @@ -363,7 +374,7 @@ this.loadingHistory = true try { const res = await getMessageList(this.imGroupId, this.nextReqMessageID) - const allFormatted = res.messageList.map(m => formatIMMessage(m, this.imUserId)) + const allFormatted = res.messageList.map(m => formatIMMessage(m, this.imUserId, this.avatarMap)) // 处理历史消息中的改价响应 const formatted = [] for (const m of allFormatted) { @@ -402,7 +413,7 @@ this.inputText = '' try { const msg = await sendTextMessage(this.imGroupId, text) - this.chatMessages.push(formatIMMessage(msg, this.imUserId)) + this.chatMessages.push(formatIMMessage(msg, this.imUserId, this.avatarMap)) this.scrollToBottom() } catch (e) { uni.showToast({ @@ -435,7 +446,7 @@ success: async (res) => { try { const msg = await sendImageMessage(this.imGroupId, res) - this.chatMessages.push(formatIMMessage(msg, this.imUserId)) + this.chatMessages.push(formatIMMessage(msg, this.imUserId, this.avatarMap)) this.scrollToBottom() } catch (e) { uni.showToast({ @@ -567,7 +578,7 @@ // 发一条文本消息记录操作(群内可见,持久化) try { const imMsg = await sendTextMessage(this.imGroupId, `[系统提示] 单主已${actionLabel}${changeTypeLabel}改价`) - this.chatMessages.push(formatIMMessage(imMsg, this.imUserId)) + this.chatMessages.push(formatIMMessage(imMsg, this.imUserId, this.avatarMap)) } catch (ex) {} } else { this.chatMessages.push({ @@ -586,7 +597,7 @@ if (this.imReady && this.imGroupId) { try { const imMsg = await sendTextMessage(this.imGroupId, '[系统提示] 单主已完成补缴支付') - this.chatMessages.push(formatIMMessage(imMsg, this.imUserId)) + this.chatMessages.push(formatIMMessage(imMsg, this.imUserId, this.avatarMap)) } catch (ex) {} } } catch (e) { @@ -733,7 +744,7 @@ if (this.imReady && this.imGroupId) { try { const imMsg = await sendTextMessage(this.imGroupId, '[系统提示] 单主已完成补缴支付') - this.chatMessages.push(formatIMMessage(imMsg, this.imUserId)) + this.chatMessages.push(formatIMMessage(imMsg, this.imUserId, this.avatarMap)) this.scrollToBottom() } catch (ex) {} } diff --git a/miniapp/pages/message/message.vue b/miniapp/pages/message/message.vue index 0020786..e8fa0b9 100644 --- a/miniapp/pages/message/message.vue +++ b/miniapp/pages/message/message.vue @@ -113,6 +113,16 @@ export default { this.loadUnreadCount() this.loadChatList() this.updateTabBarBadge() + // 定时刷新未读数(每5秒) + this._refreshTimer = setInterval(() => { + this.loadChatList() + }, 5000) + }, + onHide() { + if (this._refreshTimer) { + clearInterval(this._refreshTimer) + this._refreshTimer = null + } }, methods: { /** 加载未读消息数 */ diff --git a/miniapp/pages/message/order-notify.vue b/miniapp/pages/message/order-notify.vue index 77b55de..47db72a 100644 --- a/miniapp/pages/message/order-notify.vue +++ b/miniapp/pages/message/order-notify.vue @@ -137,7 +137,7 @@ export default { /** 格式化时间(精确到年月日时分,) */ formatTime(dateStr) { if (!dateStr) return '' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } diff --git a/miniapp/pages/message/system-msg.vue b/miniapp/pages/message/system-msg.vue index 78d9be7..3898224 100644 --- a/miniapp/pages/message/system-msg.vue +++ b/miniapp/pages/message/system-msg.vue @@ -116,7 +116,7 @@ export default { /** 格式化时间 */ formatTime(dateStr) { if (!dateStr) return '' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } diff --git a/miniapp/pages/mine/earnings-record.vue b/miniapp/pages/mine/earnings-record.vue index 1c952cd..791b8a6 100644 --- a/miniapp/pages/mine/earnings-record.vue +++ b/miniapp/pages/mine/earnings-record.vue @@ -101,7 +101,7 @@ export default { formatTime(dateStr) { if (!dateStr) return '-' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } diff --git a/miniapp/pages/mine/earnings.vue b/miniapp/pages/mine/earnings.vue index e0478b1..e72ec38 100644 --- a/miniapp/pages/mine/earnings.vue +++ b/miniapp/pages/mine/earnings.vue @@ -180,7 +180,7 @@ export default { formatTime(dateStr) { if (!dateStr) return '-' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` }, @@ -244,8 +244,7 @@ export default { try { await applyWithdraw({ amount, - paymentMethod: 'WeChat', - qrCodeImage: '' + paymentMethod: 'WeChat' }) uni.showToast({ title: '提现申请已提交', icon: 'success' }) diff --git a/miniapp/pages/mine/profile.vue b/miniapp/pages/mine/profile.vue index 55ad7bf..356467b 100644 --- a/miniapp/pages/mine/profile.vue +++ b/miniapp/pages/mine/profile.vue @@ -16,11 +16,9 @@ 头像 - - + + + @@ -64,20 +62,28 @@ export default { methods: { goBack() { uni.navigateBack() }, - /** 微信选择头像回调 */ - async onChooseAvatar(e) { - const tempUrl = e.detail.avatarUrl - if (!tempUrl) return - try { - uni.showLoading({ title: '上传中...' }) - const uploadRes = await uploadFile(tempUrl) - this.form.avatarUrl = uploadRes.url - uni.hideLoading() - } catch (err) { - uni.hideLoading() - // 上传失败,先用临时路径显示 - this.form.avatarUrl = tempUrl - } + /** 选择头像 */ + async onPickAvatar() { + const self = this + uni.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['album', 'camera'], + success: async (res) => { + const tempUrl = res.tempFilePaths[0] + if (!tempUrl) return + try { + uni.showLoading({ title: '上传中...' }) + const uploadRes = await uploadFile(tempUrl) + self.form.avatarUrl = uploadRes.url + uni.hideLoading() + } catch (err) { + uni.hideLoading() + // 上传失败,先用临时路径显示 + self.form.avatarUrl = tempUrl + } + } + }) }, /** 昵称输入框失焦(微信昵称选择后触发) */ diff --git a/miniapp/pages/order/complete-order.vue b/miniapp/pages/order/complete-order.vue index 4db5a6e..2018c96 100644 --- a/miniapp/pages/order/complete-order.vue +++ b/miniapp/pages/order/complete-order.vue @@ -199,6 +199,18 @@ export default { if (!res.confirm) return try { await confirmOrder(this.orderId) + + // 通过 IM 通知跑腿 + try { + await initIM() + const groupId = this.orderInfo.imGroupId || `order_${this.orderId}` + await sendCustomMessage(groupId, { + bizType: 'order-status', + action: 'WaitConfirm→Completed', + description: '单主已确认完成,订单已结束' + }) + } catch (ex) {} + uni.showToast({ title: '订单已完成', icon: 'success' }) setTimeout(() => { uni.navigateBack() @@ -219,6 +231,18 @@ export default { if (!res.confirm) return try { await rejectOrder(this.orderId) + + // 通过 IM 通知跑腿 + try { + await initIM() + const groupId = this.orderInfo.imGroupId || `order_${this.orderId}` + await sendCustomMessage(groupId, { + bizType: 'order-status', + action: 'WaitConfirm→InProgress', + description: '单主拒绝确认完成,订单继续进行' + }) + } catch (ex) {} + uni.showToast({ title: '已拒绝,订单继续进行', icon: 'none' }) setTimeout(() => { uni.navigateBack() @@ -233,7 +257,7 @@ export default { /** 格式化时间(精确至年月日时分) */ formatTime(dateStr) { if (!dateStr) return '-' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') diff --git a/miniapp/pages/order/my-orders.vue b/miniapp/pages/order/my-orders.vue index c74f5c7..10b2ede 100644 --- a/miniapp/pages/order/my-orders.vue +++ b/miniapp/pages/order/my-orders.vue @@ -275,7 +275,7 @@ export default { formatTime(dateStr) { if (!dateStr) return '-' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` }, diff --git a/miniapp/pages/order/my-taken.vue b/miniapp/pages/order/my-taken.vue index 21d868b..3086a5d 100644 --- a/miniapp/pages/order/my-taken.vue +++ b/miniapp/pages/order/my-taken.vue @@ -200,7 +200,7 @@ export default { formatTime(dateStr) { if (!dateStr) return '-' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` }, diff --git a/miniapp/pages/order/order-detail.vue b/miniapp/pages/order/order-detail.vue index a735768..c6ea7e1 100644 --- a/miniapp/pages/order/order-detail.vue +++ b/miniapp/pages/order/order-detail.vue @@ -323,7 +323,7 @@ export default { formatTime(dateStr) { if (!dateStr) return '-' - const d = new Date(dateStr) + const d = new Date(typeof dateStr === 'string' && !dateStr.endsWith('Z') ? dateStr + 'Z' : dateStr) const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` }, diff --git a/miniapp/utils/im.js b/miniapp/utils/im.js index d53317e..b61cf17 100644 --- a/miniapp/utils/im.js +++ b/miniapp/utils/im.js @@ -28,6 +28,19 @@ export async function initIM() { chat.on(TencentCloudChat.EVENT.SDK_READY, () => { console.log('[IM] SDK 就绪') isReady = true + // SDK就绪后同步用户头像和昵称到腾讯 IM + try { + const userInfo = uni.getStorageSync('userInfo') + if (userInfo) { + const parsed = typeof userInfo === 'string' ? JSON.parse(userInfo) : userInfo + chat.updateMyProfile({ + nick: parsed.nickname || '', + avatar: parsed.avatarUrl || '' + }) + } + } catch (e) { + console.warn('[IM] 同步资料失败:', e) + } }) chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, (event) => { @@ -169,9 +182,9 @@ export async function sendCustomMessage(groupId, customData) { } /** 将 IM 消息转换为聊天页展示格式 */ -export function formatIMMessage(msg, currentImUserId) { +export function formatIMMessage(msg, currentImUserId, avatarMap = {}) { const isSelf = msg.from === currentImUserId - const avatar = msg.avatar || '/static/logo.png' + const avatar = avatarMap[msg.from] || msg.avatar || '/static/logo.png' // 文本消息 if (msg.type === TencentCloudChat.TYPES.MSG_TEXT) { diff --git a/server/Endpoints/AdminEndpoints.cs b/server/Endpoints/AdminEndpoints.cs index ea05978..aad9f50 100644 --- a/server/Endpoints/AdminEndpoints.cs +++ b/server/Endpoints/AdminEndpoints.cs @@ -601,6 +601,7 @@ public static class AdminEndpoints o.ItemName, Status = o.Status.ToString(), o.Commission, + o.ImGroupId, OwnerId = o.OwnerId, OwnerUid = o.Owner!.Uid, OwnerNickname = o.Owner!.Nickname, @@ -621,6 +622,7 @@ public static class AdminEndpoints o.ItemName, o.Status, o.Commission, + o.ImGroupId, o.OwnerId, OwnerUid = string.IsNullOrWhiteSpace(o.OwnerUid) ? o.OwnerId.ToString() : o.OwnerUid, OwnerNickname = string.IsNullOrWhiteSpace(o.OwnerNickname) ? $"用户{o.OwnerId}" : o.OwnerNickname, @@ -661,5 +663,29 @@ public static class AdminEndpoints return Results.BadRequest(new { code = 400, message = $"拉取聊天记录失败: {ex.Message}" }); } }).RequireAuthorization("AdminOnly"); + + // 管理端删除聊天记录(解散IM群并清除订单群ID) + app.MapDelete("/api/admin/chat-list/{groupId}", async (string groupId, AppDbContext db, TencentIMService imService) => + { + try + { + // 解散 IM 群 + await imService.DestroyGroupAsync(groupId); + + // 清除订单的群ID + var order = await db.Orders.FirstOrDefaultAsync(o => o.ImGroupId == groupId); + if (order != null) + { + order.ImGroupId = null; + await db.SaveChangesAsync(); + } + + return Results.Ok(new { message = "已删除" }); + } + catch (Exception ex) + { + return Results.BadRequest(new { code = 400, message = $"删除失败: {ex.Message}" }); + } + }).RequireAuthorization("AdminOnly"); } } diff --git a/server/Endpoints/EarningEndpoints.cs b/server/Endpoints/EarningEndpoints.cs index 66cf717..20c8dd1 100644 --- a/server/Endpoints/EarningEndpoints.cs +++ b/server/Endpoints/EarningEndpoints.cs @@ -129,7 +129,6 @@ public static class EarningEndpoints UserId = userId, Amount = request.Amount, PaymentMethod = paymentMethod, - QrCodeImage = request.QrCodeImage, Status = WithdrawalStatus.Pending, CreatedAt = DateTime.UtcNow }; @@ -205,9 +204,9 @@ public static class EarningEndpoints w.Id, w.UserId, UserNickname = w.User!.Nickname ?? ("用户" + w.UserId), + UserUid = w.User!.Uid ?? w.UserId.ToString(), w.Amount, PaymentMethod = w.PaymentMethod.ToString(), - w.QrCodeImage, Status = w.Status.ToString(), w.CreatedAt, w.ProcessedAt @@ -229,20 +228,20 @@ public static class EarningEndpoints if (request.Action == "approve") { - // 先调用微信商家转账到零钱 + // 调用微信商家转账到零钱 var user = await db.Users.FindAsync(withdrawal.UserId); if (user == null || string.IsNullOrEmpty(user.OpenId)) return Results.BadRequest(new { code = 400, message = "用户信息异常,无法转账" }); var amountFen = (int)(withdrawal.Amount * 100); - var batchNo = $"W{withdrawal.Id}_{DateTime.UtcNow:yyyyMMddHHmmss}"; - var detailNo = $"D{withdrawal.Id}_{DateTime.UtcNow:yyyyMMddHHmmss}"; + var batchNo = $"W{withdrawal.Id}T{DateTime.UtcNow:yyyyMMddHHmmss}"; + var detailNo = $"D{withdrawal.Id}T{DateTime.UtcNow:yyyyMMddHHmmss}"; var (transferSuccess, transferError) = await wxPay.TransferToWallet(batchNo, detailNo, user.OpenId, amountFen, "跑腿提现到账"); if (!transferSuccess) { Console.WriteLine($"[提现] 转账失败: {transferError}"); - return Results.BadRequest(new { code = 400, message = $"转账失败,请稍后重试", detail = transferError }); + return Results.BadRequest(new { code = 400, message = "转账失败,请稍后重试", detail = transferError }); } withdrawal.Status = WithdrawalStatus.Completed; diff --git a/server/Endpoints/MessageEndpoints.cs b/server/Endpoints/MessageEndpoints.cs index 25ab44a..c8ccb94 100644 --- a/server/Endpoints/MessageEndpoints.cs +++ b/server/Endpoints/MessageEndpoints.cs @@ -145,10 +145,13 @@ public static class MessageEndpoints Id = o.Id, OrderNo = o.OrderNo, OrderType = o.OrderType.ToString(), - Title = o.Status == OrderStatus.InProgress ? "订单已被接取" - : o.Status == OrderStatus.Completed ? "订单已完成" + Title = o.Status == OrderStatus.InProgress + ? (o.OwnerId == userId ? "订单已被接取" : "您已接取订单") + : o.Status == OrderStatus.Completed + ? (o.RunnerId == userId ? "单主已确认完成" : "订单已完成") : o.Status == OrderStatus.Cancelled ? "订单已取消" - : o.Status == OrderStatus.WaitConfirm ? "订单待确认" + : o.Status == OrderStatus.WaitConfirm + ? (o.OwnerId == userId ? "跑腿已提交完成,请确认" : "等待单主确认完成") : o.Status == OrderStatus.Appealing ? "订单申诉中" : "订单状态变更", ItemName = o.ItemName, diff --git a/server/Endpoints/OrderEndpoints.cs b/server/Endpoints/OrderEndpoints.cs index 6bc73fb..2f87012 100644 --- a/server/Endpoints/OrderEndpoints.cs +++ b/server/Endpoints/OrderEndpoints.cs @@ -332,6 +332,8 @@ public static class OrderEndpoints AcceptedAt = visibleAcceptedAt, CompletedAt = visibleCompletedAt, RunnerNickname = runnerNickname, + RunnerAvatar = order.Runner?.AvatarUrl, + OwnerAvatar = order.Owner?.AvatarUrl, RunnerUid = runnerUid, RunnerPhone = runnerPhone }; @@ -513,6 +515,7 @@ public static class OrderEndpoints try { await db.SaveChangesAsync(); + Console.WriteLine($"[接单] 订单 {order.Id} 接单成功,准备创建群组"); } catch (DbUpdateConcurrencyException) { @@ -525,16 +528,19 @@ public static class OrderEndpoints var groupId = $"order_{order.Id}"; var ownerImId = $"user_{order.OwnerId}"; var runnerImId = $"user_{userId}"; + Console.WriteLine($"[IM] 准备创建群组: {groupId}, 单主: {ownerImId}, 跑腿: {runnerImId}"); var result = await imService.CreateGroupAsync(groupId, $"订单{order.OrderNo}", ownerImId, runnerImId); + Console.WriteLine($"[IM] 创建群组结果: {result}"); if (result != null) { order.ImGroupId = result; await db.SaveChangesAsync(); + Console.WriteLine($"[IM] 群组ID已保存: {result}"); } } catch (Exception ex) { - Console.WriteLine($"[IM] 创建群组失败: {ex.Message}"); + Console.WriteLine($"[IM] 创建群组失败: {ex.Message}\n{ex.StackTrace}"); } return Results.Ok(new AcceptOrderResponse diff --git a/server/Models/Dtos/EarningDtos.cs b/server/Models/Dtos/EarningDtos.cs index f7a1ade..6510c8b 100644 --- a/server/Models/Dtos/EarningDtos.cs +++ b/server/Models/Dtos/EarningDtos.cs @@ -61,10 +61,6 @@ public class WithdrawRequest /// 收款方式:WeChat 或 Alipay [Required(ErrorMessage = "收款方式不能为空")] public string PaymentMethod { get; set; } = string.Empty; - - /// 收款二维码图片 - [Required(ErrorMessage = "收款二维码不能为空")] - public string QrCodeImage { get; set; } = string.Empty; } /// diff --git a/server/Models/Dtos/OrderDtos.cs b/server/Models/Dtos/OrderDtos.cs index 80813f3..86f3c59 100644 --- a/server/Models/Dtos/OrderDtos.cs +++ b/server/Models/Dtos/OrderDtos.cs @@ -81,6 +81,10 @@ public class OrderResponse public DateTime? CompletedAt { get; set; } /// 跑腿昵称 public string? RunnerNickname { get; set; } + /// 跑腿头像 + public string? RunnerAvatar { get; set; } + /// 单主头像 + public string? OwnerAvatar { get; set; } /// 跑腿 UID public int? RunnerUid { get; set; } /// 跑腿手机号(认证时填写的手机号,仅单主可见) diff --git a/server/Services/TencentIMService.cs b/server/Services/TencentIMService.cs index 15dad8c..ec15968 100644 --- a/server/Services/TencentIMService.cs +++ b/server/Services/TencentIMService.cs @@ -94,7 +94,7 @@ public class TencentIMService var body = new { - Type = "Private", + Type = "Work", GroupId = groupId, Name = groupName, Owner_Account = ownerImId, @@ -131,16 +131,31 @@ public class TencentIMService var random = Random.Shared.Next(100000, 999999); var url = $"https://console.tim.qq.com/v4/group_open_http_svc/group_msg_get_simple?sdkappid={_sdkAppId}&identifier={AdminAccount}&usersig={adminSig}&random={random}&contenttype=json"; - var body = new - { - GroupId = groupId, - ReqMsgSeq = reqMsgSeq, - ReqMsgNumber = reqMsgNumber - }; + // ReqMsgSeq 为 0 时不传该字段,让腾讯 IM 自动返回最新消息 + object body = reqMsgSeq > 0 + ? new { GroupId = groupId, ReqMsgSeq = reqMsgSeq, ReqMsgNumber = reqMsgNumber } + : new { GroupId = groupId, ReqMsgNumber = reqMsgNumber }; var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(url, content); var json = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"[IM] 拉取群消息 {groupId}: {json.Substring(0, Math.Min(json.Length, 500))}"); return JsonSerializer.Deserialize(json); } + + /// + /// 解散 IM 群组 + /// + public async Task DestroyGroupAsync(string groupId) + { + var adminSig = GenerateUserSig(AdminAccount); + var random = Random.Shared.Next(100000, 999999); + var url = $"https://console.tim.qq.com/v4/group_open_http_svc/destroy_group?sdkappid={_sdkAppId}&identifier={AdminAccount}&usersig={adminSig}&random={random}&contenttype=json"; + + var body = new { GroupId = groupId }; + var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync(url, content); + var json = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"[IM] 解散群组 {groupId}: {json}"); + } }