feat(electron): 添加Electron桌面应用支持
- 新增Electron主进程、预加载脚本和构建配置 - 修改前端配置以支持Electron打包 - 更新项目文档和依赖 - 重构API调用使用统一axios实例
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from './utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { DataBoard, Document, Setting, MagicStick, Connection } from '@element-plus/icons-vue'
|
||||
import Dashboard from './components/Dashboard.vue'
|
||||
@@ -89,7 +89,7 @@ const handleSelect = (key: string) => {
|
||||
const handleFetchBids = async (page: number, limit: number, source?: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await axios.get('/api/bids', {
|
||||
const res = await api.get('/api/bids', {
|
||||
params: {
|
||||
page,
|
||||
limit,
|
||||
@@ -109,16 +109,16 @@ const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const [bidsRes, recentRes, kwRes, sourcesRes, statusRes] = await Promise.all([
|
||||
axios.get('/api/bids', {
|
||||
api.get('/api/bids', {
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
}),
|
||||
axios.get('/api/bids/recent'),
|
||||
axios.get('/api/keywords'),
|
||||
axios.get('/api/bids/sources'),
|
||||
axios.get('/api/crawler/status')
|
||||
api.get('/api/bids/recent'),
|
||||
api.get('/api/keywords'),
|
||||
api.get('/api/bids/sources'),
|
||||
api.get('/api/crawler/status')
|
||||
])
|
||||
bids.value = bidsRes.data.items
|
||||
total.value = bidsRes.data.total
|
||||
@@ -145,7 +145,7 @@ const updateBidsByDateRange = async (startDate: string, endDate?: string, keywor
|
||||
params.keywords = keywords.join(',')
|
||||
}
|
||||
|
||||
const response = await axios.get('/api/bids/by-date-range', { params })
|
||||
const response = await api.get('/api/bids/by-date-range', { params })
|
||||
todayBids.value = response.data
|
||||
ElMessage.success(`更新成功,共 ${response.data.length} 条数据`)
|
||||
} catch (error) {
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from '../utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Paperclip } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -97,7 +97,7 @@ const handleSizeChange = (size: number) => {
|
||||
const togglePin = async (item: any) => {
|
||||
try {
|
||||
const newPinStatus = !item.pin
|
||||
await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
|
||||
await api.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
|
||||
item.pin = newPinStatus
|
||||
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
||||
} catch (error) {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from '../utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Refresh } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -118,7 +118,7 @@ const formatDate = (dateStr: string | null) => {
|
||||
const fetchCrawlStats = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await axios.get('/api/bids/crawl-info-stats')
|
||||
const res = await api.get('/api/bids/crawl-info-stats')
|
||||
crawlStats.value = res.data
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch crawl stats:', error)
|
||||
@@ -132,7 +132,7 @@ const crawlSingleSource = async (sourceName: string) => {
|
||||
crawlingSources.value.add(sourceName)
|
||||
try {
|
||||
ElMessage.info(`正在更新 ${sourceName}...`)
|
||||
const res = await axios.post(`/api/crawler/crawl/${encodeURIComponent(sourceName)}`)
|
||||
const res = await api.post(`/api/crawler/crawl/${encodeURIComponent(sourceName)}`)
|
||||
|
||||
if (res.data.success) {
|
||||
ElMessage.success(`${sourceName} 更新成功,获取 ${res.data.count} 条数据`)
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from '../utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { MagicStick, Loading, InfoFilled, List, Paperclip } from '@element-plus/icons-vue'
|
||||
import PinnedProject from './PinnedProject.vue'
|
||||
@@ -175,11 +175,11 @@ watch(dateRange, (newDateRange) => {
|
||||
// 从数据库加载最新的 AI 推荐
|
||||
const loadLatestRecommendations = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/ai/latest-recommendations')
|
||||
const response = await api.get('/api/ai/latest-recommendations')
|
||||
const recommendations = response.data
|
||||
|
||||
// 获取所有置顶的项目
|
||||
const pinnedResponse = await axios.get('/api/bids/pinned')
|
||||
const pinnedResponse = await api.get('/api/bids/pinned')
|
||||
const pinnedTitles = new Set(pinnedResponse.data.map((b: any) => b.title))
|
||||
|
||||
// 更新每个推荐项目的 pin 状态
|
||||
@@ -240,7 +240,7 @@ const fetchAIRecommendations = async () => {
|
||||
}))
|
||||
|
||||
// 调用后端 API
|
||||
const response = await axios.post('/api/ai/recommendations', {
|
||||
const response = await api.post('/api/ai/recommendations', {
|
||||
bids: bidsData
|
||||
})
|
||||
|
||||
@@ -267,7 +267,7 @@ const fetchAIRecommendations = async () => {
|
||||
aiRecommendations.value = recommendations
|
||||
|
||||
// 保存推荐结果到数据库
|
||||
await axios.post('/api/ai/save-recommendations', {
|
||||
await api.post('/api/ai/save-recommendations', {
|
||||
recommendations
|
||||
})
|
||||
|
||||
@@ -301,7 +301,7 @@ const fetchBidsByDateRange = async () => {
|
||||
params.endDate = endDate
|
||||
}
|
||||
|
||||
const response = await axios.get('/api/bids/by-date-range', { params })
|
||||
const response = await api.get('/api/bids/by-date-range', { params })
|
||||
bidsByDateRange.value = response.data
|
||||
ElMessage.success(`获取成功,共 ${response.data.length} 个工程`)
|
||||
} catch (error: any) {
|
||||
@@ -344,7 +344,7 @@ const handlePinChanged = async (title: string) => {
|
||||
const togglePin = async (item: AIRecommendation) => {
|
||||
try {
|
||||
const newPinStatus = !item.pin
|
||||
await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
|
||||
await api.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
|
||||
item.pin = newPinStatus
|
||||
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
||||
// 刷新 PinnedProject 组件的数据
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from '../utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Refresh, Paperclip } from '@element-plus/icons-vue'
|
||||
import PinnedProject from './PinnedProject.vue'
|
||||
@@ -275,7 +275,7 @@ const handleCrawl = async () => {
|
||||
}
|
||||
crawling.value = true
|
||||
try {
|
||||
await axios.post('/api/crawler/run')
|
||||
await api.post('/api/crawler/run')
|
||||
ElMessage.success('Crawl completed successfully')
|
||||
emit('refresh') // Refresh data after crawl
|
||||
} catch (error) {
|
||||
@@ -301,7 +301,7 @@ const handlePinChanged = async (title: string) => {
|
||||
const togglePin = async (item: any) => {
|
||||
try {
|
||||
const newPinStatus = !item.pin
|
||||
await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
|
||||
await api.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: newPinStatus })
|
||||
item.pin = newPinStatus
|
||||
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
||||
// 刷新 PinnedProject 组件的数据
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from '../utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
interface Props {
|
||||
@@ -76,7 +76,7 @@ const handleAddKeyword = async () => {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await axios.post('/api/keywords', form)
|
||||
await api.post('/api/keywords', form)
|
||||
ElMessage.success('Keyword added')
|
||||
dialogVisible.value = false
|
||||
form.word = ''
|
||||
@@ -89,7 +89,7 @@ const handleAddKeyword = async () => {
|
||||
|
||||
const handleDeleteKeyword = async (id: string) => {
|
||||
try {
|
||||
await axios.delete(`/api/keywords/${id}`)
|
||||
await api.delete(`/api/keywords/${id}`)
|
||||
ElMessage.success('Keyword deleted')
|
||||
emit('refresh')
|
||||
} catch (error) {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import api from '../utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Loading, InfoFilled, Paperclip } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -63,7 +63,7 @@ const pinnedLoading = ref(false)
|
||||
const loadPinnedBids = async () => {
|
||||
pinnedLoading.value = true
|
||||
try {
|
||||
const response = await axios.get('/api/bids/pinned')
|
||||
const response = await api.get('/api/bids/pinned')
|
||||
pinnedBids.value = response.data
|
||||
} catch (error) {
|
||||
console.error('Failed to load pinned bids:', error)
|
||||
@@ -75,7 +75,7 @@ const loadPinnedBids = async () => {
|
||||
// 切换置顶列表的 Pin 状态
|
||||
const togglePin = async (item: any) => {
|
||||
try {
|
||||
await axios.patch(`/api/bids/${encodeURIComponent(item.title)}/pin`, { pin: false })
|
||||
await api.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)
|
||||
|
||||
34
frontend/src/utils/api.ts
Normal file
34
frontend/src/utils/api.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import axios from 'axios'
|
||||
|
||||
/**
|
||||
* API配置
|
||||
* 配置axios实例,设置baseURL和请求拦截器
|
||||
*/
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:3000', // 设置后端服务地址
|
||||
timeout: 10000, // 请求超时时间
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
// 可以在这里添加认证信息等
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
console.error('API请求错误:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default api
|
||||
@@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
proxy: {
|
||||
|
||||
Reference in New Issue
Block a user