Files
bocai/frontend/src/components/index.vue
2026-01-22 10:04:38 +08:00

706 lines
14 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 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>