Files
bocai/frontend/src/components/index.vue

1059 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 loginDialogVisible = ref(false);
const isLoggedIn = ref(false);
const username = ref('未记录');
const loginForm = ref({
username: '',
password: '',
loginUrl: 'https://4701268539-esh.qdk63ayw8g.com'
});
const loginLoading = ref(false);
const loginError = ref('');
// 表格数据
const tableData = ref([]);
const tableLoading = ref(false);
const tableError = ref('');
// 折线图数据
const chartLabels = ref();
const chartData1 = ref();
const loading = ref(false);
const error = ref('');
// ECharts实例
const chart1Ref = ref(null);
const chart1 = ref(null);
// 初始化ECharts图表
function initCharts() {
console.log('开始初始化图表');
console.log('chart1Ref.value:', chart1Ref.value);
// 初始化图表
if (chart1Ref.value) {
console.log('初始化图表');
chart1.value = echarts.init(chart1Ref.value);
updateChart1();
}
console.log('图表初始化完成');
}
// 更新图表1
function updateChart1() {
console.log('更新图表1');
console.log('chart1.value:', chart1.value);
console.log('chartData1.value:', chartData1.value);
console.log('chartLabels.value:', chartLabels.value);
if (!chart1.value) return;
const option = {
title: {
text: '今日盈亏数据',
left: 'center'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['数据'],
bottom: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '30%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: chartLabels.value,
axisLabel: {
rotate: 45,
interval: chartLabels.value.length > 12 ? Math.floor(chartLabels.value.length / 12) : 0, // 根据标签数量动态调整间隔,确保至少显示一些标签
fontSize: 10
}
},
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)'
}]
}
}
}
]
};
console.log('设置图表1选项');
chart1.value.setOption(option);
console.log('图表1更新完成');
}
// 监听窗口大小变化
function handleResize() {
chart1.value?.resize();
}
// 从后端获取折线图数据
async function fetchChartData() {
console.log('开始获取图表数据');
loading.value = true;
error.value = '';
try {
// 获取折线图数据
console.log('获取折线图数据');
const response = await axios.get('http://localhost:8080/api/charts/line1');
console.log('折线图数据响应:', response.data);
if (response.data) {
// 更新数据
if (response.data.data) {
chartData1.value = response.data.data;
}
// 更新标签
if (response.data.labels) {
chartLabels.value = response.data.labels;
}
// 确保数据长度与标签长度一致
if (chartData1.value.length !== chartLabels.value.length) {
console.warn('数据长度与标签长度不一致,使用默认数据');
chartData1.value = generateRandomData(chartLabels.value.length);
}
}
console.log('图表数据获取完成');
console.log('chartData1.value:', chartData1.value);
console.log('chartLabels.value:', chartLabels.value);
console.log('标签数量:', chartLabels.value.length);
// 更新图表
nextTick(() => {
console.log('更新图表数据');
updateChart1();
});
} catch (err) {
console.error('获取图表数据失败:', err);
error.value = '获取图表数据失败,请刷新页面重试';
// 使用默认数据
console.log('使用默认数据');
console.log('默认 chartData1.value:', chartData1.value);
console.log('默认 chartLabels.value:', chartLabels.value);
// 更新图表
nextTick(() => {
console.log('使用默认数据更新图表');
updateChart1();
});
} 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;
}
}
// 从后端获取用户设置数据(用户名、止盈点、止亏点)
async function fetchUserSettings() {
console.log('开始获取用户设置数据');
try {
// 获取用户设置数据
console.log('获取用户设置数据');
const response = await axios.get('http://localhost:8080/api/ocr/userSettings');
console.log('用户设置数据响应:', response.data);
if (response.data) {
// 更新用户名
if (response.data.username) {
username.value = response.data.username;
isLoggedIn.value = true;
}
// 更新止盈点
if (response.data.winNum) {
input1.value = response.data.winNum;
}
// 更新止亏点
if (response.data.loseNum) {
input2.value = response.data.loseNum;
}
}
console.log('用户设置数据获取完成');
console.log('username.value:', username.value);
console.log('input1.value:', input1.value);
console.log('input2.value:', input2.value);
} catch (err) {
console.error('获取用户设置数据失败:', err);
// 失败时不显示错误,使用默认值
}
}
onMounted(() => {
console.log('组件挂载完成');
// 从后端获取数据
fetchChartData();
fetchTableData();
fetchUserSettings();
// 初始化图表延迟一点时间确保DOM完全渲染
setTimeout(() => {
console.log('延迟初始化图表');
nextTick(() => {
console.log('nextTick后初始化图表');
initCharts();
});
}, 100);
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
// 处理登录
async function handleLogin() {
loginError.value = '';
loginLoading.value = true;
try {
console.log('登录信息:', loginForm.value);
// 验证表单
if (!loginForm.value.username || !loginForm.value.password || !loginForm.value.loginUrl) {
loginError.value = '请填写完整登录信息';
return;
}
// 构建登录API地址
const loginApiUrl = `http://localhost:8080/api/ocr/saveUserInfo`;
console.log('登录API地址:', loginApiUrl);
// 调用登录API
const response = await axios.post(loginApiUrl, {
username: loginForm.value.username,
password: loginForm.value.password
});
// 处理登录结果
if (response.data && response.code !== 500) {
// 登录成功
loginLoading.value = false;
loginDialogVisible.value = false;
isLoggedIn.value = true;
// 更新用户名
username.value = loginForm.value.username;
// 重置表单
loginForm.value = {
username: '',
password: '',
loginUrl: 'https://4701268539-esh.qdk63ayw8g.com'
};
console.log('登录成功:', response.data);
} else {
// 登录失败
loginError.value = response.data.message || '登录失败,请检查输入信息';
console.error('登录失败:', response.data);
}
} catch (err) {
loginError.value = '登录失败,请检查网络连接或登录地址';
console.error('登录失败:', err);
} finally {
loginLoading.value = false;
}
}
// 处理退出登录
function handleLogout() {
console.log('退出登录');
isLoggedIn.value = false;
// 重置用户名和账户余额
username.value = '用户名';
// 清除登录状态
localStorage.removeItem('token');
// 这里可以添加其他清理逻辑
// 1. 清除用户信息
// 2. 重置相关数据
// 3. 调用退出登录API可选
}
// 处理确认按钮点击
async function handleConfirm() {
console.log('确认按钮点击,止盈点:', input1.value, '止亏点:', input2.value);
// 验证输入
if (!input1.value || !input2.value) {
alert('请填写完整的止盈止亏点');
return;
}
try {
// 构建提交数据
const submitData = {
winNum: input1.value,
loseNum: input2.value
};
console.log('提交数据:', submitData);
// 调用后端API
const response = await axios.post('http://localhost:8080/api/ocr/saveUserInfo', submitData);
// 处理响应结果
if (response.data && response.code !== 500) {
console.log('提交成功:', response.data);
alert('设置保存成功');
} else {
console.error('提交失败:', response.data);
alert('设置保存失败: ' + (response.data.message || '未知错误'));
}
} catch (err) {
console.error('提交失败:', err);
alert('设置保存失败,请检查网络连接');
}
}
// 处理停止按钮点击
async function handleStop() {
console.log('停止按钮点击');
try {
// 调用后端停止API
const response = await axios.post('http://localhost:8080/api/ocr/saveUserInfo', {
onOff: 0
});
// 处理响应结果
if (response.data && response.code !== 500) {
console.log('停止成功:', response.data);
alert('停止成功');
} else {
console.error('停止失败:', response.data);
alert('停止失败: ' + (response.data.message || '未知错误'));
}
} catch (err) {
console.error('停止失败:', err);
alert('停止操作失败,请检查网络连接');
}
}
onUnmounted(() => {
// 销毁图表
chart1.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">{{ username }}</div>
</div>
<div class="account-actions">
<button type="button" class="login-button" @click="loginDialogVisible = true">账号信息</button>
</div>
</div>
<!-- 登录模态框 -->
<div v-if="loginDialogVisible" class="modal-overlay">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>登录</h3>
<button type="button" class="modal-close" @click="loginDialogVisible = false">×</button>
</div>
<div class="modal-body">
<div v-if="loginError" class="modal-error">
{{ loginError }}
</div>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="username">账号</label>
<input type="text" id="username" v-model="loginForm.username" placeholder="请输入账号" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" v-model="loginForm.password" placeholder="请输入密码" required>
</div>
<div class="form-group">
<label for="loginUrl">登录地址</label>
<input type="url" id="loginUrl" v-model="loginForm.loginUrl" placeholder="请输入登录地址" required>
</div>
<div class="form-actions">
<button type="button" class="cancel-button" @click="loginDialogVisible = false">取消</button>
<button type="submit" class="submit-button" :disabled="loginLoading">
<span v-if="loginLoading">保存中...</span>
<span v-else>保存</span>
</button>
</div>
</form>
</div>
</div>
</div>
<!-- 顶部输入框区域 -->
<div class="top-inputs">
<div class="input-group">
<div class="input-with-button">
<input type="text" id="input1" v-model="input1" placeholder="请输入止盈点">
</div>
</div>
<div class="input-group">
<div class="input-with-button">
<input type="text" id="input2" v-model="input2" placeholder="请输入止亏点">
<div class="button-group">
<button type="button" class="confirm-button" @click="handleConfirm">确认</button>
<button type="button" class="stop-button" @click="handleStop">停止</button>
</div>
</div>
</div>
</div>
<!-- 表格错误提示 -->
<div v-if="tableError" class="error-message">
{{ tableError }}
<button class="retry-button" @click="fetchTableData">重试</button>
</div>
<!-- 主体表格区域 -->
<div class="table-container">
<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.time }}</td>
<td>{{ item.result }}</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" style="width: 100%; height: 100%; border: 1px solid #ddd;"></div>
</div>
</div>
</div>
</template>
<style scoped>
.main-container {
display: flex;
gap: 20px;
width: 100%;
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
align-items: stretch;
}
.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;
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);
}
/* 按钮组 */
.button-group {
display: flex;
gap: 8px;
}
/* 停止按钮 */
.stop-button {
padding: 10px 16px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
white-space: nowrap;
}
.stop-button:hover {
background-color: #d32f2f;
}
.stop-button:active {
background-color: #b71c1c;
transform: translateY(1px);
}
/* 表格区域 */
.table-container {
flex: 1;
overflow: auto;
min-height: 0;
max-height: 600px;
}
.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;
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: 100%;
border-radius: 4px;
overflow: hidden;
}
/* 登录模态框 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
width: 90%;
max-width: 450px;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #e0e0e0;
}
.modal-header h3 {
margin: 0;
color: #333;
font-size: 18px;
font-weight: 600;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
color: #999;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s ease;
}
.modal-close:hover {
background-color: #f5f5f5;
color: #666;
}
.modal-body {
padding: 20px;
}
.modal-error {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #2196F3;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
.cancel-button {
padding: 10px 20px;
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.cancel-button:hover {
background-color: #e0e0e0;
}
.submit-button {
padding: 10px 20px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
}
.submit-button:hover:not(:disabled) {
background-color: #1976D2;
}
.submit-button:active:not(:disabled) {
background-color: #1565C0;
transform: translateY(1px);
}
.submit-button:disabled {
background-color: #bdbdbd;
cursor: not-allowed;
}
/* 响应式布局 */
@media (max-width: 768px) {
.main-container {
flex-direction: column;
}
.top-inputs {
flex-direction: column;
}
.right-section {
flex-direction: column;
}
.chart-container {
min-height: 300px;
}
.modal-content {
width: 95%;
margin: 20px;
}
}
</style>