diff --git a/frontend/src/components/Dashboard-AI.vue b/frontend/src/components/Dashboard-AI.vue
index a5c5bdb..71cfccc 100644
--- a/frontend/src/components/Dashboard-AI.vue
+++ b/frontend/src/components/Dashboard-AI.vue
@@ -7,6 +7,55 @@
获取 AI 推荐
+
+
+
+
+
+
+
+
+
+
+
+
@@ -27,6 +76,20 @@
+
+
+
+
+
+
+
{{ scope.row.title }}
@@ -114,7 +177,7 @@
import { ref, watch } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
-import { MagicStick, Loading, InfoFilled, List, ArrowDown } from '@element-plus/icons-vue'
+import { MagicStick, Loading, InfoFilled, List, ArrowDown, Paperclip } from '@element-plus/icons-vue'
interface AIRecommendation {
@@ -123,6 +186,7 @@ interface AIRecommendation {
source: string
confidence: number
publishDate?: string
+ pin?: boolean
}
interface Props {
@@ -137,6 +201,8 @@ const dateRange = ref<[string, string] | null>(null)
const showAllBids = ref(false)
const bidsLoading = ref(false)
const bidsByDateRange = ref([])
+const pinnedBids = ref([])
+const pinnedLoading = ref(false)
// 从 localStorage 加载保存的日期范围
const loadSavedDateRange = () => {
@@ -159,15 +225,39 @@ watch(dateRange, (newDateRange) => {
const loadLatestRecommendations = async () => {
try {
const response = await axios.get('/api/ai/latest-recommendations')
- aiRecommendations.value = response.data
+ const recommendations = response.data
+
+ // 获取所有置顶的项目
+ const pinnedResponse = await axios.get('/api/bids/pinned')
+ const pinnedTitles = new Set(pinnedResponse.data.map((b: any) => b.title))
+
+ // 更新每个推荐项目的 pin 状态
+ aiRecommendations.value = recommendations.map((rec: any) => ({
+ ...rec,
+ pin: pinnedTitles.has(rec.title)
+ }))
} catch (error) {
console.error('Failed to load latest recommendations:', error)
}
}
-// 初始化时加载保存的日期范围和最新的 AI 推荐
+// 加载置顶项目
+const loadPinnedBids = async () => {
+ pinnedLoading.value = true
+ try {
+ const response = await axios.get('/api/bids/pinned')
+ pinnedBids.value = response.data
+ } catch (error) {
+ console.error('Failed to load pinned bids:', error)
+ } finally {
+ pinnedLoading.value = false
+ }
+}
+
+// 初始化时加载保存的日期范围、最新的 AI 推荐和置顶项目
loadSavedDateRange()
loadLatestRecommendations()
+loadPinnedBids()
// 设置日期范围为最近3天
const setLast3Days = () => {
@@ -225,7 +315,8 @@ const fetchAIRecommendations = async () => {
url: bid?.url || '',
source: bid?.source || '',
confidence: rec.confidence,
- publishDate: bid?.publishDate
+ publishDate: bid?.publishDate,
+ pin: bid?.pin || false
}
})
@@ -299,6 +390,53 @@ const getConfidenceType = (confidence: number) => {
if (confidence >= 70) return 'warning'
return 'info'
}
+
+// 切换 AI 推荐项目的 Pin 状态
+const togglePin = async (item: AIRecommendation) => {
+ try {
+ const newPinStatus = !item.pin
+ await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
+ item.pin = newPinStatus
+ if (newPinStatus) {
+ // 添加到置顶列表
+ pinnedBids.value.unshift({
+ title: item.title,
+ url: item.url,
+ source: item.source,
+ publishDate: item.publishDate,
+ pin: true
+ })
+ } else {
+ // 从置顶列表移除
+ const index = pinnedBids.value.findIndex(b => b.title === item.title)
+ if (index !== -1) {
+ pinnedBids.value.splice(index, 1)
+ }
+ }
+ ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
+ } catch (error) {
+ ElMessage.error('操作失败')
+ }
+}
+
+// 切换置顶列表的 Pin 状态
+const togglePinnedPin = async (item: any) => {
+ try {
+ await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: false })
+ const index = pinnedBids.value.findIndex(b => b.title === item.title)
+ if (index !== -1) {
+ pinnedBids.value.splice(index, 1)
+ }
+ // 同时更新 AI 推荐列表中的状态
+ const aiItem = aiRecommendations.value.find(r => r.title === item.title)
+ if (aiItem) {
+ aiItem.pin = false
+ }
+ ElMessage.success('已取消置顶')
+ } catch (error) {
+ ElMessage.error('操作失败')
+ }
+}