437 lines
9.7 KiB
Vue
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>
|