feat(electron): 添加Electron桌面应用支持
- 新增Electron主进程、预加载脚本和构建配置 - 修改前端配置以支持Electron打包 - 更新项目文档和依赖 - 重构API调用使用统一axios实例
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ build
|
|||||||
*-lock.json
|
*-lock.json
|
||||||
*.woff2
|
*.woff2
|
||||||
widget/looker/frontend/src/assets/fonts/OFL.txt
|
widget/looker/frontend/src/assets/fonts/OFL.txt
|
||||||
|
dist-electron
|
||||||
133
README.md
133
README.md
@@ -15,23 +15,33 @@
|
|||||||
### 前端
|
### 前端
|
||||||
- **框架**: Vue.js 3
|
- **框架**: Vue.js 3
|
||||||
- **构建工具**: Vite
|
- **构建工具**: Vite
|
||||||
- **UI**: Tailwind CSS
|
- **UI**: Element Plus
|
||||||
- **状态管理**: Pinia
|
- **图标**: @element-plus/icons-vue
|
||||||
|
- **HTTP 客户端**: Axios
|
||||||
|
- **Tailwind CSS**: 用于样式辅助
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── ai/ # AI 模块
|
├── ai/ # AI 模块
|
||||||
│ ├── ai.controller.ts
|
│ ├── entities/
|
||||||
│ ├── ai.service.ts
|
│ │ └── ai-recommendation.entity.ts
|
||||||
│ ├── Prompt.ts
|
│ ├── Prompt.ts
|
||||||
│ └── entities/
|
│ ├── ai.controller.ts
|
||||||
|
│ ├── ai.module.ts
|
||||||
|
│ └── ai.service.ts
|
||||||
├── bids/ # 投标业务模块
|
├── bids/ # 投标业务模块
|
||||||
│ ├── controllers/
|
│ ├── controllers/
|
||||||
|
│ │ └── bid.controller.ts
|
||||||
│ ├── entities/
|
│ ├── entities/
|
||||||
│ └── services/
|
│ │ └── bid-item.entity.ts
|
||||||
|
│ ├── services/
|
||||||
|
│ │ └── bid.service.ts
|
||||||
|
│ └── bids.module.ts
|
||||||
├── crawler/ # 爬虫模块
|
├── crawler/ # 爬虫模块
|
||||||
|
│ ├── entities/
|
||||||
|
│ │ └── crawl-info-add.entity.ts
|
||||||
│ ├── services/
|
│ ├── services/
|
||||||
│ │ ├── bid-crawler.service.ts
|
│ │ ├── bid-crawler.service.ts
|
||||||
│ │ ├── cdt_target.ts
|
│ │ ├── cdt_target.ts
|
||||||
@@ -46,32 +56,63 @@ src/
|
|||||||
│ │ ├── powerbeijing_target.ts
|
│ │ ├── powerbeijing_target.ts
|
||||||
│ │ ├── sdicc_target.ts
|
│ │ ├── sdicc_target.ts
|
||||||
│ │ └── szecp_target.ts
|
│ │ └── szecp_target.ts
|
||||||
│ └── entities/
|
│ ├── crawler.controller.ts
|
||||||
|
│ └── crawler.module.ts
|
||||||
├── database/ # 数据库模块
|
├── database/ # 数据库模块
|
||||||
|
│ └── database.module.ts
|
||||||
├── keywords/ # 关键词管理模块
|
├── keywords/ # 关键词管理模块
|
||||||
|
│ ├── keyword.entity.ts
|
||||||
|
│ ├── keywords.controller.ts
|
||||||
|
│ ├── keywords.module.ts
|
||||||
|
│ └── keywords.service.ts
|
||||||
├── schedule/ # 定时任务
|
├── schedule/ # 定时任务
|
||||||
│ └── tasks/
|
│ ├── tasks/
|
||||||
│ └── bid-crawl.task.ts
|
│ │ └── bid-crawl.task.ts
|
||||||
|
│ └── schedule.module.ts
|
||||||
├── scripts/ # 脚本工具
|
├── scripts/ # 脚本工具
|
||||||
│ ├── ai-recommendations.ts
|
│ ├── ai-recommendations.ts
|
||||||
│ ├── crawl.ts
|
│ ├── crawl.ts
|
||||||
│ ├── deploy.ps1
|
│ ├── deploy.ps1
|
||||||
│ ├── remove-duplicates.ts
|
│ ├── remove-duplicates.ts
|
||||||
|
│ ├── sync.ts
|
||||||
│ └── update-source.ts
|
│ └── update-source.ts
|
||||||
└── common/ # 公共模块
|
├── common/ # 公共模块
|
||||||
└── logger/
|
│ └── logger/
|
||||||
|
│ ├── logger.module.ts
|
||||||
|
│ ├── logger.service.ts
|
||||||
|
│ └── winston.config.ts
|
||||||
|
├── app.controller.ts
|
||||||
|
├── app.module.ts
|
||||||
|
├── app.service.ts
|
||||||
|
└── main.ts
|
||||||
|
|
||||||
frontend/
|
frontend/
|
||||||
└── src/
|
├── src/
|
||||||
├── components/
|
│ ├── assets/
|
||||||
│ ├── Dashboard.vue
|
│ │ └── vue.svg
|
||||||
│ ├── Dashboard-AI.vue
|
│ ├── components/
|
||||||
│ ├── PinnedProject.vue
|
│ │ ├── Dashboard.vue
|
||||||
│ ├── Bids.vue
|
│ │ ├── Dashboard-AI.vue
|
||||||
│ ├── Keywords.vue
|
│ │ ├── PinnedProject.vue
|
||||||
│ └── CrawlInfo.vue
|
│ │ ├── Bids.vue
|
||||||
├── App.vue
|
│ │ ├── Keywords.vue
|
||||||
└── main.ts
|
│ │ └── CrawlInfo.vue
|
||||||
|
│ ├── utils/
|
||||||
|
│ │ └── api.ts
|
||||||
|
│ ├── App.vue
|
||||||
|
│ ├── main.ts
|
||||||
|
│ └── style.css
|
||||||
|
├── .env
|
||||||
|
├── .env.example
|
||||||
|
├── .gitignore
|
||||||
|
├── README.md
|
||||||
|
├── index.html
|
||||||
|
├── package.json
|
||||||
|
├── postcss.config.js
|
||||||
|
├── tsconfig.app.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── tsconfig.node.json
|
||||||
|
└── vite.config.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
@@ -141,26 +182,28 @@ npm run start:prod
|
|||||||
### 智能爬虫模块
|
### 智能爬虫模块
|
||||||
|
|
||||||
- **多源爬取**: 支持 12 个主流招标网站
|
- **多源爬取**: 支持 12 个主流招标网站
|
||||||
- 中国大唐集团电子商务平台 (CDT)
|
|
||||||
- 中国华能集团有限公司电子商务平台 (CHNG)
|
|
||||||
- 中国南方电网电子商务平台 (CSG)
|
|
||||||
- 中国海洋石油集团有限公司 (CNOOC)
|
|
||||||
- 中国华电集团有限公司电子商务平台 (CHDTP)
|
- 中国华电集团有限公司电子商务平台 (CHDTP)
|
||||||
|
- 中国华能集团有限公司电子商务平台 (CHNG)
|
||||||
|
- 深圳交易集团有限公司 (SZECP)
|
||||||
|
- 中国大唐集团电子商务平台 (CDT)
|
||||||
|
- 中国电力招标网 (EPS)
|
||||||
- 国家能源投资集团有限责任公司 (CNNCECP)
|
- 国家能源投资集团有限责任公司 (CNNCECP)
|
||||||
- 中国核工业集团有限公司 (CNNC)
|
- 中国石油天然气集团有限公司 (CGNPC)
|
||||||
- 中国电力建设集团有限公司 (POWERCHINA)
|
|
||||||
- 中国能源建设集团有限公司 (CEIC)
|
- 中国能源建设集团有限公司 (CEIC)
|
||||||
- 中国石油天然气集团有限公司 (CNPC)
|
- 中国电力建设集团有限公司 (ESPIC)
|
||||||
- 国家电网有限公司 (SGCC)
|
|
||||||
- 北京电力交易中心 (POWERBEIJING)
|
- 北京电力交易中心 (POWERBEIJING)
|
||||||
|
- 山东能源集团有限公司 (SDICC)
|
||||||
|
- 中国海洋石油集团有限公司 (CNOOC)
|
||||||
|
|
||||||
- **智能防封策略**:
|
- **智能防封策略**:
|
||||||
- 随机请求间隔 (3-8 秒)
|
- 随机请求间隔 (1-3 秒)
|
||||||
- 轮换 User-Agent
|
- 固定 User-Agent
|
||||||
- 异常检测与自动重试机制
|
- 异常检测与自动重试机制
|
||||||
- 代理支持
|
- 代理支持
|
||||||
|
|
||||||
- **定时任务**: 每 30 分钟自动执行爬取
|
- **定时任务**:
|
||||||
|
- 爬虫任务:已暂停(默认每天午夜执行)
|
||||||
|
- 数据清理:每天午夜自动执行
|
||||||
|
|
||||||
### 数据处理与存储
|
### 数据处理与存储
|
||||||
|
|
||||||
@@ -168,10 +211,10 @@ npm run start:prod
|
|||||||
- 投标项目标题
|
- 投标项目标题
|
||||||
- 详细页面 URL
|
- 详细页面 URL
|
||||||
- 发布时间
|
- 发布时间
|
||||||
- 招标单位
|
- 来源网站
|
||||||
- 截止日期
|
- 置顶标记
|
||||||
- 关键词匹配
|
- 创建时间
|
||||||
- 优先级评分
|
- 更新时间
|
||||||
|
|
||||||
- **增量存储**:
|
- **增量存储**:
|
||||||
- 通过 URL 哈希值判断是否为新数据
|
- 通过 URL 哈希值判断是否为新数据
|
||||||
@@ -215,8 +258,12 @@ npm run start:prod
|
|||||||
|
|
||||||
### 投标信息
|
### 投标信息
|
||||||
- `GET /api/bids` - 获取投标列表(支持分页、筛选)
|
- `GET /api/bids` - 获取投标列表(支持分页、筛选)
|
||||||
- `GET /api/bids/high-priority` - 获取高优先级投标
|
- `GET /api/bids/recent` - 获取最近投标
|
||||||
- `GET /api/bids/today` - 获取今日投标
|
- `GET /api/bids/pinned` - 获取置顶投标
|
||||||
|
- `GET /api/bids/sources` - 获取来源列表
|
||||||
|
- `GET /api/bids/by-date-range` - 按日期范围获取投标
|
||||||
|
- `GET /api/bids/crawl-info-stats` - 获取爬取信息统计
|
||||||
|
- `PATCH /api/bids/:title/pin` - 更新置顶状态
|
||||||
|
|
||||||
### 关键词管理
|
### 关键词管理
|
||||||
- `GET /api/keywords` - 获取所有关键词
|
- `GET /api/keywords` - 获取所有关键词
|
||||||
@@ -224,12 +271,14 @@ npm run start:prod
|
|||||||
- `DELETE /api/keywords/:id` - 删除关键词
|
- `DELETE /api/keywords/:id` - 删除关键词
|
||||||
|
|
||||||
### AI 服务
|
### AI 服务
|
||||||
- `GET /api/ai/recommendations` - 获取 AI 推荐投标
|
- `POST /api/ai/recommendations` - 获取 AI 推荐
|
||||||
- `POST /api/ai/analyze` - 分析投标信息
|
- `POST /api/ai/save-recommendations` - 保存 AI 推荐
|
||||||
|
- `GET /api/ai/latest-recommendations` - 获取最新 AI 推荐
|
||||||
|
|
||||||
### 爬虫管理
|
### 爬虫管理
|
||||||
- `GET /api/crawler/info` - 获取爬取信息
|
- `GET /api/crawler/status` - 获取爬虫状态
|
||||||
- `POST /api/crawler/trigger` - 手动触发爬取
|
- `POST /api/crawler/run` - 运行爬虫
|
||||||
|
- `POST /api/crawler/crawl/:sourceName` - 爬取单个来源
|
||||||
|
|
||||||
## 前端路由
|
## 前端路由
|
||||||
|
|
||||||
|
|||||||
39
app/electron-builder.json
Normal file
39
app/electron-builder.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"productName": "投标应用",
|
||||||
|
"appId": "com.bidding.app",
|
||||||
|
"directories": {
|
||||||
|
"output": "dist-electron",
|
||||||
|
"app": "./app"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*",
|
||||||
|
"frontend/**/*",
|
||||||
|
".env",
|
||||||
|
"node_modules/**/*",
|
||||||
|
"package.json",
|
||||||
|
"app/**/*"
|
||||||
|
],
|
||||||
|
"win": {
|
||||||
|
"target": "nsis",
|
||||||
|
"icon": "frontend/public/favicon.ico",
|
||||||
|
"requestedExecutionLevel": "asInvoker"
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"createDesktopShortcut": true,
|
||||||
|
"createStartMenuShortcut": true,
|
||||||
|
"shortcutName": "投标应用"
|
||||||
|
},
|
||||||
|
"extraResources": [
|
||||||
|
{
|
||||||
|
"from": ".env",
|
||||||
|
"to": ".env",
|
||||||
|
"filter": ["**/*"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"publish": {
|
||||||
|
"provider": "generic",
|
||||||
|
"url": "http://localhost:3000/"
|
||||||
|
}
|
||||||
|
}
|
||||||
172
app/main.js
Normal file
172
app/main.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// 加载环境变量
|
||||||
|
const envPath = path.join(__dirname, '..', '.env');
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
dotenv.config({ path: envPath });
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainWindow;
|
||||||
|
let backendProcess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建Electron主窗口
|
||||||
|
*/
|
||||||
|
function createWindow() {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
nodeIntegration: false,
|
||||||
|
contextIsolation: true,
|
||||||
|
},
|
||||||
|
autoHideMenuBar: false,
|
||||||
|
title: '投标应用',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载前端页面
|
||||||
|
const indexPath = path.join(__dirname, '..', 'frontend', 'dist', 'index.html');
|
||||||
|
mainWindow.loadFile(indexPath);
|
||||||
|
|
||||||
|
// 开发环境下打开开发者工具
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.on('closed', () => {
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等待后端服务启动
|
||||||
|
*/
|
||||||
|
function waitForBackend(port = 3000, maxRetries = 30, interval = 1000) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
const checkBackend = () => {
|
||||||
|
const net = require('net');
|
||||||
|
const client = new net.Socket();
|
||||||
|
|
||||||
|
client.once('connect', () => {
|
||||||
|
client.destroy();
|
||||||
|
console.log('后端服务已启动');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.once('error', () => {
|
||||||
|
client.destroy();
|
||||||
|
retry();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.once('timeout', () => {
|
||||||
|
client.destroy();
|
||||||
|
retry();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect(port, 'localhost');
|
||||||
|
client.setTimeout(1000);
|
||||||
|
|
||||||
|
function retry() {
|
||||||
|
retries++;
|
||||||
|
if (retries >= maxRetries) {
|
||||||
|
reject(new Error('后端服务启动超时'));
|
||||||
|
} else {
|
||||||
|
console.log(`等待后端服务启动... (${retries}/${maxRetries})`);
|
||||||
|
setTimeout(checkBackend, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkBackend();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动后端服务
|
||||||
|
*/
|
||||||
|
async function startBackend() {
|
||||||
|
const backendPath = path.join(__dirname, '..', 'dist', 'main.js');
|
||||||
|
|
||||||
|
// 检查后端构建文件是否存在
|
||||||
|
if (!fs.existsSync(backendPath)) {
|
||||||
|
console.error('后端服务构建文件不存在,请先执行 npm run build');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动后端服务
|
||||||
|
backendProcess = spawn('node', [backendPath], {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NODE_ENV: process.env.NODE_ENV || 'production',
|
||||||
|
},
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
backendProcess.on('error', (error) => {
|
||||||
|
console.error('后端服务启动失败:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
backendProcess.on('exit', (code) => {
|
||||||
|
console.log(`后端服务退出,退出码: ${code}`);
|
||||||
|
backendProcess = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待后端服务启动完成
|
||||||
|
try {
|
||||||
|
await waitForBackend();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('等待后端服务启动失败:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止后端服务
|
||||||
|
*/
|
||||||
|
function stopBackend() {
|
||||||
|
if (backendProcess) {
|
||||||
|
console.log('正在停止后端服务...');
|
||||||
|
backendProcess.kill();
|
||||||
|
backendProcess = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用就绪时启动后端服务,然后创建窗口
|
||||||
|
app.on('ready', async () => {
|
||||||
|
await startBackend();
|
||||||
|
createWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 所有窗口关闭时退出应用
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
stopBackend();
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// MacOS上点击dock图标时重新创建窗口
|
||||||
|
app.on('activate', async () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
if (!backendProcess) {
|
||||||
|
await startBackend();
|
||||||
|
}
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 应用退出前停止后端服务
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
stopBackend();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理来自渲染进程的IPC消息
|
||||||
|
ipcMain.handle('get-env', (event, key) => {
|
||||||
|
return process.env[key];
|
||||||
|
});
|
||||||
13
app/package.json
Normal file
13
app/package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "bidding-app",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "投标应用Electron版本",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron .",
|
||||||
|
"build": "electron-builder"
|
||||||
|
},
|
||||||
|
"keywords": ["electron", "bidding", "app"],
|
||||||
|
"author": "",
|
||||||
|
"license": "UNLICENSED"
|
||||||
|
}
|
||||||
14
app/preload.js
Normal file
14
app/preload.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载脚本,用于在渲染进程和主进程之间通信
|
||||||
|
* 提供安全的API给渲染进程访问主进程功能
|
||||||
|
*/
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
/**
|
||||||
|
* 获取环境变量值
|
||||||
|
* @param {string} key - 环境变量名称
|
||||||
|
* @returns {Promise<string>} - 环境变量值
|
||||||
|
*/
|
||||||
|
getEnv: (key) => ipcRenderer.invoke('get-env', key),
|
||||||
|
});
|
||||||
@@ -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">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import api from './utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { DataBoard, Document, Setting, MagicStick, Connection } from '@element-plus/icons-vue'
|
import { DataBoard, Document, Setting, MagicStick, Connection } from '@element-plus/icons-vue'
|
||||||
import Dashboard from './components/Dashboard.vue'
|
import Dashboard from './components/Dashboard.vue'
|
||||||
@@ -89,7 +89,7 @@ const handleSelect = (key: string) => {
|
|||||||
const handleFetchBids = async (page: number, limit: number, source?: string) => {
|
const handleFetchBids = async (page: number, limit: number, source?: string) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('/api/bids', {
|
const res = await api.get('/api/bids', {
|
||||||
params: {
|
params: {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
@@ -109,16 +109,16 @@ const fetchData = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const [bidsRes, recentRes, kwRes, sourcesRes, statusRes] = await Promise.all([
|
const [bidsRes, recentRes, kwRes, sourcesRes, statusRes] = await Promise.all([
|
||||||
axios.get('/api/bids', {
|
api.get('/api/bids', {
|
||||||
params: {
|
params: {
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 10
|
limit: 10
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
axios.get('/api/bids/recent'),
|
api.get('/api/bids/recent'),
|
||||||
axios.get('/api/keywords'),
|
api.get('/api/keywords'),
|
||||||
axios.get('/api/bids/sources'),
|
api.get('/api/bids/sources'),
|
||||||
axios.get('/api/crawler/status')
|
api.get('/api/crawler/status')
|
||||||
])
|
])
|
||||||
bids.value = bidsRes.data.items
|
bids.value = bidsRes.data.items
|
||||||
total.value = bidsRes.data.total
|
total.value = bidsRes.data.total
|
||||||
@@ -145,7 +145,7 @@ const updateBidsByDateRange = async (startDate: string, endDate?: string, keywor
|
|||||||
params.keywords = keywords.join(',')
|
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
|
todayBids.value = response.data
|
||||||
ElMessage.success(`更新成功,共 ${response.data.length} 条数据`)
|
ElMessage.success(`更新成功,共 ${response.data.length} 条数据`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import axios from 'axios'
|
import api from '../utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Paperclip } from '@element-plus/icons-vue'
|
import { Paperclip } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ const handleSizeChange = (size: number) => {
|
|||||||
const togglePin = async (item: any) => {
|
const togglePin = async (item: any) => {
|
||||||
try {
|
try {
|
||||||
const newPinStatus = !item.pin
|
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
|
item.pin = newPinStatus
|
||||||
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import api from '../utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
import { Refresh } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ const formatDate = (dateStr: string | null) => {
|
|||||||
const fetchCrawlStats = async () => {
|
const fetchCrawlStats = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
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
|
crawlStats.value = res.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch crawl stats:', error)
|
console.error('Failed to fetch crawl stats:', error)
|
||||||
@@ -132,7 +132,7 @@ const crawlSingleSource = async (sourceName: string) => {
|
|||||||
crawlingSources.value.add(sourceName)
|
crawlingSources.value.add(sourceName)
|
||||||
try {
|
try {
|
||||||
ElMessage.info(`正在更新 ${sourceName}...`)
|
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) {
|
if (res.data.success) {
|
||||||
ElMessage.success(`${sourceName} 更新成功,获取 ${res.data.count} 条数据`)
|
ElMessage.success(`${sourceName} 更新成功,获取 ${res.data.count} 条数据`)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import axios from 'axios'
|
import api from '../utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { MagicStick, Loading, InfoFilled, List, Paperclip } from '@element-plus/icons-vue'
|
import { MagicStick, Loading, InfoFilled, List, Paperclip } from '@element-plus/icons-vue'
|
||||||
import PinnedProject from './PinnedProject.vue'
|
import PinnedProject from './PinnedProject.vue'
|
||||||
@@ -175,11 +175,11 @@ watch(dateRange, (newDateRange) => {
|
|||||||
// 从数据库加载最新的 AI 推荐
|
// 从数据库加载最新的 AI 推荐
|
||||||
const loadLatestRecommendations = async () => {
|
const loadLatestRecommendations = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/api/ai/latest-recommendations')
|
const response = await api.get('/api/ai/latest-recommendations')
|
||||||
const recommendations = response.data
|
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))
|
const pinnedTitles = new Set(pinnedResponse.data.map((b: any) => b.title))
|
||||||
|
|
||||||
// 更新每个推荐项目的 pin 状态
|
// 更新每个推荐项目的 pin 状态
|
||||||
@@ -240,7 +240,7 @@ const fetchAIRecommendations = async () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// 调用后端 API
|
// 调用后端 API
|
||||||
const response = await axios.post('/api/ai/recommendations', {
|
const response = await api.post('/api/ai/recommendations', {
|
||||||
bids: bidsData
|
bids: bidsData
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ const fetchAIRecommendations = async () => {
|
|||||||
aiRecommendations.value = recommendations
|
aiRecommendations.value = recommendations
|
||||||
|
|
||||||
// 保存推荐结果到数据库
|
// 保存推荐结果到数据库
|
||||||
await axios.post('/api/ai/save-recommendations', {
|
await api.post('/api/ai/save-recommendations', {
|
||||||
recommendations
|
recommendations
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ const fetchBidsByDateRange = async () => {
|
|||||||
params.endDate = endDate
|
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
|
bidsByDateRange.value = response.data
|
||||||
ElMessage.success(`获取成功,共 ${response.data.length} 个工程`)
|
ElMessage.success(`获取成功,共 ${response.data.length} 个工程`)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -344,7 +344,7 @@ const handlePinChanged = async (title: string) => {
|
|||||||
const togglePin = async (item: AIRecommendation) => {
|
const togglePin = async (item: AIRecommendation) => {
|
||||||
try {
|
try {
|
||||||
const newPinStatus = !item.pin
|
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
|
item.pin = newPinStatus
|
||||||
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
||||||
// 刷新 PinnedProject 组件的数据
|
// 刷新 PinnedProject 组件的数据
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import axios from 'axios'
|
import api from '../utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Refresh, Paperclip } from '@element-plus/icons-vue'
|
import { Refresh, Paperclip } from '@element-plus/icons-vue'
|
||||||
import PinnedProject from './PinnedProject.vue'
|
import PinnedProject from './PinnedProject.vue'
|
||||||
@@ -275,7 +275,7 @@ const handleCrawl = async () => {
|
|||||||
}
|
}
|
||||||
crawling.value = true
|
crawling.value = true
|
||||||
try {
|
try {
|
||||||
await axios.post('/api/crawler/run')
|
await api.post('/api/crawler/run')
|
||||||
ElMessage.success('Crawl completed successfully')
|
ElMessage.success('Crawl completed successfully')
|
||||||
emit('refresh') // Refresh data after crawl
|
emit('refresh') // Refresh data after crawl
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -301,7 +301,7 @@ const handlePinChanged = async (title: string) => {
|
|||||||
const togglePin = async (item: any) => {
|
const togglePin = async (item: any) => {
|
||||||
try {
|
try {
|
||||||
const newPinStatus = !item.pin
|
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
|
item.pin = newPinStatus
|
||||||
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
ElMessage.success(newPinStatus ? '已置顶' : '已取消置顶')
|
||||||
// 刷新 PinnedProject 组件的数据
|
// 刷新 PinnedProject 组件的数据
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
import axios from 'axios'
|
import api from '../utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -76,7 +76,7 @@ const handleAddKeyword = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await axios.post('/api/keywords', form)
|
await api.post('/api/keywords', form)
|
||||||
ElMessage.success('Keyword added')
|
ElMessage.success('Keyword added')
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
form.word = ''
|
form.word = ''
|
||||||
@@ -89,7 +89,7 @@ const handleAddKeyword = async () => {
|
|||||||
|
|
||||||
const handleDeleteKeyword = async (id: string) => {
|
const handleDeleteKeyword = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
await axios.delete(`/api/keywords/${id}`)
|
await api.delete(`/api/keywords/${id}`)
|
||||||
ElMessage.success('Keyword deleted')
|
ElMessage.success('Keyword deleted')
|
||||||
emit('refresh')
|
emit('refresh')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import api from '../utils/api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Loading, InfoFilled, Paperclip } from '@element-plus/icons-vue'
|
import { Loading, InfoFilled, Paperclip } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ const pinnedLoading = ref(false)
|
|||||||
const loadPinnedBids = async () => {
|
const loadPinnedBids = async () => {
|
||||||
pinnedLoading.value = true
|
pinnedLoading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/api/bids/pinned')
|
const response = await api.get('/api/bids/pinned')
|
||||||
pinnedBids.value = response.data
|
pinnedBids.value = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load pinned bids:', error)
|
console.error('Failed to load pinned bids:', error)
|
||||||
@@ -75,7 +75,7 @@ const loadPinnedBids = async () => {
|
|||||||
// 切换置顶列表的 Pin 状态
|
// 切换置顶列表的 Pin 状态
|
||||||
const togglePin = async (item: any) => {
|
const togglePin = async (item: any) => {
|
||||||
try {
|
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)
|
const index = pinnedBids.value.findIndex(b => b.title === item.title)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
pinnedBids.value.splice(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/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
base: './',
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -22,7 +22,9 @@
|
|||||||
"update-source": "ts-node -r tsconfig-paths/register src/scripts/update-source.ts",
|
"update-source": "ts-node -r tsconfig-paths/register src/scripts/update-source.ts",
|
||||||
"ai-recommendations": "ts-node -r tsconfig-paths/register src/scripts/ai-recommendations.ts",
|
"ai-recommendations": "ts-node -r tsconfig-paths/register src/scripts/ai-recommendations.ts",
|
||||||
"sync": "ts-node -r tsconfig-paths/register src/scripts/sync.ts",
|
"sync": "ts-node -r tsconfig-paths/register src/scripts/sync.ts",
|
||||||
"deploy": "powershell -ExecutionPolicy Bypass -File src/scripts/deploy.ps1"
|
"deploy": "powershell -ExecutionPolicy Bypass -File src/scripts/deploy.ps1",
|
||||||
|
"electron:start": "npm run build && set NODE_ENV=development&& electron ./app",
|
||||||
|
"electron:build": "npm run build && electron-builder --config ./app/electron-builder.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
@@ -53,11 +55,17 @@
|
|||||||
"@nestjs/cli": "^11.0.14",
|
"@nestjs/cli": "^11.0.14",
|
||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@types/cacheable-request": "^6.0.3",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
|
"@types/http-cache-semantics": "^4.0.4",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
"@types/responselike": "^1.0.3",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
"electron": "^39.2.7",
|
||||||
|
"electron-builder": "^26.4.0",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-prettier": "^5.2.2",
|
"eslint-plugin-prettier": "^5.2.2",
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ const appLogTransport = new DailyRotateFile({
|
|||||||
datePattern: 'YYYY-MM-DD',
|
datePattern: 'YYYY-MM-DD',
|
||||||
maxSize: '20m',
|
maxSize: '20m',
|
||||||
maxFiles: '30d',
|
maxFiles: '30d',
|
||||||
format: logFormat,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 错误日志传输(按天轮转)
|
// 错误日志传输(按天轮转)
|
||||||
@@ -68,16 +67,15 @@ const errorLogTransport = new DailyRotateFile({
|
|||||||
dirname: logDir,
|
dirname: logDir,
|
||||||
filename: 'error-%DATE%.log',
|
filename: 'error-%DATE%.log',
|
||||||
datePattern: 'YYYY-MM-DD',
|
datePattern: 'YYYY-MM-DD',
|
||||||
level: 'error',
|
|
||||||
maxSize: '20m',
|
maxSize: '20m',
|
||||||
maxFiles: '30d',
|
maxFiles: '30d',
|
||||||
format: logFormat,
|
level: 'error',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 winston logger 实例
|
// 创建 winston logger 实例
|
||||||
export const winstonLogger = winston.createLogger({
|
export const winstonLogger = winston.createLogger({
|
||||||
level: process.env.LOG_LEVEL || 'info',
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
format: logFormat,
|
format: logFormat,
|
||||||
transports: [consoleTransport, appLogTransport, errorLogTransport],
|
transports: [consoleTransport, appLogTransport as any, errorLogTransport as any],
|
||||||
exitOnError: false,
|
exitOnError: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,19 @@ interface CrawlResult {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnyCrawler = typeof ChdtpCrawler | typeof ChngCrawler | typeof SzecpCrawler | typeof CdtCrawler | typeof EpsCrawler | typeof CnncecpCrawler | typeof CgnpcCrawler | typeof CeicCrawler | typeof EspicCrawler | typeof PowerbeijingCrawler | typeof SdiccCrawler | typeof CnoocCrawler;
|
type AnyCrawler =
|
||||||
|
| typeof ChdtpCrawler
|
||||||
|
| typeof ChngCrawler
|
||||||
|
| typeof SzecpCrawler
|
||||||
|
| typeof CdtCrawler
|
||||||
|
| typeof EpsCrawler
|
||||||
|
| typeof CnncecpCrawler
|
||||||
|
| typeof CgnpcCrawler
|
||||||
|
| typeof CeicCrawler
|
||||||
|
| typeof EspicCrawler
|
||||||
|
| typeof PowerbeijingCrawler
|
||||||
|
| typeof SdiccCrawler
|
||||||
|
| typeof CnoocCrawler;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BidCrawlerService {
|
export class BidCrawlerService {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { BidItem } from '../bids/entities/bid-item.entity';
|
|
||||||
import { Keyword } from '../keywords/keyword.entity';
|
|
||||||
import { AiRecommendation } from '../ai/entities/ai-recommendation.entity';
|
|
||||||
import { CrawlInfoAdd } from '../crawler/entities/crawl-info-add.entity';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,7 +18,7 @@ import { CrawlInfoAdd } from '../crawler/entities/crawl-info-add.entity';
|
|||||||
username: configService.get<string>('DATABASE_USERNAME', 'root'),
|
username: configService.get<string>('DATABASE_USERNAME', 'root'),
|
||||||
password: configService.get<string>('DATABASE_PASSWORD', 'root'),
|
password: configService.get<string>('DATABASE_PASSWORD', 'root'),
|
||||||
database: configService.get<string>('DATABASE_NAME', 'bidding'),
|
database: configService.get<string>('DATABASE_NAME', 'bidding'),
|
||||||
entities: [BidItem, Keyword, AiRecommendation, CrawlInfoAdd],
|
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "nodenext",
|
"module": "commonjs",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "node",
|
||||||
"resolvePackageJsonExports": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# README
|
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
This is the official Wails Vue-TS template.
|
|
||||||
|
|
||||||
You can configure the project by editing `wails.json`. More information about the project settings can be found
|
|
||||||
here: https://wails.io/docs/reference/project-config
|
|
||||||
|
|
||||||
## Live Development
|
|
||||||
|
|
||||||
To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
|
|
||||||
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
|
|
||||||
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
|
|
||||||
to this in your browser, and you can call your Go code from devtools.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To build a redistributable, production mode package, use `wails build`.
|
|
||||||
Reference in New Issue
Block a user