refactor: replace ant-design components with shadcn/ui and update toast notifications
This commit migrates from Ant Design Vue components to Shadcn/Vue components across multiple files in the web-gold frontend application. Key changes include: - Replaced ant-design-vue imports with shadcn/ui components (Dialog, Button, Input, etc.) - Swapped ant-design-vue message/toast system for vue-sonner toast notifications - Updated icon usage from ant-design icons to lucide icons via @iconify/vue - Removed unused token refresh logic that was incorrectly implemented client-side - Applied consistent styling updates to match new component library The token refresh functionality was removed since it should be handled server-side through axios interceptors rather than client-side intervals.
This commit is contained in:
@@ -12,13 +12,13 @@
|
||||
<div class="header-content">
|
||||
<div v-if="selectedConversation" class="header-nav">
|
||||
<button class="nav-back" @click="backToList">
|
||||
<LeftOutlined />
|
||||
<Icon icon="lucide:arrow-left" class="size-4" />
|
||||
<span>返回列表</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="header-title-group">
|
||||
<h2 class="modal-title">
|
||||
<HistoryOutlined class="title-icon" />
|
||||
<Icon icon="lucide:history" class="title-icon" />
|
||||
{{ selectedConversation ? '对话详情' : '历史记录' }}
|
||||
</h2>
|
||||
<p v-if="!selectedConversation" class="modal-subtitle">
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button class="close-btn" @click="handleClose">
|
||||
<CloseOutlined />
|
||||
<Icon icon="lucide:x" class="size-4" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@@ -35,107 +35,113 @@
|
||||
<div class="modal-body">
|
||||
<!-- Conversation List View -->
|
||||
<div v-if="!selectedConversation" class="list-view">
|
||||
<a-spin :spinning="loading">
|
||||
<!-- Empty State -->
|
||||
<div v-if="groupedConversations.length === 0 && !loading" class="empty-state">
|
||||
<div class="empty-illustration">
|
||||
<div class="illustration-ring"></div>
|
||||
<div class="illustration-ring delay-1"></div>
|
||||
<HistoryOutlined class="illustration-icon" />
|
||||
</div>
|
||||
<h3 class="empty-title">暂无历史记录</h3>
|
||||
<p class="empty-desc">开始对话后,记录将保存在这里</p>
|
||||
</div>
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="loading-state">
|
||||
<Icon icon="lucide:loader-2" class="size-8 animate-spin text-primary" />
|
||||
</div>
|
||||
|
||||
<!-- Grouped Conversations -->
|
||||
<div v-else class="conversation-groups">
|
||||
<div
|
||||
v-for="(group, groupIndex) in groupedConversations"
|
||||
:key="group.label"
|
||||
class="conversation-group"
|
||||
:style="{ '--group-index': groupIndex }"
|
||||
>
|
||||
<div class="group-header">
|
||||
<span class="group-dot"></span>
|
||||
<span class="group-label">{{ group.label }}</span>
|
||||
<span class="group-count">{{ group.items.length }}</span>
|
||||
</div>
|
||||
<div class="group-items">
|
||||
<button
|
||||
v-for="(item, itemIndex) in group.items"
|
||||
:key="item.id"
|
||||
class="conversation-item"
|
||||
:style="{ '--item-index': itemIndex }"
|
||||
@click="selectConversation(item)"
|
||||
>
|
||||
<div class="item-indicator"></div>
|
||||
<div class="item-content">
|
||||
<h4 class="item-title">{{ item.name || '未命名会话' }}</h4>
|
||||
<p class="item-preview">{{ item.preview || '暂无预览内容' }}</p>
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="groupedConversations.length === 0" class="empty-state">
|
||||
<div class="empty-illustration">
|
||||
<div class="illustration-ring"></div>
|
||||
<div class="illustration-ring delay-1"></div>
|
||||
<Icon icon="lucide:history" class="illustration-icon" />
|
||||
</div>
|
||||
<h3 class="empty-title">暂无历史记录</h3>
|
||||
<p class="empty-desc">开始对话后,记录将保存在这里</p>
|
||||
</div>
|
||||
|
||||
<!-- Grouped Conversations -->
|
||||
<div v-else class="conversation-groups">
|
||||
<div
|
||||
v-for="(group, groupIndex) in groupedConversations"
|
||||
:key="group.label"
|
||||
class="conversation-group"
|
||||
:style="{ '--group-index': groupIndex }"
|
||||
>
|
||||
<div class="group-header">
|
||||
<span class="group-dot"></span>
|
||||
<span class="group-label">{{ group.label }}</span>
|
||||
<span class="group-count">{{ group.items.length }}</span>
|
||||
</div>
|
||||
<div class="group-items">
|
||||
<button
|
||||
v-for="(item, itemIndex) in group.items"
|
||||
:key="item.id"
|
||||
class="conversation-item"
|
||||
:style="{ '--item-index': itemIndex }"
|
||||
@click="selectConversation(item)"
|
||||
>
|
||||
<div class="item-indicator"></div>
|
||||
<div class="item-content">
|
||||
<h4 class="item-title">{{ item.name || '未命名会话' }}</h4>
|
||||
<p class="item-preview">{{ item.preview || '暂无预览内容' }}</p>
|
||||
</div>
|
||||
<div class="item-meta">
|
||||
<span class="meta-time">
|
||||
<Icon icon="lucide:clock" class="size-3" />
|
||||
{{ formatTime(item.updatedAt || item.createdAt) }}
|
||||
</span>
|
||||
<div class="meta-arrow">
|
||||
<Icon icon="lucide:chevron-right" class="size-4" />
|
||||
</div>
|
||||
<div class="item-meta">
|
||||
<span class="meta-time">
|
||||
<ClockCircleOutlined />
|
||||
{{ formatTime(item.updatedAt || item.createdAt) }}
|
||||
</span>
|
||||
<div class="meta-arrow">
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Detail View -->
|
||||
<div v-else class="detail-view">
|
||||
<a-spin :spinning="messageLoading">
|
||||
<div v-if="messageList.length === 0 && !messageLoading" class="empty-state">
|
||||
<CommentOutlined class="empty-icon-single" />
|
||||
<p>暂无消息记录</p>
|
||||
</div>
|
||||
<!-- Loading State -->
|
||||
<div v-if="messageLoading" class="loading-state">
|
||||
<Icon icon="lucide:loader-2" class="size-8 animate-spin text-primary" />
|
||||
</div>
|
||||
|
||||
<div v-else class="message-timeline">
|
||||
<div
|
||||
v-for="(msg, index) in messageList"
|
||||
:key="msg.id"
|
||||
class="timeline-item"
|
||||
:style="{ '--msg-index': index }"
|
||||
>
|
||||
<!-- User Message -->
|
||||
<div class="message-block message-block--user">
|
||||
<div class="message-avatar">
|
||||
<UserOutlined />
|
||||
</div>
|
||||
<div class="message-body">
|
||||
<div class="message-header">
|
||||
<span class="message-author">你</span>
|
||||
<span class="message-time">{{ formatTime(msg.createdAt) }}</span>
|
||||
</div>
|
||||
<div class="message-content">{{ msg.query }}</div>
|
||||
</div>
|
||||
<div v-else-if="messageList.length === 0" class="empty-state">
|
||||
<Icon icon="lucide:message-circle" class="empty-icon-single" />
|
||||
<p>暂无消息记录</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="message-timeline">
|
||||
<div
|
||||
v-for="(msg, index) in messageList"
|
||||
:key="msg.id"
|
||||
class="timeline-item"
|
||||
:style="{ '--msg-index': index }"
|
||||
>
|
||||
<!-- User Message -->
|
||||
<div class="message-block message-block--user">
|
||||
<div class="message-avatar">
|
||||
<Icon icon="lucide:user" class="size-4" />
|
||||
</div>
|
||||
<div class="message-body">
|
||||
<div class="message-header">
|
||||
<span class="message-author">你</span>
|
||||
<span class="message-time">{{ formatTime(msg.createdAt) }}</span>
|
||||
</div>
|
||||
<div class="message-content">{{ msg.query }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Response -->
|
||||
<div class="message-block message-block--ai">
|
||||
<div class="message-avatar">
|
||||
<RobotOutlined />
|
||||
</div>
|
||||
<div class="message-body">
|
||||
<div class="message-header">
|
||||
<span class="message-author">AI 助手</span>
|
||||
<button class="copy-btn" @click="copyContent(msg.answer)" title="复制">
|
||||
<CopyOutlined />
|
||||
</button>
|
||||
</div>
|
||||
<div class="message-content">{{ msg.answer }}</div>
|
||||
<!-- AI Response -->
|
||||
<div class="message-block message-block--ai">
|
||||
<div class="message-avatar">
|
||||
<Icon icon="lucide:bot" class="size-4" />
|
||||
</div>
|
||||
<div class="message-body">
|
||||
<div class="message-header">
|
||||
<span class="message-author">AI 助手</span>
|
||||
<button class="copy-btn" @click="copyContent(msg.answer)" title="复制">
|
||||
<Icon icon="lucide:copy" class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="message-content">{{ msg.answer }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,18 +152,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import {
|
||||
CloseOutlined,
|
||||
LeftOutlined,
|
||||
RightOutlined,
|
||||
HistoryOutlined,
|
||||
ClockCircleOutlined,
|
||||
CopyOutlined,
|
||||
UserOutlined,
|
||||
RobotOutlined,
|
||||
CommentOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { getConversations, getMessages } from '@/api/agent'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
@@ -253,7 +249,7 @@ const handleClose = () => {
|
||||
|
||||
const copyContent = async (content) => {
|
||||
const success = await copyToClipboard(content)
|
||||
success ? message.success('已复制到剪贴板') : message.error('复制失败')
|
||||
success ? toast.success('已复制到剪贴板') : toast.error('复制失败')
|
||||
}
|
||||
|
||||
const formatTime = (timestamp) => {
|
||||
@@ -444,6 +440,16 @@ watch(() => props.visible, (val) => {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Loading State
|
||||
// ========================================
|
||||
.loading-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Empty State
|
||||
// ========================================
|
||||
|
||||
Reference in New Issue
Block a user