优化咨询客服
This commit is contained in:
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
type ChatConversation struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
OrderID uint64 `gorm:"not null;uniqueIndex" json:"order_id"`
|
||||
OrderID *uint64 `gorm:"uniqueIndex" json:"order_id"`
|
||||
Type string `gorm:"size:32;not null;default:'order_group'" json:"type"`
|
||||
Title string `gorm:"size:128;not null" json:"title"`
|
||||
Status string `gorm:"size:32;not null;default:'active'" json:"status"`
|
||||
|
||||
@@ -9,7 +9,7 @@ type Principal struct {
|
||||
|
||||
type ConversationDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
OrderID uint64 `json:"order_id"`
|
||||
OrderID *uint64 `json:"order_id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
|
||||
@@ -81,6 +81,20 @@ func (h *Handler) OrderConversation(c *gin.Context) {
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) EnsureSupportConversation(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
item, err := h.service.EnsureSupportConversation(userID)
|
||||
if err != nil {
|
||||
writeChatError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Messages(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
|
||||
@@ -43,7 +43,7 @@ func EnsureOrderConversation(tx *gorm.DB, order model.RentalOrder) (*model.ChatC
|
||||
|
||||
now := time.Now()
|
||||
conversation := model.ChatConversation{
|
||||
OrderID: order.ID,
|
||||
OrderID: &order.ID,
|
||||
Type: "order_group",
|
||||
Title: orderConversationTitle(order),
|
||||
Status: "active",
|
||||
@@ -182,6 +182,89 @@ func (r *Repository) FindOrderConversation(userID uint64, orderID uint64) (*Conv
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (r *Repository) EnsureSupportConversation(userID uint64) (*ConversationDTO, error) {
|
||||
var conversationID uint64
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var existing model.ChatConversation
|
||||
err := tx.Table("chat_conversations AS c").
|
||||
Select("c.*").
|
||||
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
|
||||
Where("c.type = ? AND cp.participant_type = ? AND cp.participant_id = ?", "general_support", "user", userID).
|
||||
Order("c.id ASC").
|
||||
First(&existing).Error
|
||||
if err == nil {
|
||||
conversationID = existing.ID
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
conversation := model.ChatConversation{
|
||||
Type: "general_support",
|
||||
Title: "平台客服",
|
||||
Status: "active",
|
||||
}
|
||||
if err := tx.Create(&conversation).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
participants := []model.ChatParticipant{
|
||||
{
|
||||
ConversationID: conversation.ID,
|
||||
ParticipantType: "user",
|
||||
ParticipantID: userID,
|
||||
Role: "customer",
|
||||
JoinedAt: now,
|
||||
},
|
||||
}
|
||||
if supportID := defaultSupportAdminID(tx); supportID > 0 {
|
||||
participants = append(participants, model.ChatParticipant{
|
||||
ConversationID: conversation.ID,
|
||||
ParticipantType: "admin",
|
||||
ParticipantID: supportID,
|
||||
Role: "support",
|
||||
JoinedAt: now,
|
||||
})
|
||||
}
|
||||
for _, participant := range participants {
|
||||
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&participant).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
message := model.ChatMessage{
|
||||
ConversationID: conversation.ID,
|
||||
SenderType: "system",
|
||||
SenderRole: "system",
|
||||
ContentType: "system",
|
||||
Content: "您好,客服会尽快回复,请直接描述您遇到的问题。",
|
||||
AttachmentURLS: emptyJSONList(),
|
||||
}
|
||||
if err := tx.Create(&message).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
conversation.LastMessageID = &message.ID
|
||||
conversation.LastMessagePreview = truncatePreview(message.Content)
|
||||
conversation.LastMessageAt = &message.CreatedAt
|
||||
if err := tx.Save(&conversation).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
conversationID = conversation.ID
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item, err := r.FindConversation(Principal{Type: "user", ID: userID}, conversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.NotifyNewConversation(conversationID)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (r *Repository) Messages(principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
page, pageSize = normalizePagination(page, pageSize)
|
||||
if _, err := r.findParticipant(r.db, principal, conversationID, false); err != nil {
|
||||
@@ -473,7 +556,7 @@ func (r *Repository) adminNames(ids []uint64) (map[uint64]string, error) {
|
||||
|
||||
type conversationRow struct {
|
||||
ID uint64
|
||||
OrderID uint64
|
||||
OrderID *uint64
|
||||
Type string
|
||||
Title string
|
||||
Status string
|
||||
|
||||
@@ -41,6 +41,16 @@ func (s *Service) FindOrderConversation(userID uint64, orderID uint64) (*Convers
|
||||
return s.repo.FindOrderConversation(userID, orderID)
|
||||
}
|
||||
|
||||
func (s *Service) EnsureSupportConversation(userID uint64) (*ConversationDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if userID == 0 {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
return s.repo.EnsureSupportConversation(userID)
|
||||
}
|
||||
|
||||
func (s *Service) Messages(principal Principal, conversationID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
|
||||
@@ -247,6 +247,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
if chatHubHandler != nil {
|
||||
chatRoutes.GET("/events", chatHubHandler.UserEvents)
|
||||
}
|
||||
chatRoutes.POST("/support", chatHandler.EnsureSupportConversation)
|
||||
chatRoutes.GET("", chatHandler.List)
|
||||
chatRoutes.GET("/:id", chatHandler.Detail)
|
||||
chatRoutes.GET("/:id/messages", chatHandler.Messages)
|
||||
|
||||
@@ -315,7 +315,7 @@ CREATE TABLE system_configs (
|
||||
|
||||
CREATE TABLE chat_conversations (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NULL,
|
||||
type VARCHAR(32) NOT NULL DEFAULT 'order_group',
|
||||
title VARCHAR(128) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE chat_conversations
|
||||
MODIFY order_id BIGINT UNSIGNED NULL;
|
||||
@@ -6,7 +6,7 @@ export interface ChatParticipant {
|
||||
conversation_id: number
|
||||
participant_type: 'user' | 'admin'
|
||||
participant_id: number
|
||||
role: 'renter' | 'owner' | 'support'
|
||||
role: 'renter' | 'owner' | 'support' | 'customer'
|
||||
remark: string
|
||||
display_name: string
|
||||
avatar_url: string
|
||||
@@ -16,11 +16,11 @@ export interface ChatParticipant {
|
||||
|
||||
export interface ChatConversation {
|
||||
id: number
|
||||
order_id: number
|
||||
order_id: number | null
|
||||
type: string
|
||||
title: string
|
||||
status: string
|
||||
role: 'renter' | 'owner' | 'support'
|
||||
role: 'renter' | 'owner' | 'support' | 'customer'
|
||||
participants?: ChatParticipant[]
|
||||
last_message_id?: number
|
||||
last_message_preview: string
|
||||
@@ -35,7 +35,7 @@ export interface ChatMessage {
|
||||
conversation_id: number
|
||||
sender_type: 'user' | 'admin' | 'system'
|
||||
sender_id: number
|
||||
sender_role: 'renter' | 'owner' | 'support' | 'system'
|
||||
sender_role: 'renter' | 'owner' | 'support' | 'customer' | 'system'
|
||||
sender_name: string
|
||||
sender_avatar: string
|
||||
is_self: boolean
|
||||
@@ -62,6 +62,11 @@ export async function fetchOrderChat(orderId: number) {
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function ensureSupportChat() {
|
||||
const { data } = await apiClient.post<ApiResponse<ChatConversation>>('/chats/support')
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchChatMessages(id: number, page = 1, pageSize = 100) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<ChatMessage>>>(`/chats/${id}/messages`, {
|
||||
params: { page, page_size: pageSize },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
ArrowDown,
|
||||
ChatDotRound,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
UserFilled,
|
||||
Wallet,
|
||||
} from "@element-plus/icons-vue";
|
||||
import { ensureSupportChat } from "@/api/chats";
|
||||
import { useSessionStore } from "@/stores/session";
|
||||
|
||||
const session = useSessionStore();
|
||||
@@ -26,12 +28,30 @@ const navItems = [
|
||||
];
|
||||
|
||||
const showUserDropdown = ref(false);
|
||||
const supportLoading = ref(false);
|
||||
|
||||
function handleLogout() {
|
||||
session.logout();
|
||||
showUserDropdown.value = false;
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
async function handleSupportClick() {
|
||||
if (!session.isLoggedIn) {
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
return;
|
||||
}
|
||||
if (supportLoading.value) return;
|
||||
supportLoading.value = true;
|
||||
try {
|
||||
const chat = await ensureSupportChat();
|
||||
router.push(`/messages/${chat.id}`);
|
||||
} catch {
|
||||
ElMessage.error("联系客服失败,请稍后重试");
|
||||
} finally {
|
||||
supportLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -63,10 +83,10 @@ function handleLogout() {
|
||||
</div>
|
||||
|
||||
<div class="pc-user-actions">
|
||||
<RouterLink class="pc-icon-link service-link" to="/notifications" title="联系客服">
|
||||
<button class="pc-icon-link service-link" type="button" title="联系客服" @click="handleSupportClick">
|
||||
<el-icon><Service /></el-icon>
|
||||
<span>客服</span>
|
||||
</RouterLink>
|
||||
<span>{{ supportLoading ? "接入中" : "客服" }}</span>
|
||||
</button>
|
||||
|
||||
<RouterLink class="pc-post-link" :to="session.isLoggedIn ? '/seller/listings/create' : '/login'">
|
||||
<el-icon><CirclePlus /></el-icon>
|
||||
@@ -310,6 +330,7 @@ function handleLogout() {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ const listRef = ref<HTMLElement | null>(null)
|
||||
const conversationID = computed(() => Number(route.params.id || 0))
|
||||
const memberText = computed(() => {
|
||||
const participants = conversation.value?.participants || []
|
||||
if (participants.length === 0) return '订单群聊'
|
||||
if (participants.length === 0) return conversation.value?.type === 'general_support' ? '平台客服' : '订单群聊'
|
||||
return participants.map(item => roleLabel(item.role)).join(' · ')
|
||||
})
|
||||
|
||||
@@ -138,6 +138,7 @@ function roleLabel(role: string) {
|
||||
renter: '租客',
|
||||
owner: '号主',
|
||||
support: '客服',
|
||||
customer: '咨询',
|
||||
system: '系统',
|
||||
}
|
||||
return map[role] || '成员'
|
||||
@@ -166,7 +167,7 @@ function handleKeydown(e: KeyboardEvent) {
|
||||
<span>返回</span>
|
||||
</button>
|
||||
<div class="chat-title">
|
||||
<h2>{{ conversation?.title || '订单群聊' }}</h2>
|
||||
<h2>{{ conversation?.title || '客服会话' }}</h2>
|
||||
<p>{{ memberText }}</p>
|
||||
</div>
|
||||
<el-button
|
||||
|
||||
@@ -51,12 +51,12 @@ function openConversation(item: ChatConversation) {
|
||||
}
|
||||
|
||||
function roleLabel(role: string) {
|
||||
const map: Record<string, string> = { renter: '租客', owner: '号主', support: '客服' }
|
||||
const map: Record<string, string> = { renter: '租客', owner: '号主', support: '客服', customer: '咨询' }
|
||||
return map[role] || '成员'
|
||||
}
|
||||
|
||||
function previewText(item: ChatConversation) {
|
||||
return item.last_message_preview || '订单群聊已创建'
|
||||
return item.last_message_preview || (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -66,7 +66,7 @@ function previewText(item: ChatConversation) {
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Messages</p>
|
||||
<h1>消息</h1>
|
||||
<p>{{ unreadTotal > 0 ? `${unreadTotal} 条未读` : '查看订单群聊消息,与租客、号主和客服沟通。' }}</p>
|
||||
<p>{{ unreadTotal > 0 ? `${unreadTotal} 条未读` : '查看订单群聊和平台客服消息。' }}</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button :icon="Refresh" :loading="loading" @click="loadChats(true)">刷新</el-button>
|
||||
@@ -77,7 +77,7 @@ function previewText(item: ChatConversation) {
|
||||
<div v-if="loading && conversations.length === 0" class="message-loading" v-loading="loading" />
|
||||
|
||||
<div v-else-if="!loading && conversations.length === 0" class="empty-panel">
|
||||
<el-empty description="暂无订单群聊">
|
||||
<el-empty description="暂无会话">
|
||||
<el-button type="primary" :icon="Tickets" @click="router.push('/orders')">查看订单</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
@@ -101,7 +101,7 @@ function previewText(item: ChatConversation) {
|
||||
</div>
|
||||
<div class="conversation-meta">
|
||||
<span class="role-chip">{{ roleLabel(item.role) }}</span>
|
||||
<span class="order-id">订单 #{{ item.order_id }}</span>
|
||||
<span class="order-id">{{ item.order_id ? `订单 #${item.order_id}` : '平台客服' }}</span>
|
||||
</div>
|
||||
<p class="conversation-preview">{{ previewText(item) }}</p>
|
||||
</div>
|
||||
|
||||
@@ -200,6 +200,7 @@ function roleLabel(role: string) {
|
||||
renter: '租客',
|
||||
owner: '号主',
|
||||
support: '客服',
|
||||
customer: '咨询',
|
||||
system: '系统',
|
||||
}
|
||||
return map[role] || '成员'
|
||||
@@ -227,8 +228,8 @@ function getSupportName(item: ChatConversation) {
|
||||
<section class="admin-page">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>客服群聊</h1>
|
||||
<p>处理订单三方沟通</p>
|
||||
<h1>客服会话</h1>
|
||||
<p>处理订单三方沟通和平台咨询</p>
|
||||
</div>
|
||||
<div class="head-right">
|
||||
<el-button @click="quickReplyVisible = true">快捷回复管理</el-button>
|
||||
@@ -257,7 +258,7 @@ function getSupportName(item: ChatConversation) {
|
||||
<strong>{{ getConversationTitle(item) }}</strong>
|
||||
<span>{{ formatDateMinute(item.last_message_at || item.created_at) }}</span>
|
||||
</div>
|
||||
<p>{{ item.last_message_preview || '订单群聊已创建' }}</p>
|
||||
<p>{{ item.last_message_preview || (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建') }}</p>
|
||||
<div class="row-meta">
|
||||
<span class="support-name">{{ getSupportName(item) }}</span>
|
||||
<em v-if="item.unread_count > 0">{{ item.unread_count }}</em>
|
||||
@@ -288,7 +289,7 @@ function getSupportName(item: ChatConversation) {
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<el-button size="small" @click="transferVisible = true">转接</el-button>
|
||||
<RouterLink :to="`/admin/orders/${active.order_id}`">
|
||||
<RouterLink v-if="active.order_id" :to="`/admin/orders/${active.order_id}`">
|
||||
<el-button size="small">查看订单</el-button>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@ const listRef = ref<HTMLElement | null>(null)
|
||||
const conversationID = computed(() => Number(route.params.id || 0))
|
||||
const memberText = computed(() => {
|
||||
const participants = conversation.value?.participants || []
|
||||
if (participants.length === 0) return '订单群聊'
|
||||
if (participants.length === 0) return conversation.value?.type === 'general_support' ? '平台客服' : '订单群聊'
|
||||
return participants.map(item => roleLabel(item.role)).join(' · ')
|
||||
})
|
||||
|
||||
@@ -123,6 +123,7 @@ function roleLabel(role: string) {
|
||||
renter: '租客',
|
||||
owner: '号主',
|
||||
support: '客服',
|
||||
customer: '咨询',
|
||||
system: '系统',
|
||||
}
|
||||
return map[role] || '成员'
|
||||
@@ -141,12 +142,13 @@ function senderLabel(message: ChatMessage) {
|
||||
<van-icon name="arrow-left" :size="20" />
|
||||
</button>
|
||||
<div class="chat-title">
|
||||
<h1>{{ conversation?.title || '订单群聊' }}</h1>
|
||||
<h1>{{ conversation?.title || '客服会话' }}</h1>
|
||||
<p>{{ memberText }}</p>
|
||||
</div>
|
||||
<button class="icon-btn" type="button" @click="conversation && router.push(`/m/orders/${conversation.order_id}`)">
|
||||
<button v-if="conversation?.order_id" class="icon-btn" type="button" @click="router.push(`/m/orders/${conversation.order_id}`)">
|
||||
<van-icon name="orders-o" :size="20" />
|
||||
</button>
|
||||
<span v-else class="icon-placeholder"></span>
|
||||
</header>
|
||||
|
||||
<section ref="listRef" class="message-list" :class="{ loading }">
|
||||
@@ -216,6 +218,12 @@ function senderLabel(message: ChatMessage) {
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.icon-placeholder {
|
||||
display: block;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
import { RouterLink, useRouter } from "vue-router";
|
||||
import { showToast } from "vant";
|
||||
import MobileBottomNav from "@/components/MobileBottomNav.vue";
|
||||
|
||||
import { ensureSupportChat } from "@/api/chats";
|
||||
import {
|
||||
emptyListingPublishOptions,
|
||||
type ListingPublishOptions,
|
||||
@@ -36,7 +37,10 @@ import {
|
||||
readAssetNumber,
|
||||
readAssetString,
|
||||
} from "@/utils/listingDisplay";
|
||||
import { useSessionStore } from "@/stores/session";
|
||||
|
||||
const router = useRouter();
|
||||
const session = useSessionStore();
|
||||
const loading = ref(false);
|
||||
const loadFailed = ref(false);
|
||||
const listings = ref<Listing[]>([]);
|
||||
@@ -50,6 +54,7 @@ const refreshing = ref(false);
|
||||
const searchValue = ref("");
|
||||
const announcements = ref<string[]>(defaultHomeAnnouncements);
|
||||
const bannerSlides = ref<HomeBannerSlide[]>(defaultHomeBanners);
|
||||
const supportLoading = ref(false);
|
||||
|
||||
const sortOptions = [
|
||||
{ key: "comprehensive", label: "综合排序" },
|
||||
@@ -81,6 +86,23 @@ const activeSortLabel = computed(
|
||||
"综合排序"
|
||||
);
|
||||
|
||||
async function handleSupportClick() {
|
||||
if (!session.isLoggedIn) {
|
||||
router.push({ path: "/m/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
return;
|
||||
}
|
||||
if (supportLoading.value) return;
|
||||
supportLoading.value = true;
|
||||
try {
|
||||
const chat = await ensureSupportChat();
|
||||
router.push(`/m/chats/${chat.id}`);
|
||||
} catch {
|
||||
showToast({ message: "联系客服失败,请稍后重试", icon: "cross" });
|
||||
} finally {
|
||||
supportLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const serverFilterOptions = computed(() =>
|
||||
uniqueOptions(
|
||||
publishOptions.value.server_options
|
||||
@@ -327,7 +349,9 @@ function uniqueOptions(values: string[]) {
|
||||
placeholder="搜区服 / 段位"
|
||||
class="home-search"
|
||||
/>
|
||||
<button class="mobile-service" type="button">客服</button>
|
||||
<button class="mobile-service" type="button" :disabled="supportLoading" @click="handleSupportClick">
|
||||
{{ supportLoading ? "接入中" : "客服" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 防骗提示卡片(不用 van-notice-bar) -->
|
||||
@@ -498,4 +522,3 @@ function uniqueOptions(values: string[]) {
|
||||
</template>
|
||||
|
||||
<style scoped src="./MobileHomeView.css"></style>
|
||||
|
||||
|
||||
@@ -62,12 +62,13 @@ function roleLabel(role: string) {
|
||||
renter: '租客',
|
||||
owner: '号主',
|
||||
support: '客服',
|
||||
customer: '咨询',
|
||||
}
|
||||
return map[role] || '成员'
|
||||
}
|
||||
|
||||
function previewText(item: ChatConversation) {
|
||||
return item.last_message_preview || '订单群聊已创建'
|
||||
return item.last_message_preview || (item.type === 'general_support' ? '客服会话已创建' : '订单群聊已创建')
|
||||
}
|
||||
|
||||
const unreadTotal = computed(() => conversations.value.reduce((sum, item) => sum + item.unread_count, 0))
|
||||
@@ -93,7 +94,7 @@ const unreadTotal = computed(() => conversations.value.reduce((sum, item) => sum
|
||||
>
|
||||
<van-empty
|
||||
v-if="!loading && conversations.length === 0"
|
||||
description="暂无订单群聊"
|
||||
description="暂无会话"
|
||||
class="empty-state"
|
||||
/>
|
||||
|
||||
@@ -116,7 +117,7 @@ const unreadTotal = computed(() => conversations.value.reduce((sum, item) => sum
|
||||
</div>
|
||||
<div class="conversation-meta">
|
||||
<span class="role-chip">{{ roleLabel(item.role) }}</span>
|
||||
<span>订单 #{{ item.order_id }}</span>
|
||||
<span>{{ item.order_id ? `订单 #${item.order_id}` : '平台客服' }}</span>
|
||||
</div>
|
||||
<p>{{ previewText(item) }}</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user