Browse Source

修改返回首页跳转若依问题,优化3个可视化大屏,登录页优化

main
ZhaoYang 3 days ago
parent
commit
e11f3af894
  1. 634
      chenhai-ui/src/components/visual/jkjc.vue
  2. 446
      chenhai-ui/src/components/visual/sbgl.vue
  3. 561
      chenhai-ui/src/components/visual/scgk.vue
  4. 21
      chenhai-ui/src/router/index.js
  5. 53
      chenhai-ui/src/views/Home.vue
  6. 522
      chenhai-ui/src/views/login.vue

634
chenhai-ui/src/components/visual/jkjc.vue

@ -1,24 +1,26 @@
<template> <template>
<div class="health-dashboard"> <div class="health-dashboard">
<!-- 主内容区域 -->
<!-- 主内容区域Grid三列布局 -->
<div class="dashboard-main"> <div class="dashboard-main">
<!-- 左侧驼圈列表 -->
<!-- 左侧驼圈列表 + 健康统计总览 + 健康预警 -->
<div class="col-left"> <div class="col-left">
<div class="card pen-list-card"> <div class="card pen-list-card">
<div class="card-header"> <div class="card-header">
<span class="title">🐫 驼圈列表</span> <span class="title">🐫 驼圈列表</span>
<span class="sub"> {{ pens.length }} 个圈舍</span> <span class="sub"> {{ pens.length }} 个圈舍</span>
</div> </div>
<div class="pen-grid">
<div
v-for="pen in pens"
:key="pen.name"
class="pen-item"
:class="{ active: selectedPen === pen.name }"
@click="selectPen(pen.name)"
>
<div class="pen-name">{{ pen.name }}</div>
<div class="pen-count">{{ pen.count }}</div>
<div class="pen-grid-wrapper">
<div class="pen-grid">
<div
v-for="pen in pens"
:key="pen.name"
class="pen-item"
:class="{ active: selectedPen === pen.name }"
@click="selectPen(pen.name)"
>
<div class="pen-name">{{ pen.name }}</div>
<div class="pen-count">{{ pen.count }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -66,20 +68,22 @@
<span class="title"> 健康预警</span> <span class="title"> 健康预警</span>
<span class="badge">{{ healthAlerts.length }}</span> <span class="badge">{{ healthAlerts.length }}</span>
</div> </div>
<div class="alert-list">
<div v-for="(alert, idx) in healthAlerts" :key="idx" class="alert-item">
<span class="alert-level" :class="alert.level">{{ alert.levelText }}</span>
<div class="alert-content">
<div class="alert-title">{{ alert.title }}</div>
<div class="alert-desc">{{ alert.desc }}</div>
<div class="alert-list-wrapper">
<div class="alert-list">
<div v-for="(alert, idx) in healthAlerts" :key="idx" class="alert-item">
<span class="alert-level" :class="alert.level">{{ alert.levelText }}</span>
<div class="alert-content">
<div class="alert-title">{{ alert.title }}</div>
<div class="alert-desc">{{ alert.desc }}</div>
</div>
<span class="alert-time">{{ alert.time }}</span>
</div> </div>
<span class="alert-time">{{ alert.time }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 中间圈详情 + 健康指标趋势 -->
<!-- 中间详情 + 健康指标趋势 + 个体骆驼健康数据 -->
<div class="col-center"> <div class="col-center">
<div class="card pen-detail-card"> <div class="card pen-detail-card">
<div class="card-header"> <div class="card-header">
@ -142,31 +146,33 @@
class="search-input" class="search-input"
/> />
</div> </div>
<div class="camel-grid">
<div
v-for="camel in filteredCamels"
:key="camel.id"
class="camel-item"
:class="{ warning: camel.healthStatus !== 'healthy' }"
@click="selectCamel(camel)"
>
<div class="camel-avatar">
<span class="camel-icon">🐪</span>
<span v-if="camel.healthStatus !== 'healthy'" class="warning-dot"></span>
</div>
<div class="camel-info">
<div class="camel-id">{{ camel.id }}</div>
<div class="camel-temp">{{ camel.temperature }}°C</div>
</div>
<div class="camel-status" :class="camel.healthStatus">
{{ getHealthStatusText(camel.healthStatus) }}
<div class="camel-grid-wrapper">
<div class="camel-grid">
<div
v-for="camel in filteredCamels"
:key="camel.id"
class="camel-item"
:class="{ warning: camel.healthStatus !== 'healthy' }"
@click="selectCamel(camel)"
>
<div class="camel-avatar">
<span class="camel-icon">🐪</span>
<span v-if="camel.healthStatus !== 'healthy'" class="warning-dot"></span>
</div>
<div class="camel-info">
<div class="camel-id">{{ camel.id }}</div>
<div class="camel-temp">{{ camel.temperature }}°C</div>
</div>
<div class="camel-status" :class="camel.healthStatus">
{{ getHealthStatusText(camel.healthStatus) }}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 右侧个体骆驼详细健康报告 -->
<!-- 右侧个体骆驼详细健康报告 + 健康建议 -->
<div class="col-right"> <div class="col-right">
<div class="card health-report-card"> <div class="card health-report-card">
<div class="card-header"> <div class="card-header">
@ -225,7 +231,7 @@
</div> </div>
</div> </div>
<div class="report-timeline">
<div class="report-timeline compact-timeline">
<div class="timeline-title">📅 近期健康记录</div> <div class="timeline-title">📅 近期健康记录</div>
<div class="timeline-list"> <div class="timeline-list">
<div v-for="record in selectedCamel.healthRecords" :key="record.date" class="timeline-item"> <div v-for="record in selectedCamel.healthRecords" :key="record.date" class="timeline-item">
@ -244,7 +250,6 @@
</div> </div>
</div> </div>
<!-- 健康建议 -->
<div class="card advice-card"> <div class="card advice-card">
<div class="card-header"> <div class="card-header">
<span class="title">💡 健康管理建议</span> <span class="title">💡 健康管理建议</span>
@ -266,14 +271,12 @@
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
export default { export default {
name: 'HealthDashboard', name: 'HealthDashboard',
data() { data() {
return { return {
// (Excel)
pens: [ pens: [
{ name: 'A1圈', count: 35 }, { name: 'A2圈', count: 35 }, { name: 'A1圈', count: 35 }, { name: 'A2圈', count: 35 },
{ name: 'B1圈', count: 35 }, { name: 'B2圈', count: 35 }, { name: 'B1圈', count: 35 }, { name: 'B2圈', count: 35 },
@ -288,7 +291,6 @@ export default {
], ],
selectedPen: 'A1圈', selectedPen: 'A1圈',
//
healthStats: { healthStats: {
totalCamels: 700, totalCamels: 700,
healthyRate: 94.2, healthyRate: 94.2,
@ -296,7 +298,6 @@ export default {
warningCount: 42 warningCount: 42
}, },
//
healthAlerts: [ healthAlerts: [
{ level: 'high', levelText: '紧急', title: 'A1圈骆驼体温异常', desc: '编号A1-023体温39.2°C', time: '09:32' }, { level: 'high', levelText: '紧急', title: 'A1圈骆驼体温异常', desc: '编号A1-023体温39.2°C', time: '09:32' },
{ level: 'medium', levelText: '警告', title: 'B2圈空气质量下降', desc: '氨气浓度超标', time: '09:15' }, { level: 'medium', levelText: '警告', title: 'B2圈空气质量下降', desc: '氨气浓度超标', time: '09:15' },
@ -304,7 +305,6 @@ export default {
{ level: 'medium', levelText: '警告', title: 'E2圈饮水异常', desc: '2峰骆驼饮水次数减少', time: '07:22' } { level: 'medium', levelText: '警告', title: 'E2圈饮水异常', desc: '2峰骆驼饮水次数减少', time: '07:22' }
], ],
//
penHealthData: { penHealthData: {
avgTemp: 37.5, avgTemp: 37.5,
airQuality: 68, airQuality: 68,
@ -312,7 +312,6 @@ export default {
ventilation: 3 ventilation: 3
}, },
//
chartMetrics: [ chartMetrics: [
{ key: 'temperature', name: '体温' }, { key: 'temperature', name: '体温' },
{ key: 'activity', name: '活动量' }, { key: 'activity', name: '活动量' },
@ -320,19 +319,16 @@ export default {
], ],
activeMetric: 'temperature', activeMetric: 'temperature',
// (35)
camels: [], camels: [],
camelSearch: '', camelSearch: '',
selectedCamel: null, selectedCamel: null,
//
trendData: { trendData: {
temperature: [37.2, 37.3, 37.4, 37.5, 37.6, 37.5, 37.4], temperature: [37.2, 37.3, 37.4, 37.5, 37.6, 37.5, 37.4],
activity: [5200, 5400, 5600, 5800, 5900, 6100, 6000], activity: [5200, 5400, 5600, 5800, 5900, 6100, 6000],
healthRate: [92.5, 93.1, 93.6, 93.8, 94.0, 94.1, 94.2] healthRate: [92.5, 93.1, 93.6, 93.8, 94.0, 94.1, 94.2]
}, },
//
healthAdvice: [ healthAdvice: [
{ icon: '🌡️', title: '体温监控', desc: 'A1圈有2峰骆驼体温偏高,建议隔离观察' }, { icon: '🌡️', title: '体温监控', desc: 'A1圈有2峰骆驼体温偏高,建议隔离观察' },
{ icon: '💨', title: '通风改善', desc: 'B2圈空气质量下降,建议开启通风设备' }, { icon: '💨', title: '通风改善', desc: 'B2圈空气质量下降,建议开启通风设备' },
@ -340,11 +336,9 @@ export default {
{ icon: '🏃', title: '活动促进', desc: 'C1圈活动量偏低,建议增加驱赶活动' } { icon: '🏃', title: '活动促进', desc: 'C1圈活动量偏低,建议增加驱赶活动' }
], ],
// ECharts
healthChartIns: null, healthChartIns: null,
//
timeLabels: []
timeLabels: [],
resizeObserver: null
}; };
}, },
@ -361,31 +355,20 @@ export default {
}, },
mounted() { mounted() {
this.getCurrentDateTime();
this.initTimeLabels(); this.initTimeLabels();
this.initCamelData(); this.initCamelData();
this.initHealthChart(); this.initHealthChart();
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
this.observeChartResize();
}, },
beforeDestroy() { beforeDestroy() {
if (this.healthChartIns) this.healthChartIns.dispose(); if (this.healthChartIns) this.healthChartIns.dispose();
if (this.resizeObserver) this.resizeObserver.disconnect();
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
}, },
methods: { methods: {
getCurrentDateTime() {
const dateDom = document.querySelector('#cloudDate');
const timeDom = document.querySelector('#cloudTime');
const update = () => {
const now = dayjs().locale('zh-cn');
if (dateDom) dateDom.innerHTML = now.format('MM月DD日 dddd');
if (timeDom) timeDom.innerHTML = now.format('HH:mm:ss');
};
update();
setInterval(update, 1000);
},
initTimeLabels() { initTimeLabels() {
for (let i = 6; i >= 0; i--) { for (let i = 6; i >= 0; i--) {
this.timeLabels.push(dayjs().subtract(i, 'day').format('MM/DD')); this.timeLabels.push(dayjs().subtract(i, 'day').format('MM/DD'));
@ -393,7 +376,6 @@ export default {
}, },
initCamelData() { initCamelData() {
// 35
const allCamels = []; const allCamels = [];
const statuses = ['healthy', 'warning', 'critical']; const statuses = ['healthy', 'warning', 'critical'];
const statusWeights = [0.85, 0.12, 0.03]; const statusWeights = [0.85, 0.12, 0.03];
@ -430,8 +412,8 @@ export default {
generateHealthRecords(status) { generateHealthRecords(status) {
const records = []; const records = [];
const dates = [0, 1, 2, 3, 4, 5, 6];
for (let i = 0; i < 5; i++) {
const dates = [0, 1, 2, 3];
for (let i = 0; i < 4; i++) {
const date = dayjs().subtract(dates[i], 'day').format('MM/DD'); const date = dayjs().subtract(dates[i], 'day').format('MM/DD');
if (status === 'healthy') { if (status === 'healthy') {
records.push({ records.push({
@ -459,7 +441,6 @@ export default {
selectPen(penName) { selectPen(penName) {
this.selectedPen = penName; this.selectedPen = penName;
this.selectedCamel = null; this.selectedCamel = null;
// ()
this.penHealthData = { this.penHealthData = {
avgTemp: +(37.2 + Math.random() * 0.8).toFixed(1), avgTemp: +(37.2 + Math.random() * 0.8).toFixed(1),
airQuality: Math.floor(40 + Math.random() * 50), airQuality: Math.floor(40 + Math.random() * 50),
@ -478,11 +459,7 @@ export default {
}, },
getHealthStatusText(status) { getHealthStatusText(status) {
const map = {
healthy: '健康',
warning: '亚健康',
critical: '异常'
};
const map = { healthy: '健康', warning: '亚健康', critical: '异常' };
return map[status] || '未知'; return map[status] || '未知';
}, },
@ -529,22 +506,15 @@ export default {
initHealthChart() { initHealthChart() {
const chartDom = this.$refs.healthChart; const chartDom = this.$refs.healthChart;
if (!chartDom) return;
this.healthChartIns = echarts.init(chartDom); this.healthChartIns = echarts.init(chartDom);
this.updateHealthChart(); this.updateHealthChart();
}, },
updateHealthChart() { updateHealthChart() {
const currentData = this.trendData[this.activeMetric]; const currentData = this.trendData[this.activeMetric];
const unitMap = {
temperature: '°C',
activity: '步',
healthRate: '%'
};
const nameMap = {
temperature: '平均体温',
activity: '平均活动量',
healthRate: '健康率'
};
const unitMap = { temperature: '°C', activity: '步', healthRate: '%' };
const nameMap = { temperature: '平均体温', activity: '平均活动量', healthRate: '健康率' };
const yAxisMin = this.activeMetric === 'healthRate' ? 90 : null; const yAxisMin = this.activeMetric === 'healthRate' ? 90 : null;
this.healthChartIns.setOption({ this.healthChartIns.setOption({
@ -590,7 +560,16 @@ export default {
this.updateHealthChart(); this.updateHealthChart();
}, },
observeChartResize() {
const chartEl = this.$refs.healthChart;
if (!chartEl) return;
this.resizeObserver = new ResizeObserver(() => {
setTimeout(() => {
if (this.healthChartIns) this.healthChartIns.resize();
}, 100);
});
this.resizeObserver.observe(chartEl);
},
handleResize() { handleResize() {
if (this.healthChartIns) this.healthChartIns.resize(); if (this.healthChartIns) this.healthChartIns.resize();
@ -602,56 +581,65 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.health-dashboard { .health-dashboard {
width: 100%; width: 100%;
height: calc(100vh - 100px);
height: calc(100vh - 120px);
background: transparent; background: transparent;
padding: 20px 24px 30px;
padding: 20px 20px 0;
box-sizing: border-box; box-sizing: border-box;
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif; font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif;
overflow-y: auto;
overflow-x: auto;
overflow: hidden;
} }
.dashboard-main { .dashboard-main {
display: flex;
display: grid;
grid-template-columns: 1.2fr 2fr 1.2fr;
gap: 20px; gap: 20px;
flex-wrap: wrap;
align-items: stretch;
.col-left, .col-right {
flex: 1.1;
min-width: 280px;
display: flex;
flex-direction: column;
gap: 20px;
}
.col-center {
flex: 1.8;
min-width: 420px;
display: flex;
flex-direction: column;
gap: 20px;
}
height: 100%;
min-height: 0;
}
.col-left {
display: grid;
grid-template-rows: 1fr auto 1fr;
gap: 20px;
min-height: 0;
overflow: hidden;
}
.col-center {
display: grid;
grid-template-rows: auto 0.8fr 1fr;
gap: 20px;
min-height: 0;
overflow: hidden;
}
.col-right {
display: grid;
grid-template-rows: 1fr auto;
gap: 20px;
min-height: 0;
overflow: hidden;
} }
.card { .card {
background: transparent;
backdrop-filter: blur(0);
background: rgba(8, 28, 45, 0.55);
backdrop-filter: blur(3px);
border-radius: 16px; border-radius: 16px;
border: 1.5px solid rgba(52, 152, 219, 0.7); border: 1.5px solid rgba(52, 152, 219, 0.7);
overflow: hidden;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0;
overflow: hidden;
&:hover { &:hover {
border-color: #5dade2; border-color: #5dade2;
box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25); box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25);
background: rgba(52, 152, 219, 0.05);
background: rgba(52, 152, 219, 0.08);
} }
.card-header { .card-header {
padding: 14px 20px;
padding: 12px 16px;
border-bottom: 1px solid rgba(52, 152, 219, 0.4); border-bottom: 1px solid rgba(52, 152, 219, 0.4);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -659,9 +647,9 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
flex-shrink: 0; flex-shrink: 0;
.title { .title {
font-size: 16px;
font-size: 15px;
font-weight: 600; font-weight: 600;
color: #fff; color: #fff;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@ -700,8 +688,13 @@ export default {
} }
} }
//
// -
.pen-list-card { .pen-list-card {
.pen-grid-wrapper {
flex: 1;
overflow-y: auto;
min-height: 0;
}
.pen-grid { .pen-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@ -736,19 +729,20 @@ export default {
// //
.stats-card { .stats-card {
flex-shrink: 0;
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 16px; gap: 16px;
padding: 20px;
padding: 16px;
.stat-item { .stat-item {
text-align: center; text-align: center;
.stat-value { .stat-value {
font-size: 28px;
font-size: 24px;
font-weight: bold; font-weight: bold;
color: #5dade2; color: #5dade2;
.unit { .unit {
font-size: 12px;
font-size: 11px;
margin-left: 2px; margin-left: 2px;
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
@ -767,57 +761,49 @@ export default {
} }
} }
//
// -
.alert-card { .alert-card {
flex: 1;
display: flex;
flex-direction: column;
.alert-list {
padding: 8px 16px;
.alert-list-wrapper {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
min-height: 0;
}
.alert-list {
padding: 12px 16px;
.alert-item { .alert-item {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 12px; gap: 12px;
border-bottom: 1px solid rgba(52, 152, 219, 0.2); border-bottom: 1px solid rgba(52, 152, 219, 0.2);
padding: 12px 4px;
padding: 14px 4px;
.alert-level { .alert-level {
font-size: 10px;
padding: 2px 8px;
font-size: 13px;
padding: 4px 12px;
border-radius: 20px; border-radius: 20px;
min-width: 40px;
min-width: 48px;
text-align: center; text-align: center;
&.high {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
}
&.medium {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.low {
background: rgba(52, 152, 219, 0.2);
color: #3498db;
}
font-weight: 500;
&.high { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
&.medium { background: rgba(243, 156, 18, 0.2); color: #f39c12; }
&.low { background: rgba(52, 152, 219, 0.2); color: #3498db; }
} }
.alert-content { .alert-content {
flex: 1; flex: 1;
.alert-title {
font-size: 13px;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
.alert-title {
font-size: 14px;
font-weight: 600;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 4px;
} }
.alert-desc {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
margin-top: 2px;
.alert-desc {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
line-height: 1.4;
} }
} }
.alert-time {
font-size: 10px;
color: rgba(255, 255, 255, 0.4);
.alert-time {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
} }
} }
} }
@ -825,53 +811,35 @@ export default {
// //
.pen-detail-card { .pen-detail-card {
flex-shrink: 0;
.pen-detail { .pen-detail {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 16px; gap: 16px;
padding: 20px;
padding: 16px;
.detail-item { .detail-item {
text-align: center; text-align: center;
.detail-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 6px;
}
.detail-value {
font-size: 22px;
font-weight: bold;
color: #5dade2;
.unit {
font-size: 11px;
margin-left: 2px;
color: rgba(255, 255, 255, 0.5);
}
}
.detail-status {
font-size: 10px;
margin-top: 4px;
&.normal { color: #2ecc71; }
&.warning { color: #f39c12; }
&.danger { color: #e74c3c; }
}
.detail-label { font-size: 11px; color: rgba(255, 255, 255, 0.7); margin-bottom: 6px; }
.detail-value { font-size: 20px; font-weight: bold; color: #5dade2; .unit { font-size: 10px; } }
.detail-status { font-size: 10px; margin-top: 4px; &.normal { color: #2ecc71; } &.warning { color: #f39c12; } &.danger { color: #e74c3c; } }
} }
} }
} }
// //
.chart-card {
flex-shrink: 0;
}
.chart-container { .chart-container {
width: 100%; width: 100%;
height: 240px;
padding: 8px;
height: 100%;
min-height: 0;
} }
// //
.camel-list-card { .camel-list-card {
display: flex;
flex-direction: column;
.camel-search { .camel-search {
padding: 12px 16px;
padding: 10px 16px;
flex-shrink: 0; flex-shrink: 0;
.search-input { .search-input {
width: 100%; width: 100%;
@ -882,22 +850,20 @@ export default {
color: white; color: white;
font-size: 12px; font-size: 12px;
outline: none; outline: none;
&:focus {
border-color: #5dade2;
}
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
&:focus { border-color: #5dade2; }
&::placeholder { color: rgba(255, 255, 255, 0.4); }
} }
} }
.camel-grid-wrapper {
flex: 1;
overflow-y: auto;
min-height: 0;
}
.camel-grid { .camel-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
padding: 16px; padding: 16px;
flex: 1;
overflow-y: auto;
max-height: 280px;
.camel-item { .camel-item {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(52, 152, 219, 0.4); border: 1px solid rgba(52, 152, 219, 0.4);
@ -908,247 +874,99 @@ export default {
gap: 10px; gap: 10px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
&:hover {
border-color: #5dade2;
background: rgba(52, 152, 219, 0.1);
}
&.warning {
border-color: rgba(243, 156, 18, 0.6);
}
.camel-avatar {
position: relative;
.camel-icon {
font-size: 28px;
}
.warning-dot {
position: absolute;
top: -2px;
right: -2px;
width: 10px;
height: 10px;
background: #f39c12;
border-radius: 50%;
animation: pulse 1.5s infinite;
}
}
.camel-info {
flex: 1;
.camel-id {
font-size: 12px;
font-weight: 500;
color: #fff;
}
.camel-temp {
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
}
}
.camel-status {
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
&.healthy {
background: rgba(46, 204, 113, 0.2);
color: #2ecc71;
}
&.warning {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.critical {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
}
}
&:hover { border-color: #5dade2; background: rgba(52, 152, 219, 0.1); }
&.warning { border-color: rgba(243, 156, 18, 0.6); }
.camel-avatar { position: relative; .camel-icon { font-size: 28px; } .warning-dot { position: absolute; top: -2px; right: -2px; width: 10px; height: 10px; background: #f39c12; border-radius: 50%; animation: pulse 1.5s infinite; } }
.camel-info { flex: 1; .camel-id { font-size: 12px; font-weight: 500; color: #fff; } .camel-temp { font-size: 10px; color: rgba(255, 255, 255, 0.6); } }
.camel-status { font-size: 10px; padding: 2px 6px; border-radius: 10px; &.healthy { background: rgba(46, 204, 113, 0.2); color: #2ecc71; } &.warning { background: rgba(243, 156, 18, 0.2); color: #f39c12; } &.critical { background: rgba(231, 76, 60, 0.2); color: #e74c3c; } }
} }
} }
} }
//
// -
.health-report-card { .health-report-card {
flex: 1;
display: flex;
flex-direction: column;
.health-report { .health-report {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0;
.report-header { .report-header {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
padding: 20px;
padding: 16px;
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
border-bottom: 1px solid rgba(52, 152, 219, 0.3); border-bottom: 1px solid rgba(52, 152, 219, 0.3);
flex-shrink: 0; flex-shrink: 0;
.camel-avatar-large {
font-size: 48px;
}
.camel-basic {
flex: 1;
.camel-name {
font-size: 18px;
font-weight: bold;
color: #fff;
}
.camel-location {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
margin-top: 4px;
}
}
.health-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
&.healthy {
background: rgba(46, 204, 113, 0.2);
color: #2ecc71;
}
&.warning {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.critical {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
}
}
.camel-avatar-large { font-size: 48px; }
.camel-basic { flex: 1; .camel-name { font-size: 16px; font-weight: bold; color: #fff; } .camel-location { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 4px; } }
.health-badge { padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 500; &.healthy { background: rgba(46, 204, 113, 0.2); color: #2ecc71; } &.warning { background: rgba(243, 156, 18, 0.2); color: #f39c12; } &.critical { background: rgba(231, 76, 60, 0.2); color: #e74c3c; } }
} }
.report-metrics { .report-metrics {
padding: 16px; padding: 16px;
flex-shrink: 0; flex-shrink: 0;
.metric-row {
display: flex;
gap: 20px;
margin-bottom: 16px;
.metric {
flex: 1;
.metric-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 4px;
}
.metric-value {
font-size: 20px;
font-weight: bold;
color: #5dade2;
&.warning {
color: #e74c3c;
}
.unit {
font-size: 11px;
margin-left: 2px;
color: rgba(255, 255, 255, 0.5);
}
}
.metric-range {
font-size: 10px;
color: rgba(255, 255, 255, 0.4);
margin-top: 4px;
}
.metric-progress {
margin-top: 8px;
background: rgba(52, 152, 219, 0.3);
border-radius: 10px;
height: 6px;
.progress-bar {
height: 6px;
background: #5dade2;
border-radius: 10px;
}
}
}
}
.metric-row { display: flex; gap: 16px; margin-bottom: 16px; .metric { flex: 1; .metric-label { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-bottom: 4px; } .metric-value { font-size: 18px; font-weight: bold; color: #5dade2; &.warning { color: #e74c3c; } .unit { font-size: 10px; } } .metric-range { font-size: 9px; color: rgba(255, 255, 255, 0.4); margin-top: 4px; } .metric-progress { margin-top: 8px; background: rgba(52, 152, 219, 0.3); border-radius: 10px; height: 6px; .progress-bar { height: 6px; background: #5dade2; border-radius: 10px; } } } }
} }
.report-timeline {
padding: 16px;
border-top: 1px solid rgba(52, 152, 219, 0.3);
.report-timeline.compact-timeline {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
.timeline-title {
font-size: 13px;
font-weight: 500;
color: #fff;
margin-bottom: 12px;
}
.timeline-list {
.timeline-item {
display: flex;
gap: 12px;
margin-bottom: 12px;
.timeline-date {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
min-width: 50px;
padding: 16px;
border-top: 1px solid rgba(52, 152, 219, 0.3);
.timeline-title {
font-size: 14px;
font-weight: 600;
color: #fff;
margin-bottom: 12px;
}
.timeline-list .timeline-item {
display: flex;
gap: 16px;
margin-bottom: 14px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(52, 152, 219, 0.15);
&:last-child { border-bottom: none; }
.timeline-date {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
min-width: 55px;
font-weight: 500;
}
.timeline-content {
flex: 1;
.timeline-event {
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 4px;
} }
.timeline-content {
flex: 1;
.timeline-event {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
.timeline-detail {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
}
.timeline-detail {
font-size: 12px;
color: rgba(255, 255, 255, 0.65);
line-height: 1.4;
} }
} }
} }
} }
} }
.no-selection { .no-selection {
text-align: center;
padding: 60px 20px;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.placeholder-icon {
font-size: 64px;
opacity: 0.5;
display: block;
margin-bottom: 16px;
}
p {
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
}
text-align: center;
padding: 40px 20px;
.placeholder-icon { font-size: 64px; opacity: 0.5; margin-bottom: 16px; }
p { color: rgba(255, 255, 255, 0.5); font-size: 13px; }
} }
} }
// //
.advice-card { .advice-card {
flex-shrink: 0;
.advice-list { .advice-list {
padding: 16px; padding: 16px;
.advice-item {
display: flex;
gap: 12px;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
.advice-icon {
font-size: 20px;
}
.advice-content {
flex: 1;
.advice-title {
font-size: 13px;
font-weight: 500;
color: #fff;
}
.advice-desc {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
margin-top: 2px;
}
}
}
.advice-item { display: flex; gap: 12px; margin-bottom: 16px; &:last-child { margin-bottom: 0; } .advice-icon { font-size: 20px; } .advice-content { flex: 1; .advice-title { font-size: 13px; font-weight: 500; color: #fff; } .advice-desc { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 2px; } } }
} }
} }
@ -1156,4 +974,26 @@ export default {
0%, 100% { opacity: 1; transform: scale(1); } 0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); } 50% { opacity: 0.5; transform: scale(1.2); }
} }
//
.pen-grid-wrapper::-webkit-scrollbar,
.alert-list-wrapper::-webkit-scrollbar,
.camel-grid-wrapper::-webkit-scrollbar,
.report-timeline.compact-timeline::-webkit-scrollbar {
width: 4px;
}
.pen-grid-wrapper::-webkit-scrollbar-track,
.alert-list-wrapper::-webkit-scrollbar-track,
.camel-grid-wrapper::-webkit-scrollbar-track,
.report-timeline.compact-timeline::-webkit-scrollbar-track {
background: rgba(52, 152, 219, 0.2);
border-radius: 2px;
}
.pen-grid-wrapper::-webkit-scrollbar-thumb,
.alert-list-wrapper::-webkit-scrollbar-thumb,
.camel-grid-wrapper::-webkit-scrollbar-thumb,
.report-timeline.compact-timeline::-webkit-scrollbar-thumb {
background: #5dade2;
border-radius: 2px;
}
</style> </style>

446
chenhai-ui/src/components/visual/sbgl.vue

@ -1,10 +1,10 @@
<template> <template>
<div class="device-dashboard"> <div class="device-dashboard">
<!-- 主内容区域 - 使用更规整的网格布局 -->
<!-- 主内容区域 - Grid三列布局 -->
<div class="dashboard-main"> <div class="dashboard-main">
<!-- 左侧列设备清单与告警 --> <!-- 左侧列设备清单与告警 -->
<div class="col-left"> <div class="col-left">
<!-- 设备清单卡片移至左侧第一项 -->
<!-- 设备清单卡片 -->
<div class="card device-list-card"> <div class="card device-list-card">
<div class="card-header"> <div class="card-header">
<span class="title"> 设备清单</span> <span class="title"> 设备清单</span>
@ -13,27 +13,29 @@
<div class="device-search"> <div class="device-search">
<input type="text" v-model="deviceSearch" placeholder="搜索设备..." class="search-input" /> <input type="text" v-model="deviceSearch" placeholder="搜索设备..." class="search-input" />
</div> </div>
<div class="device-list">
<div
v-for="device in filteredDevices"
:key="device.id"
class="device-item"
:class="{ active: selectedDevice && selectedDevice.id === device.id, warning: device.status === 'warning', offline: device.status === 'offline' }"
@click="selectDevice(device)"
>
<div class="device-icon">{{ device.icon }}</div>
<div class="device-info">
<div class="device-name">{{ device.name }}</div>
<div class="device-loc">{{ device.location }}</div>
</div>
<div class="device-status-badge" :class="device.status">
{{ device.statusText }}
<div class="device-list-wrapper">
<div class="device-list">
<div
v-for="device in filteredDevices"
:key="device.id"
class="device-item"
:class="{ active: selectedDevice && selectedDevice.id === device.id, warning: device.status === 'warning', offline: device.status === 'offline' }"
@click="selectDevice(device)"
>
<div class="device-icon">{{ device.icon }}</div>
<div class="device-info">
<div class="device-name">{{ device.name }}</div>
<div class="device-loc">{{ device.location }}</div>
</div>
<div class="device-status-badge" :class="device.status">
{{ device.statusText }}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 设备统计卡片移至左侧第二项 -->
<!-- 设备统计卡片 -->
<div class="card device-stats-card"> <div class="card device-stats-card">
<div class="card-header"> <div class="card-header">
<span class="title">📊 设备运行总览</span> <span class="title">📊 设备运行总览</span>
@ -59,24 +61,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 告警列表卡片 -->
<div class="card alert-card">
<div class="card-header">
<span class="title"> 设备告警</span>
<span class="badge">{{ deviceAlerts.length }}</span>
</div>
<div class="alert-list">
<div v-for="(alert, idx) in deviceAlerts" :key="idx" class="alert-item">
<span class="alert-level" :class="alert.level">{{ alert.levelText }}</span>
<div class="alert-content">
<div class="alert-title">{{ alert.title }}</div>
<div class="alert-desc">{{ alert.desc }}</div>
</div>
<span class="alert-time">{{ alert.time }}</span>
</div>
</div>
</div>
</div> </div>
<!-- 中间列监控画面与缩略图 --> <!-- 中间列监控画面与缩略图 -->
@ -125,14 +109,13 @@
<!-- 右侧列骆驼档案与统计信息 --> <!-- 右侧列骆驼档案与统计信息 -->
<div class="col-right"> <div class="col-right">
<!-- 骆驼档案数据卡片 - 表头固定内容滚动修复对齐问题 -->
<!-- 骆驼档案数据卡片 -->
<div class="card camel-archive-card"> <div class="card camel-archive-card">
<div class="card-header"> <div class="card-header">
<span class="title">🐪 骆驼档案数据</span> <span class="title">🐪 骆驼档案数据</span>
<span class="sub"> {{ camelData.length }} 条记录</span> <span class="sub"> {{ camelData.length }} 条记录</span>
</div> </div>
<div class="camel-table-wrapper"> <div class="camel-table-wrapper">
<!-- 独立表头确保与滚动内容对齐 -->
<table class="camel-table camel-table-header"> <table class="camel-table camel-table-header">
<thead> <thead>
<tr> <tr>
@ -254,18 +237,12 @@
</template> </template>
<script> <script>
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import vueSeamlessScroll from 'vue-seamless-scroll';
export default { export default {
name: 'DeviceDashboard', name: 'DeviceDashboard',
components: {
vueSeamlessScroll
},
data() { data() {
return { return {
//
deviceStats: { deviceStats: {
total: 48, total: 48,
online: 42, online: 42,
@ -274,7 +251,6 @@ export default {
maintenance: 5 maintenance: 5
}, },
//
devices: [ devices: [
{ id: 'CAM-01', icon: '📹', name: '高清摄像头', location: 'A1圈', status: 'online', statusText: '在线', lastInspection: '2024-01-15', nextMaintenance: '2024-02-15', runtime: 3240 }, { id: 'CAM-01', icon: '📹', name: '高清摄像头', location: 'A1圈', status: 'online', statusText: '在线', lastInspection: '2024-01-15', nextMaintenance: '2024-02-15', runtime: 3240 },
{ id: 'CAM-02', icon: '📹', name: '高清摄像头', location: 'A2圈', status: 'online', statusText: '在线', lastInspection: '2024-01-15', nextMaintenance: '2024-02-15', runtime: 3120 }, { id: 'CAM-02', icon: '📹', name: '高清摄像头', location: 'A2圈', status: 'online', statusText: '在线', lastInspection: '2024-01-15', nextMaintenance: '2024-02-15', runtime: 3120 },
@ -292,7 +268,6 @@ export default {
selectedDevice: null, selectedDevice: null,
showDeviceModal: false, showDeviceModal: false,
//
deviceAlerts: [ deviceAlerts: [
{ level: 'high', levelText: '紧急', title: '温湿度传感器异常', desc: 'A1圈温度传感器信号丢失', time: '09:32' }, { level: 'high', levelText: '紧急', title: '温湿度传感器异常', desc: 'A1圈温度传感器信号丢失', time: '09:32' },
{ level: 'medium', levelText: '警告', title: '挤奶监测仪数据异常', desc: '数据波动超出正常范围', time: '08:15' }, { level: 'medium', levelText: '警告', title: '挤奶监测仪数据异常', desc: '数据波动超出正常范围', time: '08:15' },
@ -300,17 +275,15 @@ export default {
{ level: 'medium', levelText: '警告', title: '网络延迟过高', desc: '监控网络延迟超过500ms', time: '06:22' } { level: 'medium', levelText: '警告', title: '网络延迟过高', desc: '监控网络延迟超过500ms', time: '06:22' }
], ],
//
cameras: [ cameras: [
{ id: 1, name: 'A1圈', location: 'A1圈', thumbnail: require("../../assets/images/jk1.png"), videoUrl:require('../../assets/images/jk1.png'), status: 'online', recording: true },
{ id: 2, name: 'A2圈', location: 'A2圈', thumbnail: require('../../assets/images/jk2.png'), videoUrl:require('../../assets/images/jk2.png'), status: 'online', recording: true },
{ id: 3, name: 'B1圈', location: 'B1圈', thumbnail:require('../../assets/images/jk5.png') , videoUrl:require('../../assets/images/jk5.png'), status: 'online', recording: true },
{ id: 4, name: '挤奶车间', location: '挤奶车间', thumbnail:require('../../assets/images/jk3.png'), videoUrl:require('../../assets/images/jk3.png'), status: 'online', recording: true },
{ id: 5, name: '草料仓库', location: '草料仓库', thumbnail:require('../../assets/images/jk4.png'), videoUrl:require('../../assets/images/jk4.png') , status: 'offline', recording: false }
{ id: 1, name: 'A1圈', location: 'A1圈', thumbnail: require("../../assets/images/jk1.png"), videoUrl: require('../../assets/images/jk1.png'), status: 'online', recording: true },
{ id: 2, name: 'A2圈', location: 'A2圈', thumbnail: require('../../assets/images/jk2.png'), videoUrl: require('../../assets/images/jk2.png'), status: 'online', recording: true },
{ id: 3, name: 'B1圈', location: 'B1圈', thumbnail: require('../../assets/images/jk5.png'), videoUrl: require('../../assets/images/jk5.png'), status: 'online', recording: true },
{ id: 4, name: '挤奶车间', location: '挤奶车间', thumbnail: require('../../assets/images/jk3.png'), videoUrl: require('../../assets/images/jk3.png'), status: 'online', recording: true },
{ id: 5, name: '草料仓库', location: '草料仓库', thumbnail: require('../../assets/images/jk4.png'), videoUrl: require('../../assets/images/jk4.png'), status: 'offline', recording: false }
], ],
currentCameraIndex: 0, currentCameraIndex: 0,
//
camelData: [ camelData: [
{ id: 'A1-01', age: 8, weight: 635, breed: '阿拉善双峰驼', score: 5, milkOutput: 3.4, parity: 2 }, { id: 'A1-01', age: 8, weight: 635, breed: '阿拉善双峰驼', score: 5, milkOutput: 3.4, parity: 2 },
{ id: 'A1-02', age: 8, weight: 642, breed: '阿拉善双峰驼', score: 4, milkOutput: 4.5, parity: 2 }, { id: 'A1-02', age: 8, weight: 642, breed: '阿拉善双峰驼', score: 4, milkOutput: 4.5, parity: 2 },
@ -349,7 +322,6 @@ export default {
{ id: 'A1-35', age: 7, weight: 622, breed: '阿拉善双峰驼', score: 5, milkOutput: 4.5, parity: 1 } { id: 'A1-35', age: 7, weight: 622, breed: '阿拉善双峰驼', score: 5, milkOutput: 4.5, parity: 1 }
], ],
//
scrollOption: { scrollOption: {
step: 0.5, step: 0.5,
limitMoveNum: 10, limitMoveNum: 10,
@ -361,7 +333,6 @@ export default {
switchOffset: 30 switchOffset: 30
}, },
//
maintenanceTasks: [ maintenanceTasks: [
{ id: 1, device: '温湿度传感器 (A1圈)', date: '2024-01-25', type: 'urgent', typeText: '紧急' }, { id: 1, device: '温湿度传感器 (A1圈)', date: '2024-01-25', type: 'urgent', typeText: '紧急' },
{ id: 2, device: '饮水监测仪 (C1圈)', date: '2024-01-22', type: 'warning', typeText: '需处理' }, { id: 2, device: '饮水监测仪 (C1圈)', date: '2024-01-22', type: 'warning', typeText: '需处理' },
@ -454,63 +425,72 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// - 使 flex
.device-dashboard { .device-dashboard {
width: 100%; width: 100%;
height: calc(100vh - 100px);
height: calc(100vh - 120px);
background: transparent; background: transparent;
padding: 20px 24px 30px;
padding: 20px 20px 0;
box-sizing: border-box; box-sizing: border-box;
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif; font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif;
overflow-y: auto;
overflow-x: auto;
overflow: hidden;
} }
// 使 flex
// Grid
.dashboard-main { .dashboard-main {
display: flex;
display: grid;
grid-template-columns: 1.2fr 1.6fr 1.2fr;
gap: 20px; gap: 20px;
flex-wrap: wrap;
align-items: stretch; //
.col-left, .col-right {
flex: 1.2;
min-width: 300px;
display: flex;
flex-direction: column;
gap: 20px;
}
.col-center {
flex: 1.6;
min-width: 400px;
display: flex;
flex-direction: column;
gap: 20px;
}
height: 100%;
min-height: 0;
}
//
.col-left {
display: grid;
grid-template-rows: 1fr auto;
gap: 20px;
min-height: 0;
overflow: hidden;
}
//
.col-center {
display: grid;
grid-template-rows: auto auto;
gap: 20px;
min-height: 0;
overflow: hidden;
}
//
.col-right {
display: grid;
grid-template-rows: 1fr auto auto;
gap: 20px;
min-height: 0;
overflow: hidden;
} }
// -
//
.card { .card {
background: transparent;
backdrop-filter: blur(0);
background: rgba(8, 28, 45, 0.55);
backdrop-filter: blur(3px);
border-radius: 16px; border-radius: 16px;
border: 1.5px solid rgba(52, 152, 219, 0.7); border: 1.5px solid rgba(52, 152, 219, 0.7);
overflow: hidden;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; //
min-height: 0;
overflow: hidden;
&:hover { &:hover {
border-color: #5dade2; border-color: #5dade2;
box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25); box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25);
background: rgba(52, 152, 219, 0.05);
background: rgba(52, 152, 219, 0.08);
} }
.card-header { .card-header {
padding: 14px 20px;
padding: 12px 16px;
border-bottom: 1px solid rgba(52, 152, 219, 0.4); border-bottom: 1px solid rgba(52, 152, 219, 0.4);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -518,15 +498,14 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
flex-shrink: 0; flex-shrink: 0;
.title { .title {
font-size: 16px;
font-size: 15px;
font-weight: 600; font-weight: 600;
color: #fff; color: #fff;
letter-spacing: 0.5px; letter-spacing: 0.5px;
text-shadow: 0 0 5px rgba(52, 152, 219, 0.5); text-shadow: 0 0 5px rgba(52, 152, 219, 0.5);
} }
.badge { .badge {
background: #e74c3c; background: #e74c3c;
border-radius: 30px; border-radius: 30px;
@ -534,13 +513,10 @@ export default {
font-size: 12px; font-size: 12px;
color: white; color: white;
} }
.sub { .sub {
font-size: 11px; font-size: 11px;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
.monitor-controls { .monitor-controls {
display: flex; display: flex;
gap: 8px; gap: 8px;
@ -561,22 +537,22 @@ export default {
} }
} }
// -
//
.device-stats-card { .device-stats-card {
flex-shrink: 0;
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 16px; gap: 16px;
padding: 20px;
padding: 16px;
.stat-item { .stat-item {
text-align: center; text-align: center;
.stat-value { .stat-value {
font-size: 28px;
font-size: 24px;
font-weight: bold; font-weight: bold;
color: #5dade2; color: #5dade2;
.unit { .unit {
font-size: 12px;
font-size: 11px;
margin-left: 2px; margin-left: 2px;
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
@ -597,11 +573,8 @@ export default {
// //
.device-list-card { .device-list-card {
display: flex;
flex-direction: column;
.device-search { .device-search {
padding: 12px 16px;
padding: 10px 16px;
flex-shrink: 0; flex-shrink: 0;
.search-input { .search-input {
width: 100%; width: 100%;
@ -612,24 +585,22 @@ export default {
color: white; color: white;
font-size: 12px; font-size: 12px;
outline: none; outline: none;
&:focus {
border-color: #5dade2;
}
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
&:focus { border-color: #5dade2; }
&::placeholder { color: rgba(255, 255, 255, 0.4); }
} }
} }
.device-list {
.device-list-wrapper {
flex: 1; flex: 1;
max-height: 280px;
overflow-y: auto; overflow-y: auto;
min-height: 0;
}
.device-list {
padding: 8px 16px; padding: 8px 16px;
.device-item { .device-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding: 12px;
padding: 10px;
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
border-radius: 12px; border-radius: 12px;
margin-bottom: 8px; margin-bottom: 8px;
@ -640,44 +611,21 @@ export default {
border-color: #5dade2; border-color: #5dade2;
background: rgba(52, 152, 219, 0.15); background: rgba(52, 152, 219, 0.15);
} }
&.warning {
border-color: rgba(243, 156, 18, 0.6);
}
&.offline {
border-color: rgba(149, 165, 166, 0.5);
opacity: 0.7;
}
.device-icon {
font-size: 24px;
}
&.warning { border-color: rgba(243, 156, 18, 0.6); }
&.offline { border-color: rgba(149, 165, 166, 0.5); opacity: 0.7; }
.device-icon { font-size: 24px; }
.device-info { .device-info {
flex: 1; flex: 1;
.device-name {
font-size: 13px;
font-weight: 500;
color: #fff;
}
.device-loc {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
}
.device-name { font-size: 13px; font-weight: 500; color: #fff; }
.device-loc { font-size: 10px; color: rgba(255, 255, 255, 0.5); }
} }
.device-status-badge { .device-status-badge {
font-size: 10px; font-size: 10px;
padding: 2px 8px; padding: 2px 8px;
border-radius: 20px; border-radius: 20px;
&.online {
background: rgba(46, 204, 113, 0.2);
color: #2ecc71;
}
&.warning {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.offline {
background: rgba(149, 165, 166, 0.2);
color: #95a5a6;
}
&.online { background: rgba(46, 204, 113, 0.2); color: #2ecc71; }
&.warning { background: rgba(243, 156, 18, 0.2); color: #f39c12; }
&.offline { background: rgba(149, 165, 166, 0.2); color: #95a5a6; }
} }
} }
} }
@ -685,52 +633,35 @@ export default {
// //
.alert-card { .alert-card {
.alert-list {
padding: 8px 16px;
max-height: 240px;
.alert-list-wrapper {
flex: 1;
overflow-y: auto; overflow-y: auto;
min-height: 0;
}
.alert-list {
padding: 8px 12px;
.alert-item { .alert-item {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 12px;
gap: 10px;
border-bottom: 1px solid rgba(52, 152, 219, 0.2); border-bottom: 1px solid rgba(52, 152, 219, 0.2);
padding: 12px 4px;
padding: 10px 4px;
.alert-level { .alert-level {
font-size: 10px; font-size: 10px;
padding: 2px 8px; padding: 2px 8px;
border-radius: 20px; border-radius: 20px;
min-width: 40px; min-width: 40px;
text-align: center; text-align: center;
&.high {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
}
&.medium {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.low {
background: rgba(52, 152, 219, 0.2);
color: #3498db;
}
&.high { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
&.medium { background: rgba(243, 156, 18, 0.2); color: #f39c12; }
&.low { background: rgba(52, 152, 219, 0.2); color: #3498db; }
} }
.alert-content { .alert-content {
flex: 1; flex: 1;
.alert-title {
font-size: 13px;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.alert-desc {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
margin-top: 2px;
}
}
.alert-time {
font-size: 10px;
color: rgba(255, 255, 255, 0.4);
.alert-title { font-size: 12px; font-weight: 500; color: rgba(255, 255, 255, 0.9); }
.alert-desc { font-size: 10px; color: rgba(255, 255, 255, 0.6); margin-top: 2px; }
} }
.alert-time { font-size: 10px; color: rgba(255, 255, 255, 0.4); }
} }
} }
} }
@ -747,7 +678,7 @@ export default {
width: 100%; width: 100%;
height: auto; height: auto;
display: block; display: block;
min-height: 260px;
min-height: 200px;
object-fit: cover; object-fit: cover;
} }
.monitor-overlay { .monitor-overlay {
@ -766,7 +697,6 @@ export default {
} }
} }
} }
} }
// //
@ -788,7 +718,7 @@ export default {
} }
.thumb-img { .thumb-img {
width: 100%; width: 100%;
height: 120px;
height: 100px;
object-fit: cover; object-fit: cover;
} }
.thumb-label { .thumb-label {
@ -803,129 +733,89 @@ export default {
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 50%; border-radius: 50%;
&.online {
background: #2ecc71;
box-shadow: 0 0 5px #2ecc71;
}
&.offline {
background: #95a5a6;
}
&.online { background: #2ecc71; box-shadow: 0 0 5px #2ecc71; }
&.offline { background: #95a5a6; }
} }
} }
} }
} }
} }
// -
//
.camel-archive-card { .camel-archive-card {
.camel-table-wrapper { .camel-table-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 360px;
flex: 1;
min-height: 0;
overflow: hidden; overflow: hidden;
} }
.camel-table { .camel-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-size: 11px; font-size: 11px;
table-layout: fixed; //
table-layout: fixed;
th, td { th, td {
padding: 10px 8px;
padding: 8px 6px;
text-align: center; text-align: center;
color: #fff; color: #fff;
box-sizing: border-box; box-sizing: border-box;
} }
th { th {
background: rgba(52, 152, 219, 0.3); background: rgba(52, 152, 219, 0.3);
font-weight: 500; font-weight: 500;
border-bottom: 1px solid rgba(52, 152, 219, 0.5); border-bottom: 1px solid rgba(52, 152, 219, 0.5);
} }
} }
.camel-table-header { .camel-table-header {
flex-shrink: 0; flex-shrink: 0;
th {
padding: 10px 8px;
}
} }
.camel-table-body {
tbody {
tr {
border-bottom: 1px solid rgba(52, 152, 219, 0.2);
&:hover {
background: rgba(52, 152, 219, 0.1);
}
td {
padding: 8px 6px;
color: rgba(255, 255, 255, 0.85);
}
}
}
.camel-table-body tbody tr {
border-bottom: 1px solid rgba(52, 152, 219, 0.2);
&:hover { background: rgba(52, 152, 219, 0.1); }
} }
.seamless-scroll-container { .seamless-scroll-container {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
} }
.seamless-scroll { .seamless-scroll {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
.score { .score {
display: inline-block; display: inline-block;
padding: 2px 6px; padding: 2px 6px;
border-radius: 20px; border-radius: 20px;
&.excellent {
background: rgba(46, 204, 113, 0.2);
color: #2ecc71;
}
&.good {
background: rgba(52, 152, 219, 0.2);
color: #5dade2;
}
&.normal {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.excellent { background: rgba(46, 204, 113, 0.2); color: #2ecc71; }
&.good { background: rgba(52, 152, 219, 0.2); color: #5dade2; }
&.normal { background: rgba(243, 156, 18, 0.2); color: #f39c12; }
} }
} }
// //
.stats-summary-card { .stats-summary-card {
flex-shrink: 0;
.summary-grid { .summary-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 16px; gap: 16px;
padding: 20px;
padding: 16px;
.summary-item { .summary-item {
text-align: center; text-align: center;
.summary-value { .summary-value {
font-size: 24px;
font-size: 22px;
font-weight: bold; font-weight: bold;
color: #5dade2; color: #5dade2;
.unit {
font-size: 11px;
margin-left: 2px;
color: rgba(255, 255, 255, 0.5);
}
}
.summary-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.7);
margin-top: 4px;
.unit { font-size: 10px; margin-left: 2px; color: rgba(255, 255, 255, 0.5); }
} }
.summary-label { font-size: 11px; color: rgba(255, 255, 255, 0.7); margin-top: 4px; }
} }
} }
} }
// //
.maintenance-card { .maintenance-card {
flex-shrink: 0;
.maintenance-list { .maintenance-list {
padding: 16px; padding: 16px;
.maintenance-item { .maintenance-item {
@ -934,32 +824,15 @@ export default {
align-items: center; align-items: center;
padding: 10px 0; padding: 10px 0;
border-bottom: 1px solid rgba(52, 152, 219, 0.2); border-bottom: 1px solid rgba(52, 152, 219, 0.2);
.maintenance-device {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
flex: 1;
}
.maintenance-date {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
margin: 0 12px;
}
.maintenance-device { font-size: 12px; color: rgba(255, 255, 255, 0.9); flex: 1; }
.maintenance-date { font-size: 10px; color: rgba(255, 255, 255, 0.5); margin: 0 12px; }
.maintenance-type { .maintenance-type {
font-size: 10px; font-size: 10px;
padding: 2px 8px; padding: 2px 8px;
border-radius: 20px; border-radius: 20px;
&.urgent {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
}
&.warning {
background: rgba(243, 156, 18, 0.2);
color: #f39c12;
}
&.normal {
background: rgba(52, 152, 219, 0.2);
color: #5dade2;
}
&.urgent { background: rgba(231, 76, 60, 0.2); color: #e74c3c; }
&.warning { background: rgba(243, 156, 18, 0.2); color: #f39c12; }
&.normal { background: rgba(52, 152, 219, 0.2); color: #5dade2; }
} }
} }
} }
@ -990,20 +863,14 @@ export default {
align-items: center; align-items: center;
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid rgba(52, 152, 219, 0.4); border-bottom: 1px solid rgba(52, 152, 219, 0.4);
.modal-title {
font-size: 16px;
font-weight: 600;
color: #fff;
}
.modal-title { font-size: 16px; font-weight: 600; color: #fff; }
.close-btn { .close-btn {
background: none; background: none;
border: none; border: none;
font-size: 24px; font-size: 24px;
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
cursor: pointer; cursor: pointer;
&:hover {
color: #e74c3c;
}
&:hover { color: #e74c3c; }
} }
} }
.modal-body { .modal-body {
@ -1011,11 +878,7 @@ export default {
.detail-row { .detail-row {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 12px;
.detail-label {
width: 100px;
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.detail-label { width: 100px; font-size: 12px; color: rgba(255, 255, 255, 0.6); }
.detail-value { .detail-value {
flex: 1; flex: 1;
font-size: 12px; font-size: 12px;
@ -1041,32 +904,29 @@ export default {
color: white; color: white;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
&:hover {
background: rgba(52, 152, 219, 0.4);
}
&.primary {
background: #3498db;
border-color: #3498db;
&:hover {
background: #2980b9;
}
}
&:hover { background: rgba(52, 152, 219, 0.4); }
&.primary { background: #3498db; border-color: #3498db; &:hover { background: #2980b9; } }
} }
} }
} }
} }
//
::-webkit-scrollbar {
width: 5px;
height: 5px;
//
.device-list-wrapper::-webkit-scrollbar,
.alert-list-wrapper::-webkit-scrollbar,
.seamless-scroll-container::-webkit-scrollbar {
width: 4px;
} }
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
.device-list-wrapper::-webkit-scrollbar-track,
.alert-list-wrapper::-webkit-scrollbar-track,
.seamless-scroll-container::-webkit-scrollbar-track {
background: rgba(52, 152, 219, 0.2);
border-radius: 2px;
} }
::-webkit-scrollbar-thumb {
.device-list-wrapper::-webkit-scrollbar-thumb,
.alert-list-wrapper::-webkit-scrollbar-thumb,
.seamless-scroll-container::-webkit-scrollbar-thumb {
background: #5dade2; background: #5dade2;
border-radius: 5px;
border-radius: 2px;
} }
</style> </style>

561
chenhai-ui/src/components/visual/scgk.vue

@ -1,6 +1,5 @@
<template> <template>
<div class="production-dashboard"> <div class="production-dashboard">
<!-- 主内容区域三列布局中间为地图+统计指标 -->
<div class="dashboard-main"> <div class="dashboard-main">
<!-- 左侧指标卡片与生产数据 --> <!-- 左侧指标卡片与生产数据 -->
<div class="col-left"> <div class="col-left">
@ -88,8 +87,8 @@
<!-- 百度地图容器 --> <!-- 百度地图容器 -->
<div class="map-card"> <div class="map-card">
<baidu-map id="baiduMapContainer" class="baidu-map-container" :center="center" :zoom="17"
:scroll-wheel-zoom="true" map-type="BMAP_NORMAL_MAP">
<baidu-map class="baidu-map-container" :center="center" :zoom="zoom" :scroll-wheel-zoom="true"
map-type="BMAP_NORMAL_MAP">
<bm-navigation anchor="BMAP_ANCHOR_TOP_RIGHT"></bm-navigation> <bm-navigation anchor="BMAP_ANCHOR_TOP_RIGHT"></bm-navigation>
</baidu-map> </baidu-map>
</div> </div>
@ -130,8 +129,7 @@
<div class="card-header"> <div class="card-header">
<span class="title">🐪 泌乳母驼存栏结构</span> <span class="title">🐪 泌乳母驼存栏结构</span>
</div> </div>
<div ref="lactatingBarChart" class="bar-chart-container"></div>
<div class="quota-footer">泌乳母驼 {{ lactatingCamels }} | 总存栏 {{ productionStats.camelCount }} </div>
<div ref="lactatingBarChart" class="chart-container"></div>
</div> </div>
</div> </div>
</div> </div>
@ -140,52 +138,16 @@
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
//
const AnimateNumber = {
props: {
from: Number,
to: Number,
duration: { type: Number, default: 2000 },
decimals: { type: Number, default: 0 }
},
data() {
return {
currentValue: this.from
};
},
mounted() {
let startTime = null;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = Math.min(1, (timestamp - startTime) / this.duration);
const val = this.from + (this.to - this.from) * progress;
this.currentValue = val;
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
},
render(h) {
const formatted = this.currentValue.toFixed(this.decimals);
return h('span', {}, formatted);
}
};
export default { export default {
name: 'ProductionControlPanel', name: 'ProductionControlPanel',
components: {
'animate-number': AnimateNumber
},
data() { data() {
return { return {
//
center: { lng: 104.516843, lat: 40.272114 }, center: { lng: 104.516843, lat: 40.272114 },
zoom: 17,
productionStats: { productionStats: {
totalMilk: 2845, //
totalMilk: 2845,
camelCount: 12860, camelCount: 12860,
fiberOutput: 78.3, fiberOutput: 78.3,
activeHouseholds: 342 activeHouseholds: 342
@ -207,16 +169,13 @@ export default {
camelChartIns: null, camelChartIns: null,
trendChartIns: null, trendChartIns: null,
lactatingBarChartIns: null, lactatingBarChartIns: null,
baiduMap: null
resizeObserver: null
}; };
}, },
computed: { computed: {
lactatingCamels() { lactatingCamels() {
return Math.round(this.productionStats.camelCount * 0.48); return Math.round(this.productionStats.camelCount * 0.48);
}, },
otherCamels() {
return this.productionStats.camelCount - this.lactatingCamels;
},
barChartData() { barChartData() {
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']; const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
const lactatingData = []; const lactatingData = [];
@ -236,26 +195,18 @@ export default {
} }
}, },
mounted() { mounted() {
this.getCurrentDateTime();
this.initAllCharts(); this.initAllCharts();
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
this.$nextTick(() => {
this.initBaiduMapNative();
});
this.observeChartResize();
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
if (this.resizeObserver) this.resizeObserver.disconnect();
if (this.camelChartIns) this.camelChartIns.dispose(); if (this.camelChartIns) this.camelChartIns.dispose();
if (this.trendChartIns) this.trendChartIns.dispose(); if (this.trendChartIns) this.trendChartIns.dispose();
if (this.lactatingBarChartIns) this.lactatingBarChartIns.dispose(); if (this.lactatingBarChartIns) this.lactatingBarChartIns.dispose();
if (this.baiduMap) {
this.baiduMap.destroy();
}
}, },
methods: { methods: {
getCurrentDateTime() {
//
},
initAllCharts() { initAllCharts() {
this.initCamelStructureChart(); this.initCamelStructureChart();
this.initTrendChart(); this.initTrendChart();
@ -268,23 +219,40 @@ export default {
this.camelChartIns = echarts.init(chartDom); this.camelChartIns = echarts.init(chartDom);
this.camelChartIns.setOption({ this.camelChartIns.setOption({
tooltip: { trigger: 'item', backgroundColor: 'rgba(0,0,0,0.7)', borderColor: '#3498db', textStyle: { color: '#fff' } }, tooltip: { trigger: 'item', backgroundColor: 'rgba(0,0,0,0.7)', borderColor: '#3498db', textStyle: { color: '#fff' } },
legend: { orient: 'vertical', left: 'left', textStyle: { color: '#fff' } },
legend: {
orient: 'vertical',
left: 'left',
textStyle: { color: '#fff' },
itemWidth: 20,
itemHeight: 12
},
series: [ series: [
{ {
name: '骆驼结构', name: '骆驼结构',
type: 'pie', type: 'pie',
radius: '55%',
radius: ['70%', '80%'],
center: ['50%', '50%'], center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: 'rgba(0,0,0,0.2)',
borderWidth: 1
},
label: {
color: '#fff',
formatter: '{b}: {d}%',
fontSize: 11
},
emphasis: { scale: true },
data: [ data: [
{ value: 5840, name: '产奶母驼', itemStyle: { color: '#3498db' } }, { value: 5840, name: '产奶母驼', itemStyle: { color: '#3498db' } },
{ value: 4320, name: '育成驼', itemStyle: { color: '#5dade2' } }, { value: 4320, name: '育成驼', itemStyle: { color: '#5dade2' } },
{ value: 2700, name: '种公驼', itemStyle: { color: '#85c1e9' } } { value: 2700, name: '种公驼', itemStyle: { color: '#85c1e9' } }
],
label: { color: '#fff', formatter: '{b}: {d}%' },
emphasis: { scale: true }
]
} }
], ],
backgroundColor: 'transparent'
backgroundColor: 'transparent',
graphic: []
}); });
}, },
initTrendChart() { initTrendChart() {
@ -297,32 +265,35 @@ export default {
xAxis: { xAxis: {
type: 'category', type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#5dade2' } }
axisLabel: { color: '#fff', fontSize: 10, rotate: 30 },
axisLine: { lineStyle: { color: '#5dade2' } },
axisTick: { show: false }
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: '产奶量 (吨)', name: '产奶量 (吨)',
nameTextStyle: { color: '#fff' },
axisLabel: { color: '#fff' },
nameTextStyle: { color: '#fff', fontSize: 11 },
axisLabel: { color: '#fff', fontSize: 10 },
splitLine: { lineStyle: { color: 'rgba(93, 173, 226, 0.3)' } }, splitLine: { lineStyle: { color: 'rgba(93, 173, 226, 0.3)' } },
axisLine: { lineStyle: { color: '#5dade2' } }
axisLine: { show: false }
}, },
series: [{ series: [{
data: [165, 182, 204, 234, 290, 310, 342, 356, 330, 298, 245, 189], data: [165, 182, 204, 234, 290, 310, 342, 356, 330, 298, 245, 189],
type: 'line', type: 'line',
smooth: true, smooth: true,
lineStyle: { width: 3, color: '#5dade2' },
lineStyle: { width: 2, color: '#5dade2' },
areaStyle: { areaStyle: {
opacity: 0.2, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#3498db' }, { offset: 1, color: 'rgba(52, 152, 219, 0.1)' }
opacity: 0.2,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#3498db' },
{ offset: 1, color: 'rgba(52, 152, 219, 0.1)' }
]) ])
}, },
symbol: 'circle', symbol: 'circle',
symbolSize: 8,
symbolSize: 6,
itemStyle: { color: '#3498db' } itemStyle: { color: '#3498db' }
}], }],
grid: { containLabel: true, bottom: 0, top: 40, right: 20, left: 10 }
grid: { containLabel: true, top: 30, bottom: 5, left: 10, right: 10 }
}); });
}, },
initLactatingBarChart() { initLactatingBarChart() {
@ -350,35 +321,37 @@ export default {
data: ['泌乳母驼', '其他骆驼'], data: ['泌乳母驼', '其他骆驼'],
textStyle: { color: '#fff' }, textStyle: { color: '#fff' },
left: 'left', left: 'left',
itemWidth: 25,
itemHeight: 14
itemWidth: 20,
itemHeight: 12,
itemGap: 10
}, },
grid: { grid: {
containLabel: true, containLabel: true,
bottom: 15,
top: 30, top: 30,
right: 15,
left: 50
bottom: 5,
left: 10,
right: 10
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: months, data: months,
axisLabel: { color: '#fff', rotate: 30, interval: 0 },
axisLabel: { color: '#fff', fontSize: 9, rotate: 30, interval: 0 },
axisLine: { lineStyle: { color: '#5dade2' } }, axisLine: { lineStyle: { color: '#5dade2' } },
axisTick: { show: false } axisTick: { show: false }
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
nameTextStyle: { color: '#fff' },
axisLabel: { color: '#fff' },
nameTextStyle: { color: '#fff', fontSize: 10 },
axisLabel: { color: '#fff', fontSize: 9 },
splitLine: { lineStyle: { color: 'rgba(93, 173, 226, 0.3)' } }, splitLine: { lineStyle: { color: 'rgba(93, 173, 226, 0.3)' } },
axisLine: { lineStyle: { color: '#5dade2' } }
axisLine: { show: false }
}, },
series: [ series: [
{ {
name: '泌乳母驼', name: '泌乳母驼',
type: 'bar', type: 'bar',
stack: 'total', stack: 'total',
barWidth: '60%',
data: lactatingData, data: lactatingData,
itemStyle: { color: '#3498db', borderRadius: [4, 4, 0, 0] }, itemStyle: { color: '#3498db', borderRadius: [4, 4, 0, 0] },
label: { show: false } label: { show: false }
@ -387,6 +360,7 @@ export default {
name: '其他骆驼', name: '其他骆驼',
type: 'bar', type: 'bar',
stack: 'total', stack: 'total',
barWidth: '60%',
data: otherData, data: otherData,
itemStyle: { color: '#85c1e9', borderRadius: [4, 4, 0, 0] }, itemStyle: { color: '#85c1e9', borderRadius: [4, 4, 0, 0] },
label: { show: false } label: { show: false }
@ -395,83 +369,24 @@ export default {
backgroundColor: 'transparent' backgroundColor: 'transparent'
}); });
}, },
initBaiduMapNative() {
if (typeof window.BMapGL === 'undefined') {
console.error('百度地图API未加载,请检查AK或网络,稍后重试');
setTimeout(() => this.initBaiduMapNative(), 500);
return;
}
const mapContainer = document.getElementById('baiduMapContainer');
if (!mapContainer) {
console.error('地图容器未找到');
return;
}
mapContainer.innerHTML = '';
const map = new window.BMapGL.Map(mapContainer);
const centerPoint = new window.BMapGL.Point(105.669, 38.833);
map.centerAndZoom(centerPoint, 11);
map.enableScrollWheelZoom(true);
map.setMapStyle({ style: 'midnight' });
const markerPoint = new window.BMapGL.Point(105.669, 38.833);
const marker = new window.BMapGL.Marker(markerPoint);
map.addOverlay(marker);
const infoWindow = new window.BMapGL.InfoWindow(
'<div style="background:#0a1a2e;color:#fff;padding:5px;border-radius:8px;"><strong>吉兰泰骆驼产业园</strong><br/>存栏骆驼: 4200峰<br/>日产奶量: 8.6吨</div>',
{ width: 200, offset: new window.BMapGL.Size(0, -30) }
);
marker.addEventListener('click', () => {
map.openInfoWindow(infoWindow, markerPoint);
});
const circlePoint = new window.BMapGL.Point(105.62, 38.81);
const circle = new window.BMapGL.Circle(circlePoint, 5000, {
strokeColor: "#3498db",
strokeWeight: 2,
fillColor: "#5dade2",
fillOpacity: 0.2
});
map.addOverlay(circle);
const labelOpts = { position: new window.BMapGL.Point(105.69, 38.85), offset: new window.BMapGL.Size(20, 0) };
const label = new window.BMapGL.Label('巴音塔拉嘎查', labelOpts);
label.setStyle({
color: '#fff',
fontSize: '12px',
backgroundColor: 'rgba(0,0,0,0.6)',
border: 'none',
borderRadius: '4px',
padding: '2px 6px'
observeChartResize() {
const charts = ['camelChart', 'trendChart', 'lactatingBarChart'];
this.resizeObserver = new ResizeObserver(() => {
setTimeout(() => {
if (this.camelChartIns) this.camelChartIns.resize();
if (this.trendChartIns) this.trendChartIns.resize();
if (this.lactatingBarChartIns) this.lactatingBarChartIns.resize();
}, 100);
}); });
map.addOverlay(label);
const markerPoint2 = new window.BMapGL.Point(105.59, 38.79);
const marker2 = new window.BMapGL.Marker(markerPoint2);
map.addOverlay(marker2);
const infoWindow2 = new window.BMapGL.InfoWindow(
'<div style="background:#0a1a2e;color:#fff;padding:5px;border-radius:8px;"><strong>查干通格牧场</strong><br/>存栏骆驼: 2100峰<br/>日产奶量: 4.2吨</div>',
{ width: 200, offset: new window.BMapGL.Size(0, -30) }
);
marker2.addEventListener('click', () => {
map.openInfoWindow(infoWindow2, markerPoint2);
charts.forEach(refName => {
const el = this.$refs[refName];
if (el) this.resizeObserver.observe(el);
}); });
this.baiduMap = map;
setTimeout(() => {
if (this.baiduMap) {
this.baiduMap.centerAndZoom(centerPoint, 11);
}
}, 200);
}, },
handleResize() { handleResize() {
if (this.camelChartIns) this.camelChartIns.resize(); if (this.camelChartIns) this.camelChartIns.resize();
if (this.trendChartIns) this.trendChartIns.resize(); if (this.trendChartIns) this.trendChartIns.resize();
if (this.lactatingBarChartIns) this.lactatingBarChartIns.resize(); if (this.lactatingBarChartIns) this.lactatingBarChartIns.resize();
if (this.baiduMap) {
setTimeout(() => {
this.baiduMap.centerAndZoom(this.baiduMap.getCenter(), this.baiduMap.getZoom());
}, 100);
}
} }
} }
}; };
@ -480,66 +395,58 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.production-dashboard { .production-dashboard {
width: 100%; width: 100%;
height: 100vh;
height: calc(100vh - 120px);
background: transparent; background: transparent;
padding: 20px 24px 30px;
padding: 20px 20px 0;
box-sizing: border-box; box-sizing: border-box;
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif; font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif;
overflow-y: auto;
overflow-x: auto;
overflow: hidden;
} }
.dashboard-main { .dashboard-main {
display: flex;
display: grid;
grid-template-columns: 1.2fr 2fr 1.2fr;
gap: 20px; gap: 20px;
align-items: stretch; /* 确保所有列高度一致 */
.col-left,
.col-right {
flex: 1.2;
min-width: 280px;
display: flex;
flex-direction: column;
gap: 20px;
}
.col-center {
flex: 2;
min-width: 480px;
display: flex;
flex-direction: column;
gap: 16px;
}
height: 100%;
min-height: 0;
} }
@media (max-width: 1200px) {
.dashboard-main {
flex-wrap: wrap;
}
.col-left, .col-center, .col-right {
min-width: 320px;
}
.col-left,
.col-right {
display: grid;
grid-template-rows: auto 1fr 1fr;
gap: 20px;
min-height: 0;
overflow: hidden;
}
.col-center {
display: grid;
grid-template-rows: auto 1fr;
gap: 16px;
min-height: 0;
overflow: hidden;
} }
.card { .card {
background: transparent;
backdrop-filter: blur(0);
background: rgba(8, 28, 45, 0.55);
backdrop-filter: blur(3px);
border-radius: 16px; border-radius: 16px;
border: 1.5px solid rgba(52, 152, 219, 0.7); border: 1.5px solid rgba(52, 152, 219, 0.7);
overflow: hidden;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; /* 让卡片自动填充可用高度 */
min-height: 0;
overflow: hidden;
&:hover { &:hover {
border-color: #5dade2; border-color: #5dade2;
box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25); box-shadow: 0 4px 20px rgba(52, 152, 219, 0.25);
background: rgba(52, 152, 219, 0.05);
background: rgba(52, 152, 219, 0.08);
} }
.card-header { .card-header {
padding: 14px 20px;
padding: 12px 16px;
border-bottom: 1px solid rgba(52, 152, 219, 0.4); border-bottom: 1px solid rgba(52, 152, 219, 0.4);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -547,7 +454,7 @@ export default {
flex-shrink: 0; flex-shrink: 0;
.title { .title {
font-size: 16px;
font-size: 15px;
font-weight: 600; font-weight: 600;
color: #fff; color: #fff;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@ -564,19 +471,76 @@ export default {
} }
} }
/* 地图上方统计指标 */
/* KPI卡片 - 内容自适应 */
.kpi-card {
.kpi-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding: 16px;
flex: 1;
.kpi-item {
background: rgba(0, 0, 0, 0.25);
border-radius: 12px;
padding: 12px 8px;
text-align: center;
border: 1.5px solid rgba(52, 152, 219, 0.5);
transition: all 0.2s;
&:hover {
border-color: #5dade2;
background: rgba(52, 152, 219, 0.08);
}
.kpi-value {
font-size: 24px;
font-weight: bold;
color: #5dade2;
.unit {
font-size: 12px;
margin-left: 4px;
color: rgba(255, 255, 255, 0.7);
}
}
.kpi-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
margin: 6px 0 4px;
}
.trend {
font-size: 10px;
&.up {
color: #2ecc71;
}
}
}
}
}
/* 图表容器 - 完全自适应 */
.chart-container {
flex: 1;
width: 100%;
min-height: 0;
}
/* 地图统计指标 */
.map-stats { .map-stats {
display: flex;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px; gap: 12px;
justify-content: space-between;
flex-wrap: wrap;
flex-shrink: 0;
.stat-badge { .stat-badge {
flex: 1;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
border-radius: 12px; border-radius: 12px;
padding: 24px 8px;
padding: 16px 8px;
text-align: center; text-align: center;
border: 1.5px solid rgba(52, 152, 219, 0.6); border: 1.5px solid rgba(52, 152, 219, 0.6);
transition: all 0.2s; transition: all 0.2s;
@ -588,15 +552,14 @@ export default {
.stat-label { .stat-label {
display: block; display: block;
font-size: 20px;
font-size: 14px;
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.85);
margin-bottom: 10px;
font-weight: 500;
margin-bottom: 8px;
} }
.stat-number { .stat-number {
display: block; display: block;
font-size: 28px;
font-size: 22px;
font-weight: bold; font-weight: bold;
color: #5dade2; color: #5dade2;
@ -607,112 +570,44 @@ export default {
} }
} }
.kpi-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 20px;
flex: 1;
.kpi-item {
background: rgba(0, 0, 0, 0.25);
border-radius: 12px;
padding: 14px;
text-align: center;
border: 1.5px solid rgba(52, 152, 219, 0.5);
transition: all 0.2s;
display: flex;
flex-direction: column;
justify-content: center;
&:hover {
border-color: #5dade2;
background: rgba(52, 152, 219, 0.08);
}
.kpi-value {
font-size: 28px;
font-weight: bold;
color: #5dade2;
.unit {
font-size: 14px;
margin-left: 4px;
color: rgba(255, 255, 255, 0.7);
}
}
.kpi-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
margin: 6px 0 4px;
font-weight: 500;
}
.trend {
font-size: 11px;
font-weight: 500;
&.up {
color: #2ecc71;
}
}
}
}
.chart-container {
width: 100%;
height: 220px;
padding: 8px;
flex-shrink: 0;
}
.bar-chart-container {
width: 100%;
height: 260px;
padding: 8px 8px 0 8px;
flex-shrink: 0;
}
/* 地图卡片 */
.map-card { .map-card {
margin-top: 0;
flex: 1; flex: 1;
min-height: 0;
display: flex; display: flex;
min-height: 500px;
border-radius: 16px;
overflow: hidden;
border: 1.5px solid rgba(52, 152, 219, 0.6);
background: #0a1f2e;
.baidu-map-container { .baidu-map-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 480px;
border-radius: 16px;
overflow: hidden;
border: 1.5px solid rgba(52, 152, 219, 0.6);
background: #0a1f2e;
} }
} }
/* 预警和任务列表 */
.alert-list, .alert-list,
.task-list { .task-list {
padding: 8px 16px;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
padding: 12px;
.alert-item,
.task-item {
.alert-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid rgba(52, 152, 219, 0.3); border-bottom: 1px solid rgba(52, 152, 219, 0.3);
padding: 12px 4px;
font-size: 13px;
padding: 10px 4px;
font-size: 12px;
.alert-type { .alert-type {
width: 46px;
width: 42px;
border-radius: 20px; border-radius: 20px;
text-align: center; text-align: center;
padding: 3px 0;
padding: 2px 0;
font-size: 11px;
background: rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.4);
font-weight: 500;
&.warning { &.warning {
background: rgba(230, 126, 34, 0.3); background: rgba(230, 126, 34, 0.3);
@ -732,90 +627,64 @@ export default {
.alert-msg { .alert-msg {
flex: 1; flex: 1;
margin-left: 12px;
margin-left: 10px;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
} }
.alert-time { .alert-time {
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
font-size: 11px;
font-size: 10px;
} }
} }
} }
.task-item {
flex-direction: column;
align-items: flex-start !important;
.task-list {
.task-item {
margin-bottom: 12px;
.task-name {
font-weight: 600;
margin-bottom: 8px;
color: #5dade2;
}
.task-name {
font-weight: 600;
margin-bottom: 6px;
color: #5dade2;
font-size: 13px;
}
.task-progress {
width: 100%;
background: rgba(52, 152, 219, 0.3);
border-radius: 12px;
height: 8px;
.task-progress {
width: 100%;
background: rgba(52, 152, 219, 0.3);
border-radius: 10px;
height: 6px;
.progress-bar {
height: 8px;
background: #5dade2;
border-radius: 12px;
width: 0%;
.progress-bar {
height: 6px;
background: #5dade2;
border-radius: 10px;
}
} }
}
.task-info {
font-size: 11px;
margin-top: 8px;
color: rgba(255, 255, 255, 0.6);
.task-info {
font-size: 10px;
margin-top: 6px;
color: rgba(255, 255, 255, 0.6);
}
} }
} }
.quota-footer {
text-align: center;
font-size: 12px;
padding: 12px 0 18px;
color: rgba(255, 255, 255, 0.7);
border-top: 1px solid rgba(52, 152, 219, 0.4);
margin-top: auto;
flex-shrink: 0;
}
.col-right .card {
flex: 1;
display: flex;
flex-direction: column;
}
/* 确保左侧卡片内容自适应 */
.col-left .card {
flex: 1;
display: flex;
flex-direction: column;
}
.col-left .kpi-card {
flex: 0 0 auto; /* KPI卡片不自动拉伸 */
/* 滚动条美化 */
.alert-list::-webkit-scrollbar,
.task-list::-webkit-scrollbar {
width: 4px;
} }
.col-left .chart-card {
flex: 1;
min-height: 240px;
.alert-list::-webkit-scrollbar-track,
.task-list::-webkit-scrollbar-track {
background: rgba(52, 152, 219, 0.2);
border-radius: 2px;
} }
.production-dashboard::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.production-dashboard::-webkit-scrollbar-track {
background: rgba(52, 152, 219, 0.1);
border-radius: 3px;
}
.production-dashboard::-webkit-scrollbar-thumb {
.alert-list::-webkit-scrollbar-thumb,
.task-list::-webkit-scrollbar-thumb {
background: #5dade2; background: #5dade2;
border-radius: 3px;
border-radius: 2px;
} }
</style> </style>

21
chenhai-ui/src/router/index.js

@ -67,15 +67,28 @@ export const constantRoutes = [
component: () => import('@/views/Home'), component: () => import('@/views/Home'),
hidden: true hidden: true
}, },
// {
// path: '',
// component: Layout,
// redirect: 'index',
// children: [
// {
// path: 'index',
// component: () => import('@/views/index'),
// name: 'Index',
// meta: { title: '首页', icon: 'dashboard', affix: true }
// }
// ]
// },
{ {
path: '', path: '',
component: Layout, component: Layout,
redirect: 'index',
redirect: '/Home',
children: [ children: [
{ {
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
path: '/Home',
component: () => import('@/views/Home'),
meta: { title: '首页', icon: 'dashboard', affix: true } meta: { title: '首页', icon: 'dashboard', affix: true }
} }
] ]

53
chenhai-ui/src/views/Home.vue

@ -14,8 +14,8 @@
<!-- 标题 --> <!-- 标题 -->
<div class="title"> <div class="title">
<div>阿右旗阿拉腾敖包镇骆驼产业标准化智慧化</div>
<div>示范基地数据中心</div>
<div>阿右旗阿拉腾敖包镇骆驼产业标准化智慧化</div>
<div>示范基地数据中心</div>
</div> </div>
<!-- 头部左侧时间和日期 --> <!-- 头部左侧时间和日期 -->
@ -39,7 +39,9 @@
fontWeight: 'normal', fontWeight: 'normal',
letterSpacing: '2px' letterSpacing: '2px'
}" /> }" />
<i class="el-icon-switch-button" @click="logout()" />
</div> </div>
</div> </div>
<div id="cen_content"> <div id="cen_content">
<transition name="fade-slide" mode="out-in"> <transition name="fade-slide" mode="out-in">
@ -68,6 +70,7 @@ import 'dayjs/locale/zh-cn'
import scgk from '@/components/visual/scgk' import scgk from '@/components/visual/scgk'
import jkjc from '@/components/visual/jkjc' import jkjc from '@/components/visual/jkjc'
import sbgl from '@/components/visual/sbgl' import sbgl from '@/components/visual/sbgl'
import { getUserProfile } from '@/api/system/user'
export default { export default {
components: { components: {
@ -149,9 +152,33 @@ export default {
this.getCurrentDate() this.getCurrentDate()
this.getCurrentTime() this.getCurrentTime()
// this.getUser()
}, },
methods: { methods: {
async logout() {
this.$confirm("确定要退出系统吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$store.dispatch("LogOut").then(() => {
location.href = "/index";
});
})
.catch(() => { });
},
//
// getUser() {
// getUserProfile().then(res => {
// console.log(111,res);
// this.username = res.data.nickName
// })
// },
// tab
bindTab(index) { bindTab(index) {
this.active = index this.active = index
}, },
@ -205,7 +232,7 @@ export default {
position: relative; position: relative;
background-size: 100% 100%; background-size: 100% 100%;
.title{
.title {
width: 100%; width: 100%;
text-align: center; text-align: center;
color: #fff; color: #fff;
@ -359,11 +386,11 @@ export default {
width: 350px; width: 350px;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0;
right: 20px;
left: auto; left: auto;
top: auto; top: auto;
display: grid; display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr 1fr 50px;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
border-radius: 8px 0 0 0; border-radius: 8px 0 0 0;
@ -372,6 +399,19 @@ export default {
div { div {
width: auto; width: auto;
} }
.el-icon-switch-button {
position: absolute;
color: #fff;
right: 0;
bottom: 20px;
display: inline-block;
font-size: 20px;
cursor: pointer;
}
.el-icon-switch-button:hover{
color: #5DACE1;
}
} }
} }
@ -528,5 +568,4 @@ export default {
line-height: 40px !important; line-height: 40px !important;
} }
} }
}
</style>
}</style>

522
chenhai-ui/src/views/login.vue

@ -1,11 +1,23 @@
<template> <template>
<div class="login"> <div class="login">
<!-- 动态背景层骆驼剪影 + 沙尘粒子 -->
<div class="camel-bg">
<div class="camel-silhouette camel-1"></div>
<div class="camel-silhouette camel-2"></div>
<div class="camel-silhouette camel-3"></div>
</div>
<div class="sand-particles"></div>
<!-- 主登录卡片 - 更宽的设计 -->
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form"> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<!-- 品牌区域 -->
<!-- 品牌区域骆驼元素突出 -->
<div class="brand-area"> <div class="brand-area">
<div class="logo-icon">🐫</div>
<div class="camel-logo">
<span class="camel-icon">🐫</span>
<span class="camel-icon-small">🐪</span>
</div>
<h3 class="title">{{ title }}</h3> <h3 class="title">{{ title }}</h3>
<p class="subtitle">智慧牧场 · 轻松管理</p>
<p class="subtitle">🏜 智慧驼场 · 科技兴牧 🏜</p>
</div> </div>
<el-form-item prop="username"> <el-form-item prop="username">
@ -33,7 +45,6 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<!-- 优化后的验证码区域更紧凑对齐美观 -->
<el-form-item prop="code" v-if="captchaEnabled" class="captcha-item"> <el-form-item prop="code" v-if="captchaEnabled" class="captcha-item">
<div class="captcha-wrapper"> <div class="captcha-wrapper">
<el-input <el-input
@ -52,7 +63,6 @@
</div> </div>
</el-form-item> </el-form-item>
<!-- 选项栏记住密码 + 立即注册 -->
<div class="options-bar"> <div class="options-bar">
<el-checkbox v-model="loginForm.rememberMe" class="remember-checkbox">记住密码</el-checkbox> <el-checkbox v-model="loginForm.rememberMe" class="remember-checkbox">记住密码</el-checkbox>
<router-link v-if="register" class="register-link" to="/register">立即注册</router-link> <router-link v-if="register" class="register-link" to="/register">立即注册</router-link>
@ -71,6 +81,10 @@
</el-button> </el-button>
</el-form-item> </el-form-item>
<!-- 装饰性骆驼脚印 -->
<div class="camel-footprints">
<span>🐫</span><span>🐪</span><span>🐫</span><span>🐪</span><span>🐫</span>
</div>
</el-form> </el-form>
</div> </div>
</template> </template>
@ -172,266 +186,524 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
// -
$primary-color: #7c9a3e; // 绿
$primary-hover: #5c7a2e; // 绿
$bg-overlay: rgba(0, 0, 0, 0.3);
$text-dark: #2c3a1f;
$text-light: #6b7a5a;
$card-white: rgba(255, 255, 255, 0.96);
$shadow-sm: 0 20px 35px -10px rgba(0, 0, 0, 0.2);
$shadow-focus: 0 0 0 3px rgba(124, 154, 62, 0.2);
//
$camel-brown: #c4903a; //
$camel-dark: #8b6914; //
$camel-light: #e8c89a; //
$sand-light: #f5e6c8; //
$grass-green: #5c8a3c; // 绿
$sky-blue: #4a7c6f; //
$text-dark: #3d2b1a;
$text-light: #fef5e6;
$card-bg: rgba(255, 248, 235, 0.96);
$card-border: rgba(196, 144, 58, 0.3);
$shadow-card: 0 30px 50px -15px rgba(0, 0, 0, 0.3);
$shadow-focus: 0 0 0 3px rgba(196, 144, 58, 0.3);
.login { .login {
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%;
height: 100vh; height: 100vh;
min-height: 500px;
background: linear-gradient(135deg, #2b5e2b 0%, #8cb36b 50%, #d4c9a3 100%);
min-height: 600px;
overflow: hidden; overflow: hidden;
//
background: linear-gradient(145deg, #2d5a3b 0%, #6b8c42 30%, #c4a86a 70%, #e8d4b0 100%);
//
.camel-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
z-index: 1;
.camel-silhouette {
position: absolute;
bottom: 5%;
width: 180px;
height: 120px;
background: rgba(100, 60, 20, 0.2);
border-radius: 50% 40% 40% 50% / 60% 50% 50% 40%;
filter: blur(3px);
&::before {
content: '🐪';
position: absolute;
font-size: 100px;
opacity: 0.25;
bottom: -20px;
left: 0;
}
&.camel-1 {
left: 2%;
animation: walkCamel 25s linear infinite;
transform: scaleX(1);
&::before { content: '🐫'; }
}
&.camel-2 {
right: 5%;
animation: walkCamel 30s linear infinite reverse;
transform: scaleX(-1);
&::before { content: '🐪'; font-size: 130px; }
}
&.camel-3 {
left: 25%;
bottom: 15%;
animation: walkCamel 35s linear infinite;
animation-delay: -10s;
transform: scale(0.7);
&::before { content: '🐫'; opacity: 0.15; }
}
}
}
//
.sand-particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2;
&::before, &::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-image: radial-gradient(2px 2px at 10% 30%, rgba(210, 170, 100, 0.4) 2px, transparent 2px),
radial-gradient(3px 3px at 85% 60%, rgba(190, 140, 70, 0.3) 3px, transparent 3px);
background-size: 60px 60px, 100px 100px;
animation: floatSand 40s linear infinite;
}
&::after {
background-image: radial-gradient(1px 1px at 40% 80%, rgba(230, 200, 140, 0.5) 1px, transparent 1px);
background-size: 40px 40px;
animation: floatSand 30s linear infinite reverse;
}
}
// -
.login-form { .login-form {
position: relative; position: relative;
z-index: 2;
width: 440px;
padding: 40px 35px 35px;
background: $card-white;
z-index: 20;
width: 540px;
max-width: 90vw;
padding: 48px 48px 40px;
background: $card-bg;
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
border-radius: 32px;
box-shadow: $shadow-sm;
transition: all 0.3s ease;
border: 1px solid rgba(255,255,255,0.5);
border-radius: 48px;
box-shadow: $shadow-card;
transition: all 0.4s cubic-bezier(0.2, 0.9, 0.4, 1.1);
border: 1px solid $card-border;
&:hover { &:hover {
transform: translateY(-3px);
box-shadow: 0 25px 40px -12px rgba(0, 0, 0, 0.25);
transform: translateY(-5px);
box-shadow: 0 35px 55px -18px rgba(0, 0, 0, 0.35);
} }
} }
//
// -
.brand-area { .brand-area {
text-align: center; text-align: center;
margin-bottom: 30px;
.logo-icon {
font-size: 56px;
display: inline-block;
animation: gentleSwing 2s infinite ease;
margin-bottom: 32px;
.camel-logo {
margin-bottom: 12px;
animation: camelBounce 2s ease-in-out infinite;
.camel-icon {
font-size: 70px;
display: inline-block;
filter: drop-shadow(0 4px 12px rgba(139, 105, 20, 0.3));
transition: all 0.3s;
&:hover {
transform: scale(1.05);
filter: drop-shadow(0 6px 18px rgba(196, 144, 58, 0.5));
}
}
.camel-icon-small {
font-size: 40px;
display: inline-block;
margin-left: -10px;
vertical-align: middle;
animation: smallCamel 1.5s ease-in-out infinite alternate;
}
} }
.title { .title {
margin: 12px 0 6px;
font-size: 28px;
font-weight: 600;
margin: 8px 0 10px;
font-size: 24px;
font-weight: 700;
color: $text-dark; color: $text-dark;
letter-spacing: 2px;
letter-spacing: 1px;
line-height: 1.4;
background: linear-gradient(135deg, #8b6914 0%, #c4903a 40%, #e8c89a 100%);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
} }
.subtitle { .subtitle {
font-size: 13px;
color: $text-light;
margin-bottom: 5px;
font-weight: 400;
font-size: 15px;
color: #8b6b3d;
font-weight: 500;
letter-spacing: 1px;
} }
} }
//
//
.custom-input { .custom-input {
margin-bottom: 6px;
:deep(.el-input__inner) { :deep(.el-input__inner) {
height: 48px;
height: 54px;
border-radius: 60px; border-radius: 60px;
border: 1px solid #e2e8e6;
background: #fefef7;
padding-left: 42px;
border: 1.5px solid #e8dcc8;
background: #fffef8;
color: $text-dark;
padding-left: 54px;
font-size: 15px; font-size: 15px;
transition: all 0.2s;
transition: all 0.3s ease;
&::placeholder {
color: #b8a88a;
}
&:hover {
border-color: $camel-brown;
background: #ffffff;
}
&:focus { &:focus {
border-color: $primary-color;
border-color: $camel-brown;
box-shadow: $shadow-focus; box-shadow: $shadow-focus;
} }
} }
:deep(.el-input__prefix) { :deep(.el-input__prefix) {
left: 16px;
top: 2px;
left: 20px;
top: 3px;
.input-icon { .input-icon {
font-size: 18px;
color: $primary-color;
font-size: 20px;
color: $camel-brown;
} }
} }
} }
// ========= =========
//
.captcha-item { .captcha-item {
margin-bottom: 18px;
margin-bottom: 20px;
:deep(.el-form-item__content) { :deep(.el-form-item__content) {
line-height: initial; line-height: initial;
} }
} }
.captcha-wrapper { .captcha-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px;
gap: 14px;
width: 100%; width: 100%;
} }
.captcha-input { .captcha-input {
flex: 1; flex: 1;
:deep(.el-input__inner) { :deep(.el-input__inner) {
height: 48px;
height: 54px;
border-radius: 60px; border-radius: 60px;
border: 1px solid #e2e8e6;
background: #fefef7;
padding-left: 42px;
border: 1.5px solid #e8dcc8;
background: #fffef8;
padding-left: 54px;
font-size: 15px; font-size: 15px;
transition: all 0.2s;
&:focus { &:focus {
border-color: $primary-color;
border-color: $camel-brown;
box-shadow: $shadow-focus; box-shadow: $shadow-focus;
} }
} }
:deep(.el-input__prefix) { :deep(.el-input__prefix) {
left: 16px;
top: 2px;
left: 20px;
top: 3px;
.input-icon { .input-icon {
font-size: 18px;
color: $primary-color;
font-size: 20px;
color: $camel-brown;
} }
} }
} }
.captcha-image { .captcha-image {
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
width: 110px;
height: 48px;
border-radius: 12px;
width: 130px;
height: 54px;
border-radius: 18px;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
border: 1px solid $camel-brown;
&:hover { &:hover {
transform: scale(1.02); transform: scale(1.02);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
.refresh-icon { .refresh-icon {
opacity: 1; opacity: 1;
transform: translate(-50%, -50%) scale(1);
} }
} }
} }
.captcha-img { .captcha-img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
display: block; display: block;
} }
.refresh-icon { .refresh-icon {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 22px;
transform: translate(-50%, -50%) scale(0.9);
font-size: 24px;
font-weight: bold; font-weight: bold;
color: white; color: white;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
opacity: 0; opacity: 0;
transition: opacity 0.2s;
background: rgba(0, 0, 0, 0.5);
width: 32px;
height: 32px;
transition: all 0.25s ease;
background: rgba(100, 60, 20, 0.7);
width: 38px;
height: 38px;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
backdrop-filter: blur(2px);
backdrop-filter: blur(4px);
pointer-events: none; pointer-events: none;
} }
// +
//
.options-bar { .options-bar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin: 8px 0 25px 0;
margin: 10px 0 30px 0;
.remember-checkbox { .remember-checkbox {
:deep(.el-checkbox__inner) { :deep(.el-checkbox__inner) {
border-radius: 4px;
border-color: #cbd5e1;
background-color: #fff;
border-radius: 5px;
border-color: #c4aa80;
background-color: #fffef8;
width: 17px;
height: 17px;
&::after {
height: 9px;
left: 5px;
top: 1px;
width: 4px;
border-color: $camel-brown;
}
} }
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) { :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: $primary-color;
border-color: $primary-color;
background-color: $camel-light;
border-color: $camel-brown;
} }
:deep(.el-checkbox__label) { :deep(.el-checkbox__label) {
color: $text-light;
color: $text-dark;
font-size: 13px; font-size: 13px;
font-weight: 500;
} }
} }
.register-link { .register-link {
color: $primary-color;
color: $camel-brown;
font-size: 13px; font-size: 13px;
font-weight: 600;
text-decoration: none; text-decoration: none;
font-weight: 500;
transition: 0.2s; transition: 0.2s;
position: relative;
&::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: $camel-brown;
transition: width 0.25s ease;
}
&:hover { &:hover {
color: $primary-hover;
text-decoration: underline;
color: $camel-dark;
&::after {
width: 100%;
}
} }
} }
} }
// //
.login-btn { .login-btn {
width: 100%; width: 100%;
height: 48px;
background: $primary-color;
height: 56px;
background: linear-gradient(105deg, #c4903a 0%, #a0702a 50%, #c4903a 100%);
border: none; border: none;
border-radius: 60px; border-radius: 60px;
font-size: 16px;
font-weight: 600;
letter-spacing: 2px;
box-shadow: 0 8px 18px rgba(124, 154, 62, 0.3);
transition: all 0.25s;
font-size: 18px;
font-weight: 700;
letter-spacing: 4px;
color: #fffef5;
box-shadow: 0 8px 20px rgba(164, 112, 42, 0.4);
transition: all 0.3s cubic-bezier(0.2, 0.9, 0.4, 1.1);
&:hover, &:focus { &:hover, &:focus {
background: $primary-hover;
transform: scale(1.01);
box-shadow: 0 10px 22px rgba(92, 122, 46, 0.4);
background: linear-gradient(105deg, #d4a050 0%, #c4903a 50%, #d4a050 100%);
transform: translateY(-2px);
box-shadow: 0 12px 28px rgba(164, 112, 42, 0.5);
}
&:active {
transform: translateY(1px);
}
}
//
.camel-footprints {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 28px;
opacity: 0.5;
span {
font-size: 16px;
animation: footprintWalk 1.5s ease-in-out infinite;
animation-delay: calc(var(--i, 0) * 0.15s);
@for $i from 1 through 5 {
&:nth-child(#{$i}) {
--i: #{$i};
}
}
} }
} }
} }
//
@keyframes gentleSwing {
0%,100%{ transform: rotate(0deg); }
50%{ transform: rotate(6deg); }
//
@keyframes camelBounce {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-6px) rotate(2deg); }
} }
//
@media (max-width: 500px) {
@keyframes smallCamel {
0% { transform: translateY(0) rotate(0deg); opacity: 0.8; }
100% { transform: translateY(-8px) rotate(5deg); opacity: 1; }
}
@keyframes walkCamel {
0% { transform: translateX(-10%) translateY(0); }
25% { transform: translateX(0%) translateY(-5px); }
50% { transform: translateX(10%) translateY(0); }
75% { transform: translateX(0%) translateY(5px); }
100% { transform: translateX(-10%) translateY(0); }
}
@keyframes floatSand {
0% { background-position: 0 0; opacity: 0.6; }
100% { background-position: 100px 100px; opacity: 1; }
}
@keyframes footprintWalk {
0%, 100% { transform: translateY(0) scale(1); opacity: 0.5; }
50% { transform: translateY(-5px) scale(1.1); opacity: 1; }
}
//
@media (max-width: 650px) {
.login .login-form { .login .login-form {
width: 88%;
padding: 30px 20px;
width: 92%;
padding: 36px 28px 32px;
border-radius: 36px;
}
.brand-area {
margin-bottom: 24px;
.camel-logo .camel-icon { font-size: 55px; }
.camel-logo .camel-icon-small { font-size: 30px; }
.title { font-size: 18px; }
.subtitle { font-size: 12px; }
} }
.captcha-wrapper { .captcha-wrapper {
gap: 8px;
gap: 10px;
} }
.captcha-image { .captcha-image {
width: 95px;
height: 44px;
width: 110px;
height: 48px;
} }
.captcha-input :deep(.el-input__inner) {
height: 44px;
.captcha-input :deep(.el-input__inner),
.custom-input :deep(.el-input__inner) {
height: 48px;
padding-left: 48px;
}
.login-btn {
height: 50px;
font-size: 16px;
}
.camel-footprints {
gap: 8px;
span { font-size: 14px; }
}
}
//
@media (prefers-color-scheme: dark) {
.login .login-form {
background: rgba(30, 25, 20, 0.92);
.brand-area .title {
background: linear-gradient(135deg, #e8c89a 0%, #c4903a 100%);
background-clip: text;
-webkit-background-clip: text;
}
.custom-input :deep(.el-input__inner) {
background: #2a2518;
border-color: #5a4a30;
color: #f0e6d8;
&:focus {
border-color: $camel-brown;
}
}
.options-bar .remember-checkbox :deep(.el-checkbox__label) {
color: #f0e6d8;
}
} }
} }
</style> </style>
Loading…
Cancel
Save