1059 lines
23 KiB
Vue
1059 lines
23 KiB
Vue
<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> |