Files
sionrui/frontend/app/web-gold/src/components/agents/MyFavoritesModal.vue
sion123 8f8b0a03e4 feat(agent): 将收藏夹卡片使用次数改为显示创建时间
在收藏夹列表中,将使用次数显示替换为创建时间戳,使用 dayjs 格式化显示,并调整卡片底部布局为两端对齐。
2026-04-27 20:00:36 +08:00

283 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, computed, watch } from 'vue'
import { toast } from 'vue-sonner'
import dayjs from 'dayjs'
import { Icon } from '@iconify/vue'
import { UserPromptApi } from '@/api/userPrompt'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from '@/components/ui/alert-dialog'
// Props
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
// Emits
const emit = defineEmits(['update:visible', 'refresh', 'chat'])
// State
const loading = ref(false)
const promptList = ref([])
const searchKeyword = ref('')
// Computed - 只搜索名称
const filteredList = computed(() => {
if (!searchKeyword.value.trim()) return promptList.value
const keyword = searchKeyword.value.trim().toLowerCase()
return promptList.value.filter(item =>
item.name.toLowerCase().includes(keyword)
)
})
// Watch
watch(() => props.visible, (newVal) => {
if (newVal) {
loadList()
}
})
// Methods
async function loadList() {
loading.value = true
try {
const res = await UserPromptApi.getUserPromptList()
if (res.code === 0 && res.data) {
promptList.value = res.data.map(item => ({
id: item.id,
name: item.name,
content: item.content,
category: item.category,
createTime: item.createTime
}))
}
} catch (error) {
console.error('加载风格列表失败:', error)
toast.error('加载风格列表失败')
} finally {
loading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
async function handleDelete(id) {
try {
await UserPromptApi.deleteUserPrompt(id)
const index = promptList.value.findIndex(p => p.id === id)
if (index !== -1) {
promptList.value.splice(index, 1)
}
toast.success('删除成功')
emit('refresh')
} catch (error) {
console.error('删除失败:', error)
toast.error('删除失败')
}
}
function handleUse(item) {
emit('chat', {
source: 'prompt',
id: item.id,
name: item.name,
categoryName: item.category || '我的风格',
customSystemPrompt: item.content
})
handleClose()
}
</script>
<template>
<Dialog :open="visible" @update:open="$emit('update:visible', $event)">
<DialogContent class="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>我创建的风格</DialogTitle>
</DialogHeader>
<!-- 操作栏 -->
<div class="flex gap-3 mb-4">
<Button variant="outline" @click="loadList" :disabled="loading">
<Icon v-if="loading" icon="lucide:loader-2" class="size-4 animate-spin" />
<Icon v-else icon="lucide:refresh-cw" class="size-4" />
刷新
</Button>
</div>
<!-- 搜索框 -->
<div class="mb-4">
<Input
v-model="searchKeyword"
placeholder="搜索风格名称..."
/>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-12">
<Icon icon="lucide:loader-2" class="size-8 animate-spin text-primary" />
</div>
<!-- 列表 -->
<div class="prompt-list" v-else-if="filteredList.length > 0">
<div
v-for="item in filteredList"
:key="item.id"
class="prompt-card"
>
<div class="card-header">
<div class="card-info">
<span class="card-name">{{ item.name }}</span>
<span class="card-category" v-if="item.category">{{ item.category }}</span>
</div>
<div class="flex gap-2">
<Button variant="default" size="sm" @click="handleUse(item)">
<Icon icon="lucide:message-circle" class="size-4" />
使用
</Button>
<AlertDialog>
<AlertDialogTrigger as-child>
<Button variant="ghost" size="sm" class="text-muted-foreground hover:text-destructive">
<Icon icon="lucide:trash-2" class="size-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认删除</AlertDialogTitle>
<AlertDialogDescription>
确定要删除"{{ item.name }}"此操作无法撤销
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction @click="handleDelete(item.id)">确认删除</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
<div class="card-footer">
<span class="text-xs text-muted-foreground" v-if="item.createTime">{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm') }}</span>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-else>
<Icon icon="lucide:sparkles" class="empty-icon" />
<p class="empty-title">暂无创建的风格</p>
<p class="empty-desc">在对话中可保存生成的内容为风格</p>
</div>
</DialogContent>
</Dialog>
</template>
<style scoped lang="less">
.prompt-list {
display: flex;
flex-direction: column;
gap: 10px;
max-height: 400px;
overflow-y: auto;
}
.prompt-card {
padding: 14px 16px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--card);
transition: all 0.2s ease;
&:hover {
border-color: var(--primary);
box-shadow: 0 2px 8px oklch(0.55 0.14 270 / 0.1);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-info {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.card-name {
font-weight: 600;
font-size: 14px;
color: var(--foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-category {
font-size: 11px;
padding: 2px 8px;
background: var(--muted);
color: var(--muted-foreground);
border-radius: 6px;
flex-shrink: 0;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--border);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
text-align: center;
}
.empty-icon {
font-size: 40px;
color: var(--primary);
margin-bottom: 16px;
opacity: 0.5;
}
.empty-title {
font-size: 15px;
font-weight: 500;
color: var(--foreground);
margin: 0 0 8px 0;
}
.empty-desc {
font-size: 13px;
color: var(--muted-foreground);
margin: 0;
}
</style>