diff --git a/widget/looker/app.go b/widget/looker/app.go index ba11b44..7354acc 100644 --- a/widget/looker/app.go +++ b/widget/looker/app.go @@ -43,6 +43,15 @@ type AiRecommendation struct { CreatedAt string `json:"createdAt"` } +// CrawlInfoStat 爬虫统计信息结构体 +type CrawlInfoStat struct { + Source string `json:"source"` + Count int `json:"count"` + LatestUpdate string `json:"latestUpdate"` + LatestPublishDate string `json:"latestPublishDate"` + Error string `json:"error"` +} + // App struct type App struct { ctx context.Context @@ -207,9 +216,9 @@ func (a *App) GetPinnedBidItems() ([]BidItem, error) { return nil, fmt.Errorf("扫描行失败: %v", err) } - item.PublishDate = publishDate.Format("2006-01-02 15:04:05") - item.CreatedAt = createdAt.Format("2006-01-02 15:04:05") - item.UpdatedAt = updatedAt.Format("2006-01-02 15:04:05") + item.PublishDate = publishDate.Format("2006-01-02") + item.CreatedAt = createdAt.Format("2006-01-02") + item.UpdatedAt = updatedAt.Format("2006-01-02") items = append(items, item) } @@ -265,7 +274,7 @@ func (a *App) GetAiRecommendations() ([]AiRecommendation, error) { return nil, fmt.Errorf("扫描行失败: %v", err) } - item.CreatedAt = createdAt.Format("2006-01-02 15:04:05") + item.CreatedAt = createdAt.Format("2006-01-02") items = append(items, item) } @@ -276,3 +285,87 @@ func (a *App) GetAiRecommendations() ([]AiRecommendation, error) { return items, nil } + +// GetCrawlInfoStats 获取爬虫统计信息 +func (a *App) GetCrawlInfoStats() ([]CrawlInfoStat, error) { + dsn := a.GetDatabaseDSN() + if dsn == "" { + return nil, fmt.Errorf("数据库配置未加载") + } + + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, fmt.Errorf("连接数据库失败: %v", err) + } + defer db.Close() + + // 测试连接 + if err := db.Ping(); err != nil { + return nil, fmt.Errorf("数据库连接测试失败: %v", err) + } + + // 查询 crawl_info_add 表,按 source 分组获取最新记录 + query := `SELECT + c1.source, + c1.count, + c1.createdAt as latestUpdate, + c1.latestPublishDate, + c1.error + FROM crawl_info_add c1 + WHERE c1.createdAt = ( + SELECT MAX(c2.createdAt) + FROM crawl_info_add c2 + WHERE c2.source = c1.source + ) + ORDER BY c1.source` + + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("查询失败: %v", err) + } + defer rows.Close() + + var stats []CrawlInfoStat + for rows.Next() { + var stat CrawlInfoStat + var latestUpdate, latestPublishDate sql.NullTime + var errorStr sql.NullString + + err := rows.Scan( + &stat.Source, + &stat.Count, + &latestUpdate, + &latestPublishDate, + &errorStr, + ) + if err != nil { + return nil, fmt.Errorf("扫描行失败: %v", err) + } + + if latestUpdate.Valid { + stat.LatestUpdate = latestUpdate.Time.Format("2006-01-02 15:04:05") + } else { + stat.LatestUpdate = "" + } + + if latestPublishDate.Valid { + stat.LatestPublishDate = latestPublishDate.Time.Format("2006-01-02 15:04:05") + } else { + stat.LatestPublishDate = "" + } + + if errorStr.Valid { + stat.Error = errorStr.String + } else { + stat.Error = "" + } + + stats = append(stats, stat) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("遍历行失败: %v", err) + } + + return stats, nil +} diff --git a/widget/looker/frontend/src/App.vue b/widget/looker/frontend/src/App.vue index 79b1b2b..d2f040d 100644 --- a/widget/looker/frontend/src/App.vue +++ b/widget/looker/frontend/src/App.vue @@ -2,25 +2,43 @@ import { ref, onMounted, onUnmounted } from 'vue' import PinnedBids from './components/PinnedBids.vue' import AiRecommendations from './components/AiRecommendations.vue' +import WidgetCrawlInfo from './components/WidgetCrawlInfo.vue' const activeTab = ref('pinned') const pinnedBidsRef = ref>() const aiRecommendationsRef = ref>() +const widgetCrawlInfoRef = ref>() let refreshTimer: number | null = null +const showToast = ref(false) +const toastMessage = ref('') const tabs = [ { id: 'pinned', label: '置顶项目' }, - { id: 'ai', label: 'AI 推荐' } + { id: 'ai', label: 'AI 推荐' }, + { id: 'status', label: '状态' } ] const handleRefresh = () => { if (activeTab.value === 'pinned' && pinnedBidsRef.value) { pinnedBidsRef.value.loadPinnedBids() + showToastMessage('置顶项目已刷新') } else if (activeTab.value === 'ai' && aiRecommendationsRef.value) { aiRecommendationsRef.value.loadRecommendations() + showToastMessage('AI 推荐已刷新') + } else if (activeTab.value === 'status' && widgetCrawlInfoRef.value) { + widgetCrawlInfoRef.value.loadCrawlStats() + showToastMessage('状态已刷新') } } +const showToastMessage = (message: string) => { + toastMessage.value = message + showToast.value = true + setTimeout(() => { + showToast.value = false + }, 2000) +} + const startAutoRefresh = () => { // 每5分钟(300000毫秒)自动刷新 refreshTimer = window.setInterval(() => { @@ -63,6 +81,11 @@ onUnmounted(() => {
+ +
+ +
+ {{ toastMessage }}
@@ -146,4 +169,27 @@ body { color: #999; font-size: 12px; } + +.toast { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.8); + color: #fff; + padding: 12px 24px; + border-radius: 4px; + font-size: 12px; + z-index: 1000; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/widget/looker/frontend/src/components/HelloWorld.vue b/widget/looker/frontend/src/components/HelloWorld.vue deleted file mode 100644 index 3ab3df7..0000000 --- a/widget/looker/frontend/src/components/HelloWorld.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/widget/looker/frontend/src/components/WidgetCrawlInfo.vue b/widget/looker/frontend/src/components/WidgetCrawlInfo.vue new file mode 100644 index 0000000..9b22ff7 --- /dev/null +++ b/widget/looker/frontend/src/components/WidgetCrawlInfo.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/widget/looker/frontend/wailsjs/go/main/App.d.ts b/widget/looker/frontend/wailsjs/go/main/App.d.ts index 162cb3d..6fd5437 100644 --- a/widget/looker/frontend/wailsjs/go/main/App.d.ts +++ b/widget/looker/frontend/wailsjs/go/main/App.d.ts @@ -4,6 +4,8 @@ import {main} from '../models'; export function GetAiRecommendations():Promise>; +export function GetCrawlInfoStats():Promise>; + export function GetDatabaseConfig():Promise; export function GetDatabaseDSN():Promise; diff --git a/widget/looker/frontend/wailsjs/go/main/App.js b/widget/looker/frontend/wailsjs/go/main/App.js index a027583..a7b8b97 100644 --- a/widget/looker/frontend/wailsjs/go/main/App.js +++ b/widget/looker/frontend/wailsjs/go/main/App.js @@ -6,6 +6,10 @@ export function GetAiRecommendations() { return window['go']['main']['App']['GetAiRecommendations'](); } +export function GetCrawlInfoStats() { + return window['go']['main']['App']['GetCrawlInfoStats'](); +} + export function GetDatabaseConfig() { return window['go']['main']['App']['GetDatabaseConfig'](); } diff --git a/widget/looker/frontend/wailsjs/go/models.ts b/widget/looker/frontend/wailsjs/go/models.ts index 6d86208..6dd3189 100644 --- a/widget/looker/frontend/wailsjs/go/models.ts +++ b/widget/looker/frontend/wailsjs/go/models.ts @@ -44,6 +44,26 @@ export namespace main { this.updatedAt = source["updatedAt"]; } } + export class CrawlInfoStat { + source: string; + count: number; + latestUpdate: string; + latestPublishDate: string; + error: string; + + static createFrom(source: any = {}) { + return new CrawlInfoStat(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.source = source["source"]; + this.count = source["count"]; + this.latestUpdate = source["latestUpdate"]; + this.latestPublishDate = source["latestPublishDate"]; + this.error = source["error"]; + } + } export class DatabaseConfig { Type: string; Host: string;