Files
bidding_watcher/frontend/src/components/Dashboard.vue

280 lines
8.7 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.
<template>
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">Dashboard</h2>
<el-button type="primary" :loading="crawling" :disabled="isCrawling" @click="handleCrawl">
<el-icon style="margin-right: 5px"><Refresh /></el-icon>
立刻抓取
</el-button>
</div>
<el-row :gutter="20">
<el-col :span="24">
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header">
<span>High Priority Bids</span>
<el-tag type="danger">Top 10</el-tag>
</div>
</template>
<el-table :data="highPriorityBids" style="width: 100%" size="small">
<el-table-column prop="title" label="Title">
<template #default="scope">
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
</template>
</el-table-column>
<el-table-column prop="source" label="Source" width="240" />
<el-table-column prop="publishDate" label="Date" width="120">
<template #default="scope">{{ formatDate(scope.row.publishDate) }}</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-divider />
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0;">Today's Bids</h3>
<div style="display: flex; gap: 10px;">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="To"
start-placeholder="Start Date"
end-placeholder="End Date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
style="width: 240px;"
/>
<el-button type="primary" @click="setLast3Days">3天</el-button>
<el-button type="primary" @click="setLast7Days">7天</el-button>
<el-select
v-model="selectedKeywords"
multiple
collapse-tags
collapse-tags-tooltip
placeholder="Filter by Keywords"
clearable
style="width: 300px;"
>
<el-option
v-for="keyword in keywords"
:key="keyword.id"
:label="keyword.word"
:value="keyword.word"
/>
</el-select>
</div>
</div>
<el-table :data="filteredTodayBids" v-loading="loading" style="width: 100%">
<el-table-column prop="title" label="Title">
<template #default="scope">
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
</template>
</el-table-column>
<el-table-column prop="source" label="Source" width="220" />
<el-table-column prop="publishDate" label="Date" width="150">
<template #default="scope">{{ formatDate(scope.row.publishDate) }}</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue'
interface Props {
todayBids: any[]
highPriorityBids: any[]
keywords: any[]
loading: boolean
isCrawling: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
crawl: []
refresh: []
}>()
const selectedKeywords = ref<string[]>([])
const dateRange = ref<[string, string] | null>(null)
const crawling = ref(false)
// 从 localStorage 加载保存的关键字
const loadSavedKeywords = () => {
const saved = localStorage.getItem('selectedKeywords')
if (saved) {
try {
selectedKeywords.value = JSON.parse(saved)
} catch (e) {
console.error('Failed to parse saved keywords:', e)
}
}
}
// 监听关键字变化并保存到 localStorage
watch(selectedKeywords, (newKeywords) => {
localStorage.setItem('selectedKeywords', JSON.stringify(newKeywords))
}, { deep: true })
// 监听日期范围变化并显示提示
watch(dateRange, () => {
const totalBids = props.todayBids.length
const filteredCount = filteredTodayBids.value.length
if (totalBids > 0 && filteredCount < totalBids) {
ElMessage.info(`筛选结果:共 ${filteredCount} 条数据(总共 ${totalBids} 条)`)
}
})
const formatDate = (dateString: string) => {
if (!dateString) return '-'
return new Date(dateString).toLocaleDateString()
}
// 过滤 Today's Bids只显示包含所选关键字的项目并且在日期范围内
const filteredTodayBids = computed(() => {
let result = props.todayBids
// 按关键字筛选
if (selectedKeywords.value.length > 0) {
result = result.filter(bid => {
return selectedKeywords.value.some(keyword =>
bid.title.toLowerCase().includes(keyword.toLowerCase())
)
})
}
// 按日期范围筛选(只限制开始时间,不限制结束时间)
if (dateRange.value && dateRange.value.length === 2) {
const [startDate] = dateRange.value
result = result.filter(bid => {
if (!bid.publishDate) return false
const bidDate = new Date(bid.publishDate)
const start = new Date(startDate)
// 设置时间为当天的开始
start.setHours(0, 0, 0, 0)
return bidDate >= start
})
}
return result
})
// 监听筛选结果变化并显示提示
watch(filteredTodayBids, (newFilteredBids) => {
const totalBids = props.todayBids.length
const filteredCount = newFilteredBids.length
if (totalBids > 0 && filteredCount < totalBids) {
ElMessage.info(`筛选结果:共 ${filteredCount} 条数据(总共 ${totalBids} 条)`)
}
}, { deep: true })
// 设置日期范围为最近3天
const setLast3Days = () => {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(startDate.getDate() - 2) // 最近3天包括今天
const formatDateForPicker = (date: Date) => {
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}`
}
dateRange.value = [formatDateForPicker(startDate), formatDateForPicker(endDate)]
console.log('setLast3Days called, todayBids:', props.todayBids.length, 'dateRange:', dateRange.value)
// 直接计算筛选结果并显示提示(只限制开始时间,不限制结束时间)
const start = new Date(startDate)
start.setHours(0, 0, 0, 0)
let result = props.todayBids
result = result.filter(bid => {
if (!bid.publishDate) return false
const bidDate = new Date(bid.publishDate)
return bidDate >= start
})
const totalBids = props.todayBids.length
const filteredCount = result.length
console.log('setLast3Days result, totalBids:', totalBids, 'filteredCount:', filteredCount)
if (totalBids === 0) {
ElMessage.warning('暂无数据,请先抓取数据')
}
}
// 设置日期范围为最近7天
const setLast7Days = () => {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(startDate.getDate() - 6) // 最近7天包括今天
const formatDateForPicker = (date: Date) => {
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}`
}
dateRange.value = [formatDateForPicker(startDate), formatDateForPicker(endDate)]
console.log('setLast7Days called, todayBids:', props.todayBids.length, 'dateRange:', dateRange.value)
// 直接计算筛选结果并显示提示(只限制开始时间,不限制结束时间)
const start = new Date(startDate)
start.setHours(0, 0, 0, 0)
let result = props.todayBids
result = result.filter(bid => {
if (!bid.publishDate) return false
const bidDate = new Date(bid.publishDate)
return bidDate >= start
})
const totalBids = props.todayBids.length
const filteredCount = result.length
console.log('setLast7Days result, totalBids:', totalBids, 'filteredCount:', filteredCount)
if (totalBids === 0) {
ElMessage.warning('暂无数据,请先抓取数据')
}
}
const handleCrawl = async () => {
if (props.isCrawling) {
ElMessage.warning('Crawl is already running')
return
}
crawling.value = true
try {
await axios.post('/api/crawler/run')
ElMessage.success('Crawl completed successfully')
emit('refresh') // Refresh data after crawl
} catch (error) {
ElMessage.error('Failed to run crawl task')
} finally {
crawling.value = false
}
}
// 初始化时加载保存的关键字
loadSavedKeywords()
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>