feat: fix frontend

This commit is contained in:
shenjianZ 2025-06-22 12:10:12 +08:00
parent 7cf5070288
commit 236364a10f
10 changed files with 1072 additions and 395 deletions

31
frontend/Dockerfile Normal file
View File

@ -0,0 +1,31 @@
# 构建阶段
FROM node:18-alpine AS build
WORKDIR /app
# 复制package.json和package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 复制自定义nginx配置
COPY nginx.conf /etc/nginx/nginx.conf
# 复制构建结果到nginx目录
COPY --from=build /app/dist /usr/share/nginx/html
# 暴露端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

79
frontend/nginx.conf Normal file
View File

@ -0,0 +1,79 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 开启gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 6;
gzip_types text/plain text/css text/javascript application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html index.htm;
# 处理单页应用的路由
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket代理
location /ws/ {
proxy_pass http://backend:8080/ws/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 安全配置
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,32 +7,33 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"build:prod": "vite build --mode production",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"vue": "^3.3.0",
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"vuex": "^4.0.2",
"element-plus": "^2.3.0",
"element-plus": "^2.8.0",
"echarts": "^5.4.0",
"axios": "^1.4.0",
"dayjs": "^1.11.0",
"dayjs": "^1.11.10",
"lodash": "^4.17.21",
"sockjs-client": "^1.6.1",
"stompjs": "^2.3.3",
"@element-plus/icons-vue": "^2.1.0"
"@element-plus/icons-vue": "^2.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.0",
"vite": "^4.3.0",
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.0",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.11.0",
"prettier": "^2.8.8",
"sass": "^1.62.0",
"unplugin-auto-import": "^0.16.0",
"unplugin-vue-components": "^0.25.0"
"unplugin-auto-import": "^0.17.0",
"unplugin-vue-components": "^0.26.0"
},
"engines": {
"node": ">=16.0.0",

View File

@ -7,7 +7,7 @@ import request from '@/utils/request'
// 获取最新的市场分析数据
export function getLatestMarketAnalysis() {
return request({
url: '/api/market/latest',
url: '/market/latest',
method: 'get'
})
}
@ -15,7 +15,7 @@ export function getLatestMarketAnalysis() {
// 获取指定日期范围的市场分析数据
export function getMarketAnalysisByDateRange(startDate, endDate) {
return request({
url: '/api/market/range',
url: '/market/range',
method: 'get',
params: {
startDate,
@ -27,7 +27,7 @@ export function getMarketAnalysisByDateRange(startDate, endDate) {
// 获取最近N天的市场分析数据
export function getRecentMarketAnalysis(days) {
return request({
url: `/api/market/recent/${days}`,
url: `/market/recent/${days}`,
method: 'get'
})
}

View File

@ -7,7 +7,7 @@ import request from '@/utils/request'
// 获取实时股票数据
export function getRealtimeStockData() {
return request({
url: '/api/stock/realtime',
url: '/stock/realtime',
method: 'get'
})
}
@ -15,7 +15,7 @@ export function getRealtimeStockData() {
// 获取股票历史数据
export function getStockHistory(stockCode, startDate, endDate) {
return request({
url: `/api/stock/history/${stockCode}`,
url: `/stock/history/${stockCode}`,
method: 'get',
params: {
startDate,
@ -27,7 +27,7 @@ export function getStockHistory(stockCode, startDate, endDate) {
// 获取涨幅排行榜
export function getGrowthRanking(limit = 10) {
return request({
url: '/api/stock/ranking/growth',
url: '/stock/ranking/growth',
method: 'get',
params: { limit }
})
@ -36,7 +36,7 @@ export function getGrowthRanking(limit = 10) {
// 获取市值排行榜
export function getMarketCapRanking(limit = 10) {
return request({
url: '/api/stock/ranking/market-cap',
url: '/stock/ranking/market-cap',
method: 'get',
params: { limit }
})
@ -45,7 +45,7 @@ export function getMarketCapRanking(limit = 10) {
// 获取成交量排行榜
export function getVolumeRanking(limit = 10) {
return request({
url: '/api/stock/ranking/volume',
url: '/stock/ranking/volume',
method: 'get',
params: { limit }
})
@ -54,7 +54,7 @@ export function getVolumeRanking(limit = 10) {
// 获取股票趋势分析
export function getStockTrend(stockCode, days = 30) {
return request({
url: `/api/stock/trend/${stockCode}`,
url: `/stock/trend/${stockCode}`,
method: 'get',
params: { days }
})
@ -63,7 +63,7 @@ export function getStockTrend(stockCode, days = 30) {
// 获取市场综合分析
export function getMarketAnalysis() {
return request({
url: '/api/stock/market-analysis',
url: '/stock/market-analysis',
method: 'get'
})
}
@ -71,7 +71,7 @@ export function getMarketAnalysis() {
// 获取股票预测数据
export function getStockPrediction(stockCode, days = 7) {
return request({
url: `/api/stock/prediction/${stockCode}`,
url: `/stock/prediction/${stockCode}`,
method: 'get',
params: { days }
})
@ -80,7 +80,7 @@ export function getStockPrediction(stockCode, days = 7) {
// 获取股票详情
export function getStockDetail(stockCode) {
return request({
url: `/api/stock/detail/${stockCode}`,
url: `/stock/detail/${stockCode}`,
method: 'get'
})
}
@ -88,7 +88,7 @@ export function getStockDetail(stockCode) {
// 搜索股票
export function searchStocks(keyword) {
return request({
url: '/api/stock/search',
url: '/stock/search',
method: 'get',
params: { keyword }
})
@ -97,7 +97,7 @@ export function searchStocks(keyword) {
// 保存股票数据
export function saveStockData(stockData) {
return request({
url: '/api/stock/save',
url: '/stock/save',
method: 'post',
data: stockData
})
@ -106,7 +106,7 @@ export function saveStockData(stockData) {
// 批量保存股票数据
export function batchSaveStockData(stockDataList) {
return request({
url: '/api/stock/batch-save',
url: '/stock/batch-save',
method: 'post',
data: stockDataList
})

View File

@ -1,10 +1,29 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 根据构建模式获取API基础地址
const getBaseURL = () => {
// 首先尝试从环境变量获取
if (import.meta.env.VITE_APP_BASE_API) {
return import.meta.env.VITE_APP_BASE_API
}
// 如果是开发环境且在localhost:3000使用代理
if (import.meta.env.DEV && window.location.hostname === 'localhost') {
return '/api'
}
// 生产环境或其他情况下的默认配置
return import.meta.env.PROD
? 'http://192.168.1.36:8080/api' // 生产环境
: 'http://localhost:8080/api' // 开发环境
}
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API || 'http://localhost:8080', // 后端API地址
timeout: 15000 // 请求超时时间
baseURL: getBaseURL(),
timeout: 15000, // 请求超时时间
withCredentials: false // 允许跨域请求携带凭证
})
// 请求拦截器

View File

@ -42,9 +42,9 @@
</el-table-column>
<el-table-column prop="stockCode" label="股票代码" width="100" />
<el-table-column prop="stockName" label="股票名称" width="150" />
<el-table-column prop="currentPrice" label="当前价格" width="100">
<el-table-column prop="closePrice" label="当前价格" width="100">
<template #default="scope">
¥{{ scope.row.currentPrice?.toFixed(2) }}
¥{{ scope.row.closePrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="changePercent" label="涨跌幅" width="120">
@ -118,9 +118,9 @@
</el-table-column>
<el-table-column prop="stockCode" label="股票代码" width="100" />
<el-table-column prop="stockName" label="股票名称" width="150" />
<el-table-column prop="currentPrice" label="当前价格" width="100">
<el-table-column prop="closePrice" label="当前价格" width="100">
<template #default="scope">
¥{{ scope.row.currentPrice?.toFixed(2) }}
¥{{ scope.row.closePrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="marketCap" label="市值" width="150">
@ -189,9 +189,9 @@
</el-table-column>
<el-table-column prop="stockCode" label="股票代码" width="100" />
<el-table-column prop="stockName" label="股票名称" width="150" />
<el-table-column prop="currentPrice" label="当前价格" width="100">
<el-table-column prop="closePrice" label="当前价格" width="100">
<template #default="scope">
¥{{ scope.row.currentPrice?.toFixed(2) }}
¥{{ scope.row.closePrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="volume" label="成交量" width="150">

View File

@ -56,9 +56,9 @@
<el-table :data="searchResults" stripe @row-click="viewStockDetail">
<el-table-column prop="stockCode" label="股票代码" width="120" />
<el-table-column prop="stockName" label="股票名称" width="200" />
<el-table-column prop="currentPrice" label="当前价格" width="120">
<el-table-column prop="closePrice" label="当前价格" width="120">
<template #default="scope">
¥{{ scope.row.currentPrice?.toFixed(2) }}
¥{{ scope.row.closePrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="changePercent" label="涨跌幅" width="120">
@ -155,9 +155,9 @@
<el-table :data="paginatedRealtimeData" v-loading="realtimeLoading" stripe>
<el-table-column prop="stockCode" label="股票代码" width="100" />
<el-table-column prop="stockName" label="股票名称" width="150" />
<el-table-column prop="currentPrice" label="当前价格" width="100">
<el-table-column prop="closePrice" label="当前价格" width="100">
<template #default="scope">
¥{{ scope.row.currentPrice?.toFixed(2) }}
¥{{ scope.row.closePrice?.toFixed(2) }}
</template>
</el-table-column>
<el-table-column prop="changePercent" label="涨跌幅" width="100">

View File

@ -6,7 +6,20 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { resolve } from 'path'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
export default defineConfig(({ mode }) => {
// 根据构建模式确定后端服务器地址
const getBackendUrl = () => {
if (mode === 'production') {
return 'http://192.168.1.36:8080' // 生产环境后端地址
}
return 'http://localhost:8080' // 开发环境后端地址
}
const backendUrl = getBackendUrl()
const apiBaseUrl = `${backendUrl}/api`
console.log(`构建模式: ${mode}, 后端地址: ${backendUrl}, API基础地址: ${apiBaseUrl}`)
return {
plugins: [
vue(),
AutoImport({
@ -18,12 +31,14 @@ export default defineConfig({
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
define: {
// 为了兼容一些依赖包
global: 'globalThis',
// 定义环境变量
__VITE_APP_BASE_API__: JSON.stringify(apiBaseUrl),
},
server: {
host: '0.0.0.0',
@ -31,12 +46,12 @@ export default defineConfig({
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
target: backendUrl,
changeOrigin: true,
secure: false,
},
'/ws': {
target: 'ws://localhost:8080',
target: backendUrl.replace('http', 'ws'),
ws: true,
changeOrigin: true,
},
@ -68,7 +83,20 @@ export default defineConfig({
'element-plus',
'@element-plus/icons-vue',
'echarts',
'dayjs'
'dayjs',
'dayjs/plugin/customParseFormat',
'dayjs/plugin/advancedFormat',
'dayjs/plugin/localeData',
'dayjs/plugin/weekOfYear',
'dayjs/plugin/weekYear',
'dayjs/plugin/dayOfYear',
'dayjs/plugin/isSameOrAfter',
'dayjs/plugin/isSameOrBefore',
'dayjs/plugin/utc',
'dayjs/plugin/timezone'
],
exclude: [],
force: true
},
}
})