2025-02-20 20:44:31 +08:00

437 lines
9.7 KiB
Vue

<script setup>
import { ref, reactive } from "vue";
import * as echarts from "echarts";
import { onMounted } from "vue";
const tableData = ref([
{
id: 1,
name: "东方白鹳",
type: "鸟类",
count: 12,
location: "A区湿地",
time: "2024-03-20 10:30",
},
{
id: 2,
name: "黑鹳",
type: "鸟类",
count: 8,
location: "B区湿地",
time: "2024-03-20 11:20",
},
]);
const initChart = () => {
const chartDom = document.getElementById("speciesChart");
if (!chartDom) return;
const myChart = echarts.init(chartDom);
const option = {
title: {
text: '物种分布统计',
textStyle: {
fontSize: 16,
fontWeight: 500,
color: '#303133'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#eee',
padding: [10, 15],
textStyle: {
color: '#666'
}
},
grid: [{
left: '8%',
right: '52%',
top: '15%',
bottom: '10%'
}],
xAxis: [{
type: 'category',
data: ['鸟类', '鱼类', '两栖类', '植物', '昆虫'],
axisLine: {
lineStyle: {
color: '#DCDFE6'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#909399',
interval: 0
},
gridIndex: 0
}],
yAxis: [{
type: 'value',
name: '数量',
nameTextStyle: {
color: '#909399',
padding: [0, 30, 0, 0]
},
splitLine: {
lineStyle: {
color: '#EBEEF5',
type: 'dashed'
}
},
axisLabel: {
color: '#909399'
},
gridIndex: 0
}],
series: [
{
name: '物种数量',
type: 'bar',
barWidth: '40%',
data: [
{
value: 52,
itemStyle: { color: '#409EFF' }
},
{
value: 38,
itemStyle: { color: '#67C23A' }
},
{
value: 32,
itemStyle: { color: '#E6A23C' }
},
{
value: 42,
itemStyle: { color: '#F56C6C' }
},
{
value: 28,
itemStyle: { color: '#909399' }
}
],
label: {
show: true,
position: 'top',
color: '#666'
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.2)'
}
},
xAxisIndex: 0,
yAxisIndex: 0
},
{
name: '分布占比',
type: 'pie',
radius: ['50%', '70%'],
center: ['76%', '50%'],
label: {
show: true,
position: 'outside',
formatter: function(params) {
return `${params.name}\n${params.percent.toFixed(1)}%`;
},
color: '#666',
fontWeight: 'bold',
fontSize: 13,
padding: [4, 8],
backgroundColor: 'rgba(255, 255, 255, 0.8)',
borderRadius: 4,
},
labelLine: {
length: 15,
length2: 15,
smooth: true
},
itemStyle: {
borderWidth: 2,
borderColor: '#fff'
},
animation: true,
animationDuration: 1000,
animationDurationUpdate: 500,
animationType: 'expansion',
animationEasing: 'cubicInOut',
animationDelay: function(idx) {
return idx * 100;
},
animationDelayUpdate: function(idx) {
return idx * 100;
},
emphasis: {
scale: true,
scaleSize: 10,
focus: 'self',
blurScope: 'coordinateSystem',
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.2)'
}
},
data: [
{ value: 52, name: '鸟类', itemStyle: { color: '#409EFF' } },
{ value: 38, name: '鱼类', itemStyle: { color: '#67C23A' } },
{ value: 32, name: '两栖类', itemStyle: { color: '#E6A23C' } },
{ value: 42, name: '植物', itemStyle: { color: '#F56C6C' } },
{ value: 28, name: '昆虫', itemStyle: { color: '#909399' } }
]
}
]
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
};
onMounted(() => {
initChart();
});
// 添加导出相关的数据和方法
const exportDialogVisible = ref(false);
const exportForm = reactive({
timeRange: [],
format: "excel",
});
const handleExport = () => {
exportDialogVisible.value = true;
};
const handleExportConfirm = () => {
console.log("导出数据:", exportForm);
exportDialogVisible.value = false;
};
</script>
<template>
<div class="species-container">
<!-- 统计卡片 -->
<el-row :gutter="20" class="mb-4">
<el-col :span="6" v-for="(item, index) in speciesStats" :key="index">
<el-card class="stats-card" shadow="hover">
<div class="stats-value">{{ item.value }}</div>
<div class="stats-label">{{ item.label }}</div>
<div class="stats-trend" :class="item.trend > 0 ? 'up' : 'down'">
{{ Math.abs(item.trend) }}%
<el-icon>
<component :is="item.trend > 0 ? 'ArrowUp' : 'ArrowDown'" />
</el-icon>
</div>
</el-card>
</el-col>
</el-row>
<!-- 图表区域 -->
<el-card class="chart-card" shadow="hover">
<div id="speciesChart" style="height: 400px;"></div>
</el-card>
<el-row :gutter="20" class="mt-20">
<el-col :span="24">
<el-card>
<template #header>
<div class="card-header">
<span>识别记录</span>
<div>
<el-button type="primary" @click="handleExport">导出数据</el-button>
</div>
</div>
</template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="物种名称" min-width="120" />
<el-table-column prop="type" label="类型" width="100" />
<el-table-column prop="count" label="数量" width="100" />
<el-table-column prop="location" label="位置" min-width="120" />
<el-table-column prop="time" label="识别时间" min-width="180" />
</el-table>
</el-card>
</el-col>
</el-row>
<!-- 添加导出弹窗 -->
<el-dialog v-model="exportDialogVisible" title="导出数据" width="500px">
<el-form :model="exportForm" label-width="80px">
<el-form-item label="时间范围">
<el-date-picker
v-model="exportForm.timeRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="导出格式">
<el-radio-group v-model="exportForm.format">
<el-radio label="excel">Excel</el-radio>
<el-radio label="csv">CSV</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleExportConfirm">确认导出</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
@use "./styles/variables" as v;
.species-container {
padding: 20px;
.stats-card {
text-align: center;
padding: 20px;
.stats-value {
font-size: 24px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.stats-label {
font-size: 14px;
color: #909399;
margin-bottom: 12px;
}
.stats-trend {
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
&.up {
color: #67C23A;
}
&.down {
color: #F56C6C;
}
}
}
.chart-card {
margin-top: 20px;
}
.mb-4 {
margin-bottom: 16px;
}
.el-card {
background: #ffffff;
border: none;
border-radius: 8px;
transition: all 0.3s;
box-shadow: $box-shadow;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.08);
}
.el-card__header {
padding: 16px 20px;
border-bottom: 1px solid $border-color;
}
}
.el-table {
:deep(tbody tr) {
transition: all 0.3s;
&:hover {
background-color: rgba(64, 158, 255, 0.1) !important;
}
}
}
:deep(.el-button) {
transition: all 0.3s;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 16px;
font-weight: 500;
color: $text-primary;
position: relative;
padding-left: 12px;
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: $primary-color;
border-radius: 2px;
}
}
}
.mt-20 {
margin-top: 20px;
}
:deep(.el-table) {
th.el-table__cell {
background-color: #fafafa;
color: $text-primary;
font-weight: 500;
}
.el-table__cell {
padding: 12px 0;
}
}
.dialog-footer {
.el-button {
margin-left: 12px;
}
}
.el-card__body {
padding: 20px;
}
}
</style>