Compare commits

...

2 Commits

Author SHA1 Message Date
6e0e4b684f Merge remote-tracking branch 'origin/master' 2026-01-22 10:05:03 +08:00
7cf7784a89 添加自动执行python脚本 2026-01-22 10:04:38 +08:00
9 changed files with 947 additions and 1 deletions

1
.gitignore vendored
View File

@@ -81,7 +81,6 @@ jre/
jdk/
# 数据库文件
*.db
*.db-shm
*.db-wal
*.sqlite

View File

@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"axios": "^1.13.2",
"echarts": "^5.6.0",
"vue": "^3.5.24"
},
"devDependencies": {
@@ -593,6 +594,22 @@
"node": ">= 0.4"
}
},
"node_modules/echarts": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/entities": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz",
@@ -1364,6 +1381,21 @@
"optional": true
}
}
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
}
}
}

View File

@@ -10,6 +10,7 @@
},
"dependencies": {
"axios": "^1.13.2",
"echarts": "^5.6.0",
"vue": "^3.5.24"
},
"devDependencies": {

View File

@@ -0,0 +1,706 @@
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import axios from 'axios';
import * as echarts from 'echarts';
// 输入框数据
const input1 = ref('');
const input2 = ref('');
// 表格数据
const tableData = ref([
{ id: 1, name: '项目1', value: 120, status: '正常' },
{ id: 2, name: '项目2', value: 230, status: '警告' },
{ id: 3, name: '项目3', value: 180, status: '正常' },
{ id: 4, name: '项目4', value: 90, status: '异常' },
{ id: 5, name: '项目5', value: 320, status: '正常' },
{ id: 6, name: '项目6', value: 270, status: '警告' },
]);
const tableLoading = ref(false);
const tableError = ref('');
// 折线图数据
const chartData1 = ref([65, 59, 80, 81, 56, 55, 40]);
const chartData2 = ref([28, 48, 40, 19, 86, 27, 90]);
const chartLabels = ref(['1月', '2月', '3月', '4月', '5月', '6月', '7月']);
const loading = ref(false);
const error = ref('');
// ECharts实例
const chart1Ref = ref(null);
const chart2Ref = ref(null);
const chart1 = ref(null);
const chart2 = ref(null);
// 初始化ECharts图表
function initCharts() {
// 初始化图表1
if (chart1Ref.value) {
chart1.value = echarts.init(chart1Ref.value);
updateChart1();
}
// 初始化图表2
if (chart2Ref.value) {
chart2.value = echarts.init(chart2Ref.value);
updateChart2();
}
}
// 更新图表1
function updateChart1() {
if (!chart1.value) return;
const option = {
title: {
text: '折线图1',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['数据'],
bottom: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: chartLabels.value
},
yAxis: {
type: 'value'
},
series: [
{
name: '数据',
type: 'line',
smooth: true,
data: chartData1.value,
itemStyle: {
color: '#4CAF50'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(76, 175, 80, 0.3)'
}, {
offset: 1,
color: 'rgba(76, 175, 80, 0.1)'
}]
}
}
}
]
};
chart1.value.setOption(option);
}
// 更新图表2
function updateChart2() {
if (!chart2.value) return;
const option = {
title: {
text: '折线图2',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['数据'],
bottom: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: chartLabels.value
},
yAxis: {
type: 'value'
},
series: [
{
name: '数据',
type: 'line',
smooth: true,
data: chartData2.value,
itemStyle: {
color: '#2196F3'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(33, 150, 243, 0.3)'
}, {
offset: 1,
color: 'rgba(33, 150, 243, 0.1)'
}]
}
}
}
]
};
chart2.value.setOption(option);
}
// 监听窗口大小变化
function handleResize() {
chart1.value?.resize();
chart2.value?.resize();
}
// 从后端获取折线图数据
async function fetchChartData() {
loading.value = true;
error.value = '';
try {
// 获取折线图1数据
const response1 = await axios.get('http://localhost:8080/api/charts/line1');
if (response1.data) {
chartData1.value = response1.data.data;
chartLabels.value = response1.data.labels;
}
// 获取折线图2数据
const response2 = await axios.get('http://localhost:8080/api/charts/line2');
if (response2.data) {
chartData2.value = response2.data.data;
}
// 更新图表
nextTick(() => {
updateChart1();
updateChart2();
});
} catch (err) {
error.value = '获取图表数据失败,请刷新页面重试';
console.error('Error fetching chart data:', err);
} finally {
loading.value = false;
}
}
// 从后端获取表格数据
async function fetchTableData() {
tableLoading.value = true;
tableError.value = '';
try {
const response = await axios.get('http://localhost:8080/api/table');
if (response.data && Array.isArray(response.data)) {
tableData.value = response.data;
}
} catch (err) {
tableError.value = '获取表格数据失败,请刷新页面重试';
console.error('Error fetching table data:', err);
} finally {
tableLoading.value = false;
}
}
onMounted(() => {
// 初始化图表
nextTick(() => {
initCharts();
});
// 从后端获取数据
fetchChartData();
fetchTableData();
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
// 销毁图表
chart1.value?.dispose();
chart2.value?.dispose();
// 移除事件监听器
window.removeEventListener('resize', handleResize);
});
</script>
<template>
<div class="main-container">
<!-- 左边区域 -->
<div class="left-section">
<!-- 账号信息面板 -->
<div class="account-info">
<div class="account-avatar">👤</div>
<div class="account-details">
<div class="account-name">用户名</div>
<div class="account-role">账户余额</div>
</div>
<div class="account-actions">
<button type="button" class="login-button">登录</button>
<button type="button" class="logout-button">退出</button>
</div>
</div>
<!-- 顶部输入框区域 -->
<div class="top-inputs">
<div class="input-group">
<label for="input1">输入1</label>
<div class="input-with-button">
<input type="text" id="input1" v-model="input1" placeholder="请输入内容">
</div>
</div>
<div class="input-group">
<label for="input2">输入2</label>
<div class="input-with-button">
<input type="text" id="input2" v-model="input2" placeholder="请输入内容">
<button type="button" class="confirm-button">确认</button>
</div>
</div>
</div>
<!-- 表格错误提示 -->
<div v-if="tableError" class="error-message">
{{ tableError }}
<button class="retry-button" @click="fetchTableData">重试</button>
</div>
<!-- 主体表格区域 -->
<div class="table-container">
<h3>数据表格</h3>
<div v-if="tableLoading" class="table-loading">
加载中...
</div>
<table v-else class="data-table">
<thead>
<tr>
<th>ID</th>
<th>项目名称</th>
<th>数值</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="item in tableData" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.value }}</td>
<td>
<span :class="`status-badge ${item.status === '正常' ? 'status-normal' : item.status === '警告' ? 'status-warning' : 'status-error'}`">
{{ item.status }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 右边折线图区域 -->
<div class="right-section">
<!-- 错误提示 -->
<div v-if="error" class="error-message">
{{ error }}
<button class="retry-button" @click="fetchChartData">重试</button>
</div>
<div class="chart-container">
<div v-if="loading" class="chart-loading">
加载中...
</div>
<div v-else ref="chart1Ref" class="echart-container"></div>
</div>
<div class="chart-container">
<div v-if="loading" class="chart-loading">
加载中...
</div>
<div v-else ref="chart2Ref" class="echart-container"></div>
</div>
</div>
</div>
</template>
<style scoped>
.main-container {
display: flex;
gap: 20px;
width: 100%;
height: 100%;
padding: 20px;
box-sizing: border-box;
}
.left-section {
flex: 1;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
height: 100%;
min-height: 100%;
box-sizing: border-box;
}
/* 账号信息面板 */
.account-info {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
flex-shrink: 0;
}
.account-avatar {
font-size: 2.5rem;
line-height: 1;
}
.account-details {
flex: 1;
}
.account-name {
font-weight: 700;
color: #333;
font-size: 1rem;
margin-bottom: 4px;
}
.account-role {
font-size: 0.85rem;
color: #666;
}
.account-actions {
display: flex;
gap: 8px;
}
.login-button {
padding: 6px 12px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
}
.login-button:hover {
background-color: #1976D2;
}
.login-button:active {
background-color: #1565C0;
transform: translateY(1px);
}
.logout-button {
padding: 6px 12px;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
}
.logout-button:hover {
background-color: #5a6268;
}
.logout-button:active {
background-color: #495057;
transform: translateY(1px);
}
/* 顶部输入框区域 */
.top-inputs {
display: flex;
gap: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #e0e0e0;
flex-shrink: 0;
}
.input-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.input-with-button {
display: flex;
gap: 8px;
align-items: flex-start;
}
.input-with-button input {
flex: 1;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s ease;
}
.input-with-button input:focus {
outline: none;
border-color: #2196F3;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
}
.confirm-button {
padding: 10px 16px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
white-space: nowrap;
}
.confirm-button:hover {
background-color: #1976D2;
}
.confirm-button:active {
background-color: #1565C0;
transform: translateY(1px);
}
/* 表格区域 */
.table-container {
flex: 1;
overflow: auto;
min-height: 0;
}
.right-section {
flex: 2;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
gap: 30px;
height: 100%;
min-height: 100%;
box-sizing: border-box;
}
/* 图表区域 */
.chart-container {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
min-height: 0;
}
.input-group label {
font-weight: 600;
color: #333;
font-size: 14px;
}
/* 表格区域 */
.table-container {
flex: 1;
overflow: auto;
}
.table-container h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 16px;
font-weight: 600;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.data-table th,
.data-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.data-table th {
background-color: #f5f5f5;
font-weight: 600;
color: #333;
position: sticky;
top: 0;
z-index: 1;
}
.data-table tr:hover {
background-color: #f9f9f9;
}
/* 状态标签 */
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.status-normal {
background-color: #e8f5e9;
color: #2e7d32;
}
.status-warning {
background-color: #fff3e0;
color: #ef6c00;
}
.status-error {
background-color: #ffebee;
color: #c62828;
}
/* 错误提示 */
.error-message {
background-color: #ffebee;
color: #c62828;
padding: 12px 16px;
border-radius: 4px;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.retry-button {
padding: 4px 12px;
background-color: #c62828;
color: white;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
}
.retry-button:hover {
background-color: #b71c1c;
}
/* 加载状态 */
.chart-loading {
display: flex;
justify-content: center;
align-items: center;
height: 250px;
font-size: 14px;
color: #666;
background-color: #f5f5f5;
border-radius: 4px;
}
.table-loading {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
font-size: 14px;
color: #666;
background-color: #f5f5f5;
border-radius: 4px;
margin-top: 15px;
}
/* 图表区域 */
.chart-container {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
}
.chart-container h3 {
margin: 0;
color: #333;
font-size: 16px;
font-weight: 600;
}
.echart-container {
width: 100%;
height: 250px;
border-radius: 4px;
overflow: hidden;
}
/* 响应式布局 */
@media (max-width: 768px) {
.main-container {
flex-direction: column;
}
.top-inputs {
flex-direction: column;
}
.right-section {
flex-direction: column;
}
.chart-container {
min-height: 300px;
}
}
</style>

View File

@@ -3,8 +3,10 @@ package com.tem.bocai;
import com.tem.bocai.util.SQLiteUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class BocaiApplication {
public static void main(String[] args) {

View File

@@ -0,0 +1,94 @@
package com.tem.bocai.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@RestController
@RequestMapping("/api")
public class ChartController {
// 获取折线图1数据
@GetMapping("/charts/line1")
public Map<String, Object> getLineChartData1() {
Map<String, Object> response = new HashMap<>();
// 模拟数据 - 实际项目中应该从数据库获取
List<Integer> data = Arrays.asList(65, 59, 80, 81, 56, 55, 40);
List<String> labels = Arrays.asList("1月", "2月", "3月", "4月", "5月", "6月", "7月");
response.put("data", data);
response.put("labels", labels);
response.put("title", "折线图1数据");
return response;
}
// 获取折线图2数据
@GetMapping("/charts/line2")
public Map<String, Object> getLineChartData2() {
Map<String, Object> response = new HashMap<>();
// 模拟数据 - 实际项目中应该从数据库获取
List<Integer> data = Arrays.asList(28, 48, 40, 19, 86, 27, 90);
List<String> labels = Arrays.asList("1月", "2月", "3月", "4月", "5月", "6月", "7月");
response.put("data", data);
response.put("labels", labels);
response.put("title", "折线图2数据");
return response;
}
// 获取表格数据
@GetMapping("/table")
public List<Map<String, Object>> getTableData() {
List<Map<String, Object>> tableData = new ArrayList<>();
// 模拟数据 - 实际项目中应该从数据库获取
Map<String, Object> item1 = new HashMap<>();
item1.put("id", 1);
item1.put("name", "项目1");
item1.put("value", 120);
item1.put("status", "正常");
tableData.add(item1);
Map<String, Object> item2 = new HashMap<>();
item2.put("id", 2);
item2.put("name", "项目2");
item2.put("value", 230);
item2.put("status", "警告");
tableData.add(item2);
Map<String, Object> item3 = new HashMap<>();
item3.put("id", 3);
item3.put("name", "项目3");
item3.put("value", 180);
item3.put("status", "正常");
tableData.add(item3);
Map<String, Object> item4 = new HashMap<>();
item4.put("id", 4);
item4.put("name", "项目4");
item4.put("value", 90);
item4.put("status", "异常");
tableData.add(item4);
Map<String, Object> item5 = new HashMap<>();
item5.put("id", 5);
item5.put("name", "项目5");
item5.put("value", 320);
item5.put("status", "正常");
tableData.add(item5);
Map<String, Object> item6 = new HashMap<>();
item6.put("id", 6);
item6.put("name", "项目6");
item6.put("value", 270);
item6.put("status", "警告");
tableData.add(item6);
return tableData;
}
}

View File

@@ -0,0 +1,112 @@
package com.tem.bocai.schedules;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@Component
public class GetPredictResultSchedule {
/**
* 处理文件路径,确保路径正确
* @param filePath 文件路径
* @return 处理后的文件路径
*/
private String handleFilePath(String filePath) {
// 处理路径分隔符,统一使用系统默认分隔符
filePath = filePath.replace("/", System.getProperty("file.separator"));
filePath = filePath.replace("\\", System.getProperty("file.separator"));
// 如果路径包含空格,确保路径被正确处理
// Runtime.exec会自动处理带空格的路径不需要手动添加引号
return filePath;
}
// 每天凌晨2点执行
@Scheduled(cron = "0 0 2 * * ?")
public void executePythonScript() {
System.out.println("开始执行Python脚本...");
// 示例1基本参数
// String[] params = {"param1", "param2", "param3"};
// executePythonScriptWithParams("your_script.py", params);
// 示例2传递文件路径作为参数
String[] paramsWithFiles = {
"--input", "data/input.csv",
"--output", "results/output.json",
"--config", "config/settings.ini"
};
executePythonScriptWithParams("scripts/process_data.py", paramsWithFiles);
}
/**
* 执行带参数的Python脚本
* @param scriptPath Python脚本路径
* @param params 脚本参数数组(可以包含文件路径)
*/
public void executePythonScriptWithParams(String scriptPath, String[] params) {
try {
// 处理脚本路径,确保路径正确
scriptPath = handleFilePath(scriptPath);
// 构建命令数组
String[] command = new String[params.length + 2];
command[0] = "python";
command[1] = scriptPath;
// 添加参数,处理文件路径参数
for (int i = 0; i < params.length; i++) {
// 检查参数是否为文件路径(包含路径分隔符)
if (params[i].contains("\\") || params[i].contains("/")) {
command[i + 2] = handleFilePath(params[i]);
} else {
command[i + 2] = params[i];
}
}
System.out.println("执行命令: " + String.join(" ", command));
// 执行Python脚本
Process process = Runtime.getRuntime().exec(command);
// 读取脚本输出
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "UTF-8")
);
String line;
StringBuilder output = new StringBuilder();
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
// 读取错误输出
BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream(), "UTF-8")
);
StringBuilder errorOutput = new StringBuilder();
while ((line = errorReader.readLine()) != null) {
errorOutput.append(line).append("\n");
}
// 等待脚本执行完成
int exitCode = process.waitFor();
System.out.println("Python脚本执行完成退出码: " + exitCode);
System.out.println("脚本输出:\n" + output.toString());
if (exitCode != 0) {
System.err.println("脚本执行错误:\n" + errorOutput.toString());
}
} catch (IOException | InterruptedException e) {
System.err.println("执行Python脚本时发生错误:");
e.printStackTrace();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB