|
|
@@ -0,0 +1,837 @@
|
|
|
+<template>
|
|
|
+ <view class="ele-details-page">
|
|
|
+ <cu-custom :isBack="true">
|
|
|
+ <template slot="content">
|
|
|
+ <view class="nav-title">小气候监测站II监测要素</view>
|
|
|
+ </template>
|
|
|
+ </cu-custom>
|
|
|
+ <view class="date-picker" @click="openDatePicker">
|
|
|
+ <text>{{ startDate }}</text>
|
|
|
+ <text class="separator">-</text>
|
|
|
+ <text>{{ endDate }}</text>
|
|
|
+ <image class="date-icon" src="/static/images/device/date-active.svg" />
|
|
|
+ </view>
|
|
|
+ <u-calendar
|
|
|
+ v-model="show"
|
|
|
+ :mode="mode"
|
|
|
+ @change="onDateChange"
|
|
|
+ ref="calendar"
|
|
|
+ range-color="#999"
|
|
|
+ btn-type="success"
|
|
|
+ active-bg-color="#0BBC58"
|
|
|
+ range-bg-color="rgba(11,188,88,0.13)"
|
|
|
+ ></u-calendar>
|
|
|
+ <view class="echarts-board">
|
|
|
+ <view class="period-tabs">
|
|
|
+ <view
|
|
|
+ class="tab-item"
|
|
|
+ :class="{ active: activePeriod === '24h' }"
|
|
|
+ @click="changePeriod('24h')"
|
|
|
+ >24小时</view
|
|
|
+ >
|
|
|
+ <view
|
|
|
+ class="tab-item"
|
|
|
+ :class="{ active: activePeriod === '7d' }"
|
|
|
+ @click="changePeriod('7d')"
|
|
|
+ >一周</view
|
|
|
+ >
|
|
|
+ <view
|
|
|
+ class="tab-item"
|
|
|
+ :class="{ active: activePeriod === '30d' }"
|
|
|
+ @click="changePeriod('30d')"
|
|
|
+ >一月</view
|
|
|
+ >
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="element-tabs" v-if="hasData">
|
|
|
+ <view class="ele-board">
|
|
|
+ <view
|
|
|
+ v-for="(item, index) in eleTabs"
|
|
|
+ :key="index"
|
|
|
+ class="ele-item"
|
|
|
+ :class="{ 'ele-active': eleActive === index }"
|
|
|
+ @click="changeElement(index)"
|
|
|
+ >{{ item.name }}</view
|
|
|
+ >
|
|
|
+ </view>
|
|
|
+ <view class="fold-icon" @click="toggleFold">
|
|
|
+ <u-icon :name="isFolded ? 'arrow-down' : 'arrow-up'"></u-icon>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 展开后的所有要素 -->
|
|
|
+ <view v-if="!isFolded && hasData" class="expanded-elements">
|
|
|
+ <view
|
|
|
+ class="expanded-item"
|
|
|
+ v-for="(item, index) in eleTabs"
|
|
|
+ :key="index"
|
|
|
+ :class="{ 'ele-active': eleActive === index }"
|
|
|
+ @click="changeElement(index)"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="chart-container" v-if="hasData">
|
|
|
+ <canvas
|
|
|
+ v-show="!show"
|
|
|
+ canvas-id="temperatureChart"
|
|
|
+ id="temperatureChart"
|
|
|
+ class="chart"
|
|
|
+ @touchstart="touchLineA($event)"
|
|
|
+ @touchmove="moveLineA($event)"
|
|
|
+ @touchend="touchEndLineA($event)"
|
|
|
+ disable-scroll="true"
|
|
|
+ ></canvas>
|
|
|
+ </view>
|
|
|
+ <view class="no-data" v-else>
|
|
|
+ <text>暂无数据</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- <view class="stats-panel">
|
|
|
+ <view class="stat-item">
|
|
|
+ <text class="value">27<text class="unit">°C</text></text>
|
|
|
+ <text class="label">最低值</text>
|
|
|
+ </view>
|
|
|
+ <view class="stat-item">
|
|
|
+ <text class="value">32<text class="unit">°C</text></text>
|
|
|
+ <text class="label">平均值</text>
|
|
|
+ </view>
|
|
|
+ <view class="stat-item">
|
|
|
+ <text class="value red">42<text class="unit red">°C</text></text>
|
|
|
+ <text class="label">最高值</text>
|
|
|
+ </view>
|
|
|
+ </view> -->
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="history-section">
|
|
|
+ <view class="section-title">历史数据</view>
|
|
|
+ <view class="table-wrap">
|
|
|
+ <view class="fixed-column">
|
|
|
+ <view class="table-cell header">上报时间</view>
|
|
|
+ <view class="table-cell" v-for="(item, idx) in tableList" :key="idx">
|
|
|
+ {{ item.time }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="table-bg"></view>
|
|
|
+ <scroll-view class="scroll-column" scroll-x="true">
|
|
|
+ <view class="scroll-content">
|
|
|
+ <view class="table-row">
|
|
|
+ <view class="table-cell header">{{
|
|
|
+ activeChartInfo.name + "(" + activeChartInfo.unit + ")"
|
|
|
+ }}</view>
|
|
|
+ </view>
|
|
|
+ <view class="table-row" v-for="(item, idx) in tableList" :key="idx">
|
|
|
+ <view class="table-cell">{{ item[activeChartInfo.key] }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="pagination">
|
|
|
+ <view
|
|
|
+ class="page-item prev"
|
|
|
+ @click="prevPage"
|
|
|
+ :class="{ disabled: currentPage === 1 }"
|
|
|
+ >上一页</view
|
|
|
+ >
|
|
|
+ <view class="page-info">
|
|
|
+ <text class="curret-page">{{ currentPage }}</text>
|
|
|
+ <text>/</text>
|
|
|
+ <text>{{ totalPages }}</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="page-item next"
|
|
|
+ @click="nextPage"
|
|
|
+ :class="{ disabled: currentPage >= totalPages }"
|
|
|
+ >下一页</view
|
|
|
+ >
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import uCharts from "../../../components/js_sdk/u-charts/u-charts/u-charts.js";
|
|
|
+import baseCharts from "./charts.vue";
|
|
|
+export default {
|
|
|
+ components: { baseCharts },
|
|
|
+ onLoad(options) {
|
|
|
+ const deviceInfo = JSON.parse(decodeURIComponent(options.deviceInfo));
|
|
|
+ this.eleKey = options.eleKey || "";
|
|
|
+ this.initDateRange(deviceInfo.uptime);
|
|
|
+ this.deviceInfo = deviceInfo;
|
|
|
+ this.getChartData();
|
|
|
+ this.getObData();
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ temperatureChart: null,
|
|
|
+ activePeriod: "",
|
|
|
+ activeElement: "temperature",
|
|
|
+ show: false,
|
|
|
+ mode: "range",
|
|
|
+ startDate: "",
|
|
|
+ endDate: "",
|
|
|
+ isFolded: true, // 控制要素是否收起
|
|
|
+ eleTabs: [],
|
|
|
+ eleActive: 0,
|
|
|
+ hasData: true,
|
|
|
+ tableList: [],
|
|
|
+ titles: [],
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 9,
|
|
|
+ total: 0,
|
|
|
+ chartData: {
|
|
|
+ categories: [],
|
|
|
+ series: [],
|
|
|
+ },
|
|
|
+ activeChartInfo: {},
|
|
|
+ eleKey: "",
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ totalPages() {
|
|
|
+ return Math.ceil(this.total / this.pageSize);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ initDateRange(timestamp) {
|
|
|
+ if (!timestamp) {
|
|
|
+ const today = new Date();
|
|
|
+ this.endDate = this.formatDate(today);
|
|
|
+ const startDate = new Date(today);
|
|
|
+ startDate.setDate(startDate.getDate() - 7);
|
|
|
+ this.startDate = this.formatDate(startDate);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const date = new Date(Number(timestamp) * 1000);
|
|
|
+ this.endDate = this.formatDate(date);
|
|
|
+ const startDate = new Date(date);
|
|
|
+ startDate.setDate(startDate.getDate() - 7);
|
|
|
+ this.startDate = this.formatDate(startDate);
|
|
|
+ },
|
|
|
+ formatDate(date) {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
|
+ const day = String(date.getDate()).padStart(2, "0");
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
+ },
|
|
|
+ initChart() {
|
|
|
+ const ctx = uni.createCanvasContext("temperatureChart", this);
|
|
|
+ const currentEle = this.eleTabs[this.eleActive];
|
|
|
+ console.log(currentEle, "currentEle");
|
|
|
+ // u-charts 的 series.data 必须是数字数组;原数据 value 为字符串(含 "/"、"NA"),
|
|
|
+ // 直接传对象数组会导致 Y 轴 min/max 计算异常,刻度全部相同
|
|
|
+ const seriesData = currentEle.data.map((item) => {
|
|
|
+ const val = item.value;
|
|
|
+ if (val === "/" || val === "NA" || val === null || val === undefined || val === "") {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ const num = Number(val);
|
|
|
+ return isNaN(num) ? 0 : num;
|
|
|
+ });
|
|
|
+ const series = [
|
|
|
+ {
|
|
|
+ name: currentEle.name,
|
|
|
+ data: seriesData,
|
|
|
+ lineWidth: 3,
|
|
|
+ pointShape: "circle",
|
|
|
+ pointSize: 12,
|
|
|
+ pointColor: currentEle.color,
|
|
|
+ pointBorderColor: "#fff",
|
|
|
+ pointBorderWidth: 2,
|
|
|
+ symbol: "emptyCircle",
|
|
|
+ symbolSize: 6,
|
|
|
+ legendShape: "circle",
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ const xData = currentEle.data.map((item) => item.time);
|
|
|
+
|
|
|
+ this.temperatureChart = new uCharts({
|
|
|
+ $this: this,
|
|
|
+ canvasId: "temperatureChart",
|
|
|
+ type: "line",
|
|
|
+ context: ctx,
|
|
|
+ width: 340,
|
|
|
+ height: 180,
|
|
|
+ categories: xData,
|
|
|
+ series: series,
|
|
|
+ legend: {
|
|
|
+ position: "top",
|
|
|
+ show: true,
|
|
|
+ padding: 5,
|
|
|
+ fontColor: "#303133",
|
|
|
+ fontSize: 12,
|
|
|
+ itemType: "circle",
|
|
|
+ itemShape: "emptyCircle",
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ lineWidth: 2,
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ axisLineColor: "#E4E7ED",
|
|
|
+ disableGrid: true,
|
|
|
+ fontColor: "#656565",
|
|
|
+ fontSize: 10,
|
|
|
+ itemCount: 5,
|
|
|
+ scrollShow: true,
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ tofix: 2,
|
|
|
+ axisLineColor: "#E4E7ED",
|
|
|
+ fontColor: "#656565",
|
|
|
+ gridColor: "#E4E7ED",
|
|
|
+ dashLength: 4,
|
|
|
+ disableGrid: false,
|
|
|
+ fontSize: 10,
|
|
|
+ },
|
|
|
+ dataLabel: false,
|
|
|
+ enableScroll: true,
|
|
|
+ background: "#FFFFFF",
|
|
|
+ extra: {
|
|
|
+ line: {
|
|
|
+ type: "curve",
|
|
|
+ width: 2,
|
|
|
+ activeType: "hollow",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ touchLineA(e) {
|
|
|
+ this.temperatureChart.scrollStart(e);
|
|
|
+ },
|
|
|
+ moveLineA(e) {
|
|
|
+ this.temperatureChart.scroll(e);
|
|
|
+ },
|
|
|
+ touchEndLineA(e) {
|
|
|
+ this.temperatureChart.scrollEnd(e);
|
|
|
+ //下面是toolTip事件,如果滚动后不需要显示,可不填写
|
|
|
+ // this.temperatureChart.showToolTip(e, {
|
|
|
+ // format: function(item, category) {
|
|
|
+ // console.log(item, 'iyee', e)
|
|
|
+ // return category + ' ' + item.name + ':' + item.data
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 打开日期选择器
|
|
|
+ openDatePicker() {
|
|
|
+ this.show = true;
|
|
|
+ // 延迟设置日期,确保组件已经初始化
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$refs.calendar) {
|
|
|
+ // 直接设置组件内部的日期状态
|
|
|
+ const calendar = this.$refs.calendar;
|
|
|
+ calendar.startDate = this.startDate;
|
|
|
+ calendar.endDate = this.endDate;
|
|
|
+
|
|
|
+ // 如果有选中的日期范围,设置对应的年、月、日
|
|
|
+ if (this.startDate) {
|
|
|
+ const startParts = this.startDate.split("-");
|
|
|
+ calendar.startYear = parseInt(startParts[0]);
|
|
|
+ calendar.startMonth = parseInt(startParts[1]);
|
|
|
+ calendar.startDay = parseInt(startParts[2]);
|
|
|
+ }
|
|
|
+ if (this.endDate) {
|
|
|
+ const endParts = this.endDate.split("-");
|
|
|
+ calendar.endYear = parseInt(endParts[0]);
|
|
|
+ calendar.endMonth = parseInt(endParts[1]);
|
|
|
+ calendar.endDay = parseInt(endParts[2]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有开始日期,设置日历当前显示的年月为开始日期的年月
|
|
|
+ if (this.startDate) {
|
|
|
+ const startParts = this.startDate.split("-");
|
|
|
+ calendar.year = parseInt(startParts[0]);
|
|
|
+ calendar.month = parseInt(startParts[1]);
|
|
|
+ calendar.changeData();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 日期选择变化事件
|
|
|
+ onDateChange(e) {
|
|
|
+ this.startDate = e.startDate;
|
|
|
+ this.endDate = e.endDate;
|
|
|
+ this.activePeriod = "custom"; // 设置为自定义周期
|
|
|
+ this.show = false;
|
|
|
+ this.getChartData();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切换时间周期
|
|
|
+ changePeriod(period) {
|
|
|
+ this.activePeriod = period;
|
|
|
+
|
|
|
+ // 根据选择的周期自动计算日期范围
|
|
|
+ const now = new Date();
|
|
|
+ const endDate = now.toISOString().split("T")[0];
|
|
|
+ const startDate = new Date();
|
|
|
+
|
|
|
+ if (period === "24h") {
|
|
|
+ startDate.setDate(now.getDate());
|
|
|
+ } else if (period === "7d") {
|
|
|
+ startDate.setDate(now.getDate() - 7);
|
|
|
+ } else if (period === "30d") {
|
|
|
+ startDate.setMonth(now.getMonth() - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.startDate = startDate.toISOString().split("T")[0];
|
|
|
+ this.endDate = endDate;
|
|
|
+ this.getChartData();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切换要素标签
|
|
|
+ changeElement(index) {
|
|
|
+ if (this.eleTabs.length === 0) return;
|
|
|
+ this.eleActive = index;
|
|
|
+ this.activeChartInfo = this.eleTabs[index];
|
|
|
+ const key = this.activeChartInfo.unit ? this.activeChartInfo.name + '(' + this.activeChartInfo.unit + ')' : this.activeChartInfo.name
|
|
|
+ const keyValue = this.getKeyByName(key)
|
|
|
+ this.activeChartInfo.key = keyValue
|
|
|
+ this.isFolded = true; // 关闭展开面板
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.initChart();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ getKeyByName(key){
|
|
|
+ const titles = [...this.titles]
|
|
|
+ let k
|
|
|
+ titles.forEach(title => {
|
|
|
+ if(title.name === key){
|
|
|
+ k = title.key
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return k
|
|
|
+ },
|
|
|
+ // 切换要素展开/收起状态
|
|
|
+ toggleFold() {
|
|
|
+ this.isFolded = !this.isFolded;
|
|
|
+ },
|
|
|
+ // 上一页
|
|
|
+ prevPage() {
|
|
|
+ if (this.currentPage > 1) {
|
|
|
+ this.currentPage--;
|
|
|
+ this.getObData();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 下一页
|
|
|
+ nextPage() {
|
|
|
+ if (this.currentPage < this.totalPages) {
|
|
|
+ this.currentPage++;
|
|
|
+ this.getObData();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 折线图数据
|
|
|
+ getChartData() {
|
|
|
+ this.$myRequest({
|
|
|
+ url: "/api/api_gateway?method=greenhouse.env_first.monitor_data",
|
|
|
+ data: {
|
|
|
+ device_id: this.deviceInfo.devBid,
|
|
|
+ start: String(
|
|
|
+ Math.floor(new Date(this.startDate + " 00:00:00").getTime() / 1000),
|
|
|
+ ),
|
|
|
+ end: String(
|
|
|
+ Math.floor(new Date(this.endDate + " 23:59:59").getTime() / 1000),
|
|
|
+ ),
|
|
|
+ page: 1,
|
|
|
+ size: 999,
|
|
|
+ },
|
|
|
+ }).then((res) => {
|
|
|
+ console.log("getChartData:", res);
|
|
|
+ if (res && Array.isArray(res) && res.length > 0) {
|
|
|
+ this.eleTabs = res;
|
|
|
+ this.hasData = true;
|
|
|
+ this.activeChartInfo =
|
|
|
+ this.eleTabs.find((item) => item.name === this.eleKey) ||
|
|
|
+ this.eleTabs[0];
|
|
|
+ this.eleActive = this.eleTabs.indexOf(this.activeChartInfo);
|
|
|
+ const key = this.activeChartInfo.unit ? this.activeChartInfo.name + '(' + this.activeChartInfo.unit + ')' : this.activeChartInfo.name
|
|
|
+ const keyValue = this.getKeyByName(key)
|
|
|
+ this.activeChartInfo.key = keyValue
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.initChart();
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.eleTabs = [];
|
|
|
+ this.hasData = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 历史数据
|
|
|
+ getObData() {
|
|
|
+ this.$myRequest({
|
|
|
+ url: "/api/api_gateway?method=greenhouse.env_first.history_data",
|
|
|
+ data: {
|
|
|
+ device_id: this.deviceInfo.devBid,
|
|
|
+ start: String(
|
|
|
+ Math.floor(new Date(this.startDate + " 00:00:00").getTime() / 1000),
|
|
|
+ ),
|
|
|
+ end: String(
|
|
|
+ Math.floor(new Date(this.endDate + " 23:59:59").getTime() / 1000),
|
|
|
+ ),
|
|
|
+ page: this.currentPage,
|
|
|
+ page_size: this.pageSize,
|
|
|
+ },
|
|
|
+ }).then((res) => {
|
|
|
+ console.log("getObData:", res);
|
|
|
+ const columns = res.title;
|
|
|
+ const data = res.table;
|
|
|
+ const newData = data.map((item) => {
|
|
|
+ return this.processData(item);
|
|
|
+ });
|
|
|
+ const resCol = this.convertDynamic(columns, (prop, val) =>
|
|
|
+ Array.isArray(val) ? `${val[1]}${val[3] || ""}` : val,
|
|
|
+ );
|
|
|
+ this.columns = resCol;
|
|
|
+ this.titles = res?.title || [];
|
|
|
+ this.tableList = newData;
|
|
|
+ this.total = res.total;
|
|
|
+ console.log("tableList:", this.columns);
|
|
|
+ console.log("tableList", this.tableList);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ convertDynamic(data, getLabelValue) {
|
|
|
+ if (!data || typeof data !== "object") return [];
|
|
|
+ const result = [];
|
|
|
+ const keys = Object.keys(data);
|
|
|
+ for (const prop of keys) {
|
|
|
+ const arr = data[prop];
|
|
|
+ const value = getLabelValue(prop, arr, data);
|
|
|
+ if (value !== undefined && value !== null) {
|
|
|
+ result.push({
|
|
|
+ label: String(value),
|
|
|
+ prop,
|
|
|
+ align: "center",
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ },
|
|
|
+ processData(data) {
|
|
|
+ return Object.entries(data).reduce((acc, [key, value]) => {
|
|
|
+ acc[key] = Array.isArray(value) ? value[0] : value;
|
|
|
+ return acc;
|
|
|
+ }, {});
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.ele-details-page {
|
|
|
+ background:
|
|
|
+ linear-gradient(180deg, #ffffff00 0%, #f5f6fa 23.64%, #f5f6fa 100%),
|
|
|
+ linear-gradient(102deg, #bfeadd 6.77%, #b8f1e7 40.15%, #b9eef5 84.02%);
|
|
|
+ min-height: 100vh;
|
|
|
+ padding: 0 32rpx;
|
|
|
+ padding-top: 98rpx;
|
|
|
+}
|
|
|
+.nav-title{
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.date-picker {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ padding: 16rpx 24rpx;
|
|
|
+ margin-bottom: 36rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 46rpx;
|
|
|
+ .date-icon {
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.separator {
|
|
|
+ margin: 0 16rpx;
|
|
|
+}
|
|
|
+.echarts-board {
|
|
|
+ padding: 32rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ background: #fff;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ position: relative;
|
|
|
+ .period-tabs {
|
|
|
+ width: 350rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin: auto;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+ border-radius: 32rpx;
|
|
|
+ background: #f1f4f8;
|
|
|
+ padding: 8rpx 10rpx;
|
|
|
+ color: #303133;
|
|
|
+ font-family: "Source Han Sans CN VF";
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: normal;
|
|
|
+ .tab-item {
|
|
|
+ padding: 5rpx 24rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ line-height: 48rpx;
|
|
|
+ border-radius: 32rpx;
|
|
|
+ }
|
|
|
+ .active {
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .element-tabs {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ overflow-x: auto;
|
|
|
+ white-space: nowrap;
|
|
|
+ .ele-board {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ .ele-item {
|
|
|
+ position: relative;
|
|
|
+ color: #999999;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 400;
|
|
|
+ white-space: nowrap;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ padding: 14rpx 16rpx;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .ele-active {
|
|
|
+ color: #303133;
|
|
|
+ font-weight: 500;
|
|
|
+ &::after {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ bottom: 4rpx;
|
|
|
+ display: block;
|
|
|
+ width: 30rpx;
|
|
|
+ height: 8rpx;
|
|
|
+ border-radius: 4rpx;
|
|
|
+ background: #1fc676;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .fold-icon {
|
|
|
+ width: 48rpx;
|
|
|
+ border-left: 2px solid #999;
|
|
|
+ padding-left: 16rpx;
|
|
|
+ margin-left: 16rpx;
|
|
|
+ cursor: pointer;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* 展开后的要素容器样式 */
|
|
|
+ .expanded-elements {
|
|
|
+ position: absolute;
|
|
|
+ top: 126rpx;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 16rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+ z-index: 10;
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .expanded-item {
|
|
|
+ flex: 0 0 calc(33.33% - 16rpx);
|
|
|
+ text-align: center;
|
|
|
+ padding: 16rpx 0;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ color: #999999;
|
|
|
+ font-size: 26rpx;
|
|
|
+ background: #ffffff;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .expanded-item.ele-active {
|
|
|
+ color: #1fc676;
|
|
|
+ background: #e6f7ef;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ border-radius: 16rpx;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ position: relative;
|
|
|
+ padding: 10rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ height: 336rpx;
|
|
|
+ z-index:0;
|
|
|
+ .chart {
|
|
|
+ width: 100%;
|
|
|
+ height: 336rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .no-data {
|
|
|
+ border-radius: 16rpx;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ padding: 10rpx;
|
|
|
+ height: 336rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+ text {
|
|
|
+ color: #999;
|
|
|
+ font-size: 32rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-panel {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ background-color: #f1f4f8;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx 64rpx;
|
|
|
+ .stat-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ .label {
|
|
|
+ color: #656565;
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: 400;
|
|
|
+ }
|
|
|
+ .value {
|
|
|
+ font-size: 40rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ .unit {
|
|
|
+ color: #999999;
|
|
|
+ font-size: 20rpx;
|
|
|
+ }
|
|
|
+ .red {
|
|
|
+ color: #fb4e52;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.history-section {
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx 32rpx;
|
|
|
+ .section-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+ .table-wrap {
|
|
|
+ display: flex;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin: 20rpx 0;
|
|
|
+ color: #042118;
|
|
|
+ font-family: "Source Han Sans CN VF";
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: normal;
|
|
|
+ .fixed-column {
|
|
|
+ width: 250rpx;
|
|
|
+ .table-cell {
|
|
|
+ width: 100%;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .table-bg {
|
|
|
+ width: 16rpx;
|
|
|
+ background: linear-gradient(270deg, #ffffff33 0%, #9598a433 100%);
|
|
|
+ }
|
|
|
+ .scroll-column {
|
|
|
+ flex: 1;
|
|
|
+ height: auto;
|
|
|
+ overflow: hidden;
|
|
|
+ margin-left: -10rpx;
|
|
|
+ }
|
|
|
+ .scroll-content {
|
|
|
+ min-width: 100%;
|
|
|
+ width: fit-content;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ .table-row {
|
|
|
+ display: flex;
|
|
|
+ .table-cell {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .table-cell {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ line-height: 64rpx;
|
|
|
+ text-align: center;
|
|
|
+ border-bottom: 2px solid #e4e7ed;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ .table-cell.header {
|
|
|
+ background: #f6f8fc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .pagination {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-top: 24rpx;
|
|
|
+ .page-item {
|
|
|
+ width: 104rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ color: #656565;
|
|
|
+ font-size: 24rpx;
|
|
|
+ line-height: 48rpx;
|
|
|
+ text-align: center;
|
|
|
+ border: 2rpx solid #e4e7ed;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ }
|
|
|
+ .page-item.disabled {
|
|
|
+ color: #999;
|
|
|
+ border-color: #e4e7ed;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ }
|
|
|
+ .next {
|
|
|
+ color: #0bbc58;
|
|
|
+ border: 2rpx solid #0bbc58;
|
|
|
+ }
|
|
|
+ .next.disabled {
|
|
|
+ color: #999;
|
|
|
+ border-color: #e4e7ed;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ }
|
|
|
+ .page-info {
|
|
|
+ color: #999999;
|
|
|
+ font-family: "Source Han Sans CN VF";
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: normal;
|
|
|
+ .curret-page {
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+::v-deep .u-calendar__action {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+</style>
|