| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807 |
- <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"
- ></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
- 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.uptime }}
- </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.title] }}</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));
- console.log(deviceInfo, "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: [],
- 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");
- currentEle.data.forEach((item) => {
- if (item.value === "/") {
- item.value = "0";
- }
- });
- const series = [
- {
- name: currentEle.name,
- data: currentEle.data,
- 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: {
- splitNumber: 4,
- 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];
- this.isFolded = true; // 关闭展开面板
- this.$nextTick(() => {
- this.initChart();
- });
- },
- // 切换要素展开/收起状态
- 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=qxz.data.qxz_ob_data_map",
- 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,
- page_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.title === this.eleKey) ||
- this.eleTabs[0];
- this.eleActive = this.eleTabs.indexOf(this.activeChartInfo);
- console.log(this.activeChartInfo, "activeChartInfo");
- this.$nextTick(() => {
- this.initChart();
- });
- } else {
- this.eleTabs = [];
- this.hasData = false;
- }
- });
- },
- // 历史数据
- getObData() {
- this.$myRequest({
- url: "/api/api_gateway?method=qxz.data.qxz_ob_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.data;
- 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.tableList = newData;
- this.total = res.count;
- 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;
- }
- .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;
- }
- }
- /* 展开后的要素容器样式 */
- .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;
- .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>
|