refactor: 将置顶项目功能提取为独立组件

This commit is contained in:
dmy
2026-01-13 21:03:52 +08:00
parent 50bc930663
commit feb18c01bb
3 changed files with 126 additions and 101 deletions

View File

@@ -7,55 +7,7 @@
获取 AI 推荐 获取 AI 推荐
</el-button> </el-button>
</div> </div>
<el-row :gutter="20"> <PinnedProject />
<el-col :span="24">
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header">
<span>Pined</span>
<el-tag type="danger">{{ pinnedBids.length }} 个置顶</el-tag>
</div>
</template>
<div v-if="pinnedLoading" style="text-align: center; padding: 40px;">
<el-icon class="is-loading" :size="30"><Loading /></el-icon>
<p style="margin-top: 10px; color: #909399;">加载中...</p>
</div>
<div v-else-if="pinnedBids.length === 0" style="text-align: center; padding: 40px; color: #909399;">
<el-icon :size="40"><InfoFilled /></el-icon>
<p style="margin-top: 10px;">暂无置顶项目</p>
</div>
<div v-else>
<el-table :data="pinnedBids" style="width: 100%" size="small">
<el-table-column label="Pin" width="60" align="center">
<template #default="scope">
<el-icon
:style="{
color: '#f56c6c',
cursor: 'pointer',
fontSize: '18px'
}"
@click="togglePinnedPin(scope.row)"
>
<Paperclip />
</el-icon>
</template>
</el-table-column>
<el-table-column prop="title" label="项目名称">
<template #default="scope">
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
</template>
</el-table-column>
<el-table-column prop="source" label="来源" width="200" />
<el-table-column prop="publishDate" label="发布日期" width="180">
<template #default="scope">
{{ formatDate(scope.row.publishDate) }}
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24"> <el-col :span="24">
<el-card class="box-card" shadow="hover"> <el-card class="box-card" shadow="hover">
@@ -178,6 +130,7 @@ import { ref, watch } from 'vue'
import axios from 'axios' import axios from 'axios'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { MagicStick, Loading, InfoFilled, List, ArrowDown, Paperclip } from '@element-plus/icons-vue' import { MagicStick, Loading, InfoFilled, List, ArrowDown, Paperclip } from '@element-plus/icons-vue'
import PinnedProject from './PinnedProject.vue'
interface AIRecommendation { interface AIRecommendation {
@@ -201,8 +154,6 @@ const dateRange = ref<[string, string] | null>(null)
const showAllBids = ref(false) const showAllBids = ref(false)
const bidsLoading = ref(false) const bidsLoading = ref(false)
const bidsByDateRange = ref<any[]>([]) const bidsByDateRange = ref<any[]>([])
const pinnedBids = ref<any[]>([])
const pinnedLoading = ref(false)
// 从 localStorage 加载保存的日期范围 // 从 localStorage 加载保存的日期范围
const loadSavedDateRange = () => { const loadSavedDateRange = () => {
@@ -241,23 +192,9 @@ const loadLatestRecommendations = async () => {
} }
} }
// 加载置顶项目 // 初始化时加载保存的日期范围和最新的 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() loadSavedDateRange()
loadLatestRecommendations() loadLatestRecommendations()
loadPinnedBids()
// 设置日期范围为最近3天 // 设置日期范围为最近3天
const setLast3Days = () => { const setLast3Days = () => {
@@ -397,46 +334,11 @@ const togglePin = async (item: AIRecommendation) => {
const newPinStatus = !item.pin const newPinStatus = !item.pin
await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus }) await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
item.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 ? '已置顶' : '已取消置顶') ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
} catch (error) { } catch (error) {
ElMessage.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('操作失败')
}
}
</script> </script>
<style scoped> <style scoped>

View File

@@ -7,6 +7,7 @@
立刻抓取 立刻抓取
</el-button> </el-button>
</div> </div>
<PinnedProject />
<el-divider /> <el-divider />
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0;">Today's Bids</h3> <h3 style="margin: 0;">Today's Bids</h3>
@@ -65,6 +66,7 @@ import { ref, computed, watch } from 'vue'
import axios from 'axios' import axios from 'axios'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Refresh, ArrowDown } from '@element-plus/icons-vue' import { Refresh, ArrowDown } from '@element-plus/icons-vue'
import PinnedProject from './PinnedProject.vue'
interface Props { interface Props {
todayBids: any[] todayBids: any[]

View File

@@ -0,0 +1,121 @@
<template>
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header">
<span>Pined</span>
<el-tag type="danger">{{ pinnedBids.length }} 个置顶</el-tag>
</div>
</template>
<div v-if="pinnedLoading" style="text-align: center; padding: 40px;">
<el-icon class="is-loading" :size="30"><Loading /></el-icon>
<p style="margin-top: 10px; color: #909399;">加载中...</p>
</div>
<div v-else-if="pinnedBids.length === 0" style="text-align: center; padding: 40px; color: #909399;">
<el-icon :size="40"><InfoFilled /></el-icon>
<p style="margin-top: 10px;">暂无置顶项目</p>
</div>
<div v-else>
<el-table :data="pinnedBids" style="width: 100%" size="small">
<el-table-column label="Pin" width="60" align="center">
<template #default="scope">
<el-icon
:style="{
color: '#f56c6c',
cursor: 'pointer',
fontSize: '18px'
}"
@click="togglePin(scope.row)"
>
<Paperclip />
</el-icon>
</template>
</el-table-column>
<el-table-column prop="title" label="项目名称">
<template #default="scope">
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
</template>
</el-table-column>
<el-table-column prop="source" label="来源" width="200" />
<el-table-column prop="publishDate" label="发布日期" width="180">
<template #default="scope">
{{ formatDate(scope.row.publishDate) }}
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { Loading, InfoFilled, Paperclip } from '@element-plus/icons-vue'
const pinnedBids = ref<any[]>([])
const pinnedLoading = ref(false)
// 加载置顶项目
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
}
}
// 切换置顶列表的 Pin 状态
const togglePin = 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)
}
ElMessage.success('已取消置顶')
} catch (error) {
ElMessage.error('操作失败')
}
}
// 格式化日期,只显示年月日
const formatDate = (dateStr: string) => {
if (!dateStr) return ''
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 初始化时加载置顶项目
onMounted(() => {
loadPinnedBids()
})
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.box-card {
margin-top: 10px;
margin-bottom: 10px;
}
a {
text-decoration: none;
color: inherit;
}
a:hover {
color: #409eff;
}
</style>