eleDetail.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. <template>
  2. <view class="ele-details-page">
  3. <cu-custom :isBack="true">
  4. <template slot="content">
  5. <view class="nav-title">气象/墒情监测</view>
  6. </template>
  7. </cu-custom>
  8. <view class="date-picker" @click="openDatePicker">
  9. <text>{{ startDate }}</text>
  10. <text class="separator">-</text>
  11. <text>{{ endDate }}</text>
  12. <image class="date-icon" src="/static/images/device/date-active.svg" />
  13. </view>
  14. <u-calendar
  15. v-model="show"
  16. :mode="mode"
  17. @change="onDateChange"
  18. ref="calendar"
  19. range-color="#999"
  20. btn-type="success"
  21. active-bg-color="#0BBC58"
  22. range-bg-color="rgba(11,188,88,0.13)"
  23. ></u-calendar>
  24. <view class="echarts-board">
  25. <view class="period-tabs">
  26. <view
  27. class="tab-item"
  28. :class="{ active: activePeriod === '24h' }"
  29. @click="changePeriod('24h')"
  30. >24小时</view
  31. >
  32. <view
  33. class="tab-item"
  34. :class="{ active: activePeriod === '7d' }"
  35. @click="changePeriod('7d')"
  36. >一周</view
  37. >
  38. <view
  39. class="tab-item"
  40. :class="{ active: activePeriod === '30d' }"
  41. @click="changePeriod('30d')"
  42. >一月</view
  43. >
  44. </view>
  45. <view class="element-tabs" v-if="hasData">
  46. <view class="ele-board">
  47. <view
  48. v-for="(item, index) in eleTabsList"
  49. :key="index"
  50. class="ele-item"
  51. :class="{ 'ele-active': eleActive === index }"
  52. @click="changeElement(index,item)"
  53. >{{ item.name }}</view
  54. >
  55. </view>
  56. <view class="fold-icon" @click="toggleFold">
  57. <u-icon :name="isFolded ? 'arrow-down' : 'arrow-up'"></u-icon>
  58. </view>
  59. </view>
  60. <!-- 展开后的所有要素 -->
  61. <view v-if="!isFolded && hasData" class="expanded-elements">
  62. <view
  63. class="expanded-item"
  64. v-for="(item, index) in eleTabsList"
  65. :key="index"
  66. :class="{ 'ele-active': eleActive === index }"
  67. @click="changeElement(index,item)"
  68. >
  69. {{ item.name }}
  70. </view>
  71. </view>
  72. <view class="chart-container" v-if="hasData">
  73. <canvas
  74. v-show="!show"
  75. canvas-id="temperatureChart"
  76. id="temperatureChart"
  77. class="chart"
  78. @touchstart="touchLineA($event)"
  79. @touchmove="moveLineA($event)"
  80. @touchend="touchEndLineA($event)"
  81. disable-scroll="true"
  82. ></canvas>
  83. </view>
  84. <view class="no-data" v-else>
  85. <text>暂无数据</text>
  86. </view>
  87. <!-- <view class="stats-panel">
  88. <view class="stat-item">
  89. <text class="value">27<text class="unit">°C</text></text>
  90. <text class="label">最低值</text>
  91. </view>
  92. <view class="stat-item">
  93. <text class="value">32<text class="unit">°C</text></text>
  94. <text class="label">平均值</text>
  95. </view>
  96. <view class="stat-item">
  97. <text class="value red">42<text class="unit red">°C</text></text>
  98. <text class="label">最高值</text>
  99. </view>
  100. </view> -->
  101. </view>
  102. <view class="history-section">
  103. <view class="section-title">历史数据</view>
  104. <view class="table-wrap">
  105. <view class="fixed-column">
  106. <view class="table-cell header">上报时间</view>
  107. <view class="table-cell" v-for="(item, idx) in tableList" :key="idx">
  108. {{ formatTimestamp(item.time) }}
  109. </view>
  110. </view>
  111. <view class="table-bg"></view>
  112. <scroll-view class="scroll-column" scroll-x="true">
  113. <view class="scroll-content">
  114. <view class="table-row">
  115. <view class="table-cell header">{{
  116. eleKey + "(" + unit + ")"
  117. }}</view>
  118. </view>
  119. <view class="table-row" v-for="(item, idx) in tableList" :key="idx">
  120. <view class="table-cell">{{ item.value }}</view>
  121. </view>
  122. </view>
  123. </scroll-view>
  124. </view>
  125. <view class="pagination">
  126. <view
  127. class="page-item prev"
  128. @click="prevPage"
  129. :class="{ disabled: currentPage === 1 }"
  130. >上一页</view
  131. >
  132. <view class="page-info">
  133. <text class="curret-page">{{ currentPage }}</text>
  134. <text>/</text>
  135. <text>{{ totalPages }}</text>
  136. </view>
  137. <view
  138. class="page-item next"
  139. @click="nextPage"
  140. :class="{ disabled: currentPage >= totalPages }"
  141. >下一页</view
  142. >
  143. </view>
  144. </view>
  145. </view>
  146. </template>
  147. <script>
  148. import uCharts from "../../../components/js_sdk/u-charts/u-charts/u-charts.js";
  149. import baseCharts from "./charts.vue";
  150. export default {
  151. components: { baseCharts },
  152. onLoad(options) {
  153. const deviceInfo = JSON.parse(decodeURIComponent(options.deviceInfo));
  154. this.eleKey = options.eleKey || "";
  155. this.unit = options.unit || "";
  156. this.key = options.key || "";
  157. this.initDateRange(deviceInfo.uptime);
  158. this.deviceInfo = deviceInfo;
  159. this.getChartData();
  160. this.getObData();
  161. },
  162. data() {
  163. return {
  164. unit: "",
  165. key: "",
  166. temperatureChart: null,
  167. activePeriod: "",
  168. activeElement: "temperature",
  169. show: false,
  170. mode: "range",
  171. startDate: "",
  172. endDate: "",
  173. isFolded: true, // 控制要素是否收起
  174. eleTabs: [],
  175. eleTabsList: [],
  176. eleActive: 0,
  177. hasData: true,
  178. tableList: [],
  179. tableAllList: [],
  180. titles: [],
  181. currentPage: 1,
  182. pageSize: 9,
  183. total: 0,
  184. chartData: {
  185. categories: [],
  186. series: [],
  187. },
  188. activeChartInfo: {},
  189. eleKey: "",
  190. };
  191. },
  192. computed: {
  193. totalPages() {
  194. return Math.ceil(this.total / this.pageSize);
  195. },
  196. },
  197. methods: {
  198. formatTimestamp(timestamp) {
  199. if (!timestamp) return '';
  200. const date = new Date(Number(timestamp) * 1000);
  201. const year = date.getFullYear();
  202. const month = String(date.getMonth() + 1).padStart(2, '0');
  203. const day = String(date.getDate()).padStart(2, '0');
  204. const hours = String(date.getHours()).padStart(2, '0');
  205. const minutes = String(date.getMinutes()).padStart(2, '0');
  206. const seconds = String(date.getSeconds()).padStart(2, '0');
  207. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  208. },
  209. initDateRange(timestamp) {
  210. if (!timestamp) {
  211. const today = new Date();
  212. this.endDate = this.formatDate(today);
  213. const startDate = new Date(today);
  214. startDate.setDate(startDate.getDate() - 7);
  215. this.startDate = this.formatDate(startDate);
  216. return;
  217. }
  218. const date = new Date(Number(timestamp) * 1000);
  219. this.endDate = this.formatDate(date);
  220. const startDate = new Date(date);
  221. startDate.setDate(startDate.getDate() - 7);
  222. this.startDate = this.formatDate(startDate);
  223. },
  224. formatDate(date) {
  225. const year = date.getFullYear();
  226. const month = String(date.getMonth() + 1).padStart(2, "0");
  227. const day = String(date.getDate()).padStart(2, "0");
  228. return `${year}-${month}-${day}`;
  229. },
  230. initChart() {
  231. const ctx = uni.createCanvasContext("temperatureChart", this);
  232. const currentEle = this.eleTabs.get(this.key);
  233. // u-charts 的 series.data 必须是数字数组;原数据 value 为字符串(含 "/"、"NA"),
  234. // 直接传对象数组会导致 Y 轴 min/max 计算异常,刻度全部相同
  235. const seriesData = currentEle.map((item) => {
  236. const val = item.value;
  237. if (val === "/" || val === "NA" || val === null || val === undefined || val === "") {
  238. return 0;
  239. }
  240. const num = Number(val);
  241. return isNaN(num) ? 0 : num;
  242. });
  243. const series = [
  244. {
  245. name: currentEle[0].name,
  246. data: seriesData,
  247. lineWidth: 3,
  248. pointShape: "circle",
  249. pointSize: 12,
  250. pointColor: currentEle.color,
  251. pointBorderColor: "#fff",
  252. pointBorderWidth: 2,
  253. symbol: "emptyCircle",
  254. symbolSize: 6,
  255. legendShape: "circle",
  256. },
  257. ];
  258. const xData = currentEle.map((item) => item.time);
  259. this.temperatureChart = new uCharts({
  260. $this: this,
  261. canvasId: "temperatureChart",
  262. type: "line",
  263. context: ctx,
  264. width: 340,
  265. height: 180,
  266. categories: xData,
  267. series: series,
  268. legend: {
  269. position: "top",
  270. show: true,
  271. padding: 5,
  272. fontColor: "#303133",
  273. fontSize: 12,
  274. itemType: "circle",
  275. itemShape: "emptyCircle",
  276. itemWidth: 12,
  277. itemHeight: 12,
  278. lineWidth: 2,
  279. },
  280. xAxis: {
  281. axisLineColor: "#E4E7ED",
  282. disableGrid: true,
  283. fontColor: "#656565",
  284. fontSize: 10,
  285. itemCount: 5,
  286. scrollShow: true,
  287. },
  288. yAxis: {
  289. tofix: 2,
  290. axisLineColor: "#E4E7ED",
  291. fontColor: "#656565",
  292. gridColor: "#E4E7ED",
  293. dashLength: 4,
  294. disableGrid: false,
  295. fontSize: 10,
  296. },
  297. dataLabel: false,
  298. enableScroll: true,
  299. background: "#FFFFFF",
  300. extra: {
  301. line: {
  302. type: "curve",
  303. width: 2,
  304. activeType: "hollow",
  305. },
  306. },
  307. });
  308. },
  309. touchLineA(e) {
  310. this.temperatureChart.scrollStart(e);
  311. },
  312. moveLineA(e) {
  313. this.temperatureChart.scroll(e);
  314. },
  315. touchEndLineA(e) {
  316. this.temperatureChart.scrollEnd(e);
  317. //下面是toolTip事件,如果滚动后不需要显示,可不填写
  318. // this.temperatureChart.showToolTip(e, {
  319. // format: function(item, category) {
  320. // console.log(item, 'iyee', e)
  321. // return category + ' ' + item.name + ':' + item.data
  322. // }
  323. // });
  324. },
  325. // 打开日期选择器
  326. openDatePicker() {
  327. this.show = true;
  328. // 延迟设置日期,确保组件已经初始化
  329. this.$nextTick(() => {
  330. if (this.$refs.calendar) {
  331. // 直接设置组件内部的日期状态
  332. const calendar = this.$refs.calendar;
  333. calendar.startDate = this.startDate;
  334. calendar.endDate = this.endDate;
  335. // 如果有选中的日期范围,设置对应的年、月、日
  336. if (this.startDate) {
  337. const startParts = this.startDate.split("-");
  338. calendar.startYear = parseInt(startParts[0]);
  339. calendar.startMonth = parseInt(startParts[1]);
  340. calendar.startDay = parseInt(startParts[2]);
  341. }
  342. if (this.endDate) {
  343. const endParts = this.endDate.split("-");
  344. calendar.endYear = parseInt(endParts[0]);
  345. calendar.endMonth = parseInt(endParts[1]);
  346. calendar.endDay = parseInt(endParts[2]);
  347. }
  348. // 如果有开始日期,设置日历当前显示的年月为开始日期的年月
  349. if (this.startDate) {
  350. const startParts = this.startDate.split("-");
  351. calendar.year = parseInt(startParts[0]);
  352. calendar.month = parseInt(startParts[1]);
  353. calendar.changeData();
  354. }
  355. }
  356. });
  357. },
  358. // 日期选择变化事件
  359. onDateChange(e) {
  360. this.startDate = e.startDate;
  361. this.endDate = e.endDate;
  362. this.activePeriod = "custom"; // 设置为自定义周期
  363. this.show = false;
  364. this.getChartData();
  365. this.getObData();
  366. },
  367. // 切换时间周期
  368. changePeriod(period) {
  369. this.activePeriod = period;
  370. // 根据选择的周期自动计算日期范围
  371. const now = new Date();
  372. const endDate = now.toISOString().split("T")[0];
  373. const startDate = new Date();
  374. if (period === "24h") {
  375. startDate.setDate(now.getDate());
  376. } else if (period === "7d") {
  377. startDate.setDate(now.getDate() - 7);
  378. } else if (period === "30d") {
  379. startDate.setMonth(now.getMonth() - 1);
  380. }
  381. this.startDate = startDate.toISOString().split("T")[0];
  382. this.endDate = endDate;
  383. this.getChartData();
  384. this.getObData();
  385. },
  386. // 切换要素标签
  387. changeElement(index,item) {
  388. if (this.eleTabs.length === 0) return;
  389. this.eleActive = index;
  390. this.activeChartInfo = this.eleTabs[index];
  391. this.key = item.key
  392. this.isFolded = true; // 关闭展开面板
  393. const newData = [];
  394. this.eleKey = item.name
  395. this.unit = item.unit
  396. this.tableAllList.forEach(item => {
  397. const dat = item?.dat || {};
  398. for(let key in dat){
  399. if(key == this.key){
  400. const value = dat[key] ? dat[key]?.split('#')?.[0] : 'N/A';
  401. newData.push({
  402. time: item.time,
  403. value,
  404. })
  405. }
  406. }
  407. })
  408. this.tableList = newData;
  409. this.$nextTick(() => {
  410. this.initChart();
  411. });
  412. },
  413. getKeyByName(key){
  414. const titles = [...this.titles]
  415. let k
  416. titles.forEach(title => {
  417. if(title.name === key){
  418. k = title.key
  419. }
  420. })
  421. return k
  422. },
  423. // 切换要素展开/收起状态
  424. toggleFold() {
  425. this.isFolded = !this.isFolded;
  426. },
  427. // 上一页
  428. prevPage() {
  429. if (this.currentPage > 1) {
  430. this.currentPage--;
  431. this.getObData();
  432. }
  433. },
  434. // 下一页
  435. nextPage() {
  436. if (this.currentPage < this.totalPages) {
  437. this.currentPage++;
  438. this.getObData();
  439. }
  440. },
  441. // 折线图数据
  442. getChartData() {
  443. this.$myRequest({
  444. url: "/api/api_gateway?method=weather.weather.qxz_data_chart",
  445. data: {
  446. device_id: this.deviceInfo.devBid,
  447. begin: String(
  448. Math.floor(new Date(this.startDate + " 00:00:00").getTime() / 1000),
  449. ),
  450. end: String(
  451. Math.floor(new Date(this.endDate + " 23:59:59").getTime() / 1000),
  452. ),
  453. },
  454. }).then((res) => {
  455. console.log("getChartData:", res);
  456. const resList = res?.data || []
  457. const conf = res?.conf || {}
  458. if (resList && Array.isArray(resList) && resList.length > 0) {
  459. const map = new Map()
  460. resList.forEach(item => {
  461. const dat = item?.dat[1] || {};
  462. for(let key in dat){
  463. if(dat[key]){
  464. const name = conf[key].split('#')[0]
  465. if(map.has(key)){
  466. map.get(key).push({
  467. time: this.formatTime(item.time*1000,'yyyy-MM-dd'),
  468. value: dat[key],
  469. name,
  470. key
  471. })
  472. }else{
  473. map.set(key, [{
  474. time: this.formatTime(item.time*1000,'yyyy-MM-dd'),
  475. value: dat[key],
  476. name,
  477. key
  478. }]
  479. )
  480. }
  481. }
  482. }
  483. })
  484. this.eleTabs = map;
  485. this.eleTabsList = [];
  486. map.forEach((value, key) => {
  487. this.eleTabsList.push({
  488. name: conf[key].split('#')[0],
  489. key,
  490. unit: conf[key].split('#')[1],
  491. value: value,
  492. })
  493. })
  494. this.hasData = true;
  495. this.$nextTick(() => {
  496. this.initChart();
  497. });
  498. } else {
  499. this.eleTabs = [];
  500. this.hasData = false;
  501. }
  502. });
  503. },
  504. // 历史数据
  505. getObData() {
  506. this.$myRequest({
  507. url: "/api/api_gateway?method=weather.weather.qxz_detail",
  508. data: {
  509. device_id: this.deviceInfo.devBid,
  510. start_time: String(
  511. Math.floor(new Date(this.startDate + " 00:00:00").getTime() / 1000),
  512. ),
  513. end_time: String(
  514. Math.floor(new Date(this.endDate + " 23:59:59").getTime() / 1000),
  515. ),
  516. page: this.currentPage,
  517. page_size: this.pageSize,
  518. },
  519. }).then((res) => {
  520. console.log("getObData:", res);
  521. const data = res.data || [];
  522. const newData = [];
  523. this.tableAllList = data;
  524. data.forEach(item => {
  525. const dat = item?.dat || {};
  526. for(let key in dat){
  527. if(key == this.key){
  528. newData.push({
  529. time: item.time,
  530. value: dat[key].split('#')[0],
  531. })
  532. }
  533. }
  534. })
  535. this.titles = res?.title || [];
  536. this.tableList = newData;
  537. this.total = res.nums;
  538. });
  539. },
  540. convertDynamic(data, getLabelValue) {
  541. if (!data || typeof data !== "object") return [];
  542. const result = [];
  543. const keys = Object.keys(data);
  544. for (const prop of keys) {
  545. const arr = data[prop];
  546. const value = getLabelValue(prop, arr, data);
  547. if (value !== undefined && value !== null) {
  548. result.push({
  549. label: String(value),
  550. prop,
  551. align: "center",
  552. });
  553. }
  554. }
  555. return result;
  556. },
  557. processData(data) {
  558. return Object.entries(data).reduce((acc, [key, value]) => {
  559. acc[key] = Array.isArray(value) ? value[0] : value;
  560. return acc;
  561. }, {});
  562. },
  563. },
  564. };
  565. </script>
  566. <style lang="scss">
  567. .ele-details-page {
  568. background:
  569. linear-gradient(180deg, #ffffff00 0%, #f5f6fa 23.64%, #f5f6fa 100%),
  570. linear-gradient(102deg, #bfeadd 6.77%, #b8f1e7 40.15%, #b9eef5 84.02%);
  571. min-height: 100vh;
  572. padding: 0 32rpx;
  573. padding-top: 98rpx;
  574. }
  575. .nav-title{
  576. text-align: center;
  577. }
  578. .date-picker {
  579. display: flex;
  580. align-items: center;
  581. justify-content: space-between;
  582. background-color: #fff;
  583. border-radius: 40rpx;
  584. padding: 16rpx 24rpx;
  585. margin-bottom: 36rpx;
  586. font-size: 28rpx;
  587. color: #999;
  588. margin-top: 46rpx;
  589. .date-icon {
  590. width: 32rpx;
  591. height: 32rpx;
  592. }
  593. }
  594. .separator {
  595. margin: 0 16rpx;
  596. }
  597. .echarts-board {
  598. padding: 32rpx;
  599. border-radius: 16rpx;
  600. background: #fff;
  601. margin-bottom: 24rpx;
  602. position: relative;
  603. .period-tabs {
  604. width: 350rpx;
  605. height: 64rpx;
  606. display: flex;
  607. justify-content: space-between;
  608. align-items: center;
  609. margin: auto;
  610. margin-bottom: 12rpx;
  611. border-radius: 32rpx;
  612. background: #f1f4f8;
  613. padding: 8rpx 10rpx;
  614. color: #303133;
  615. font-family: "Source Han Sans CN VF";
  616. font-size: 26rpx;
  617. font-style: normal;
  618. font-weight: 400;
  619. line-height: normal;
  620. .tab-item {
  621. padding: 5rpx 24rpx;
  622. height: 48rpx;
  623. line-height: 48rpx;
  624. border-radius: 32rpx;
  625. }
  626. .active {
  627. background: #fff;
  628. }
  629. }
  630. .element-tabs {
  631. display: flex;
  632. justify-content: space-between;
  633. align-items: center;
  634. overflow-x: auto;
  635. white-space: nowrap;
  636. .ele-board {
  637. flex: 1;
  638. display: flex;
  639. align-items: center;
  640. overflow-x: auto;
  641. overflow-y: hidden;
  642. white-space: nowrap;
  643. }
  644. .ele-item {
  645. position: relative;
  646. color: #999999;
  647. font-size: 28rpx;
  648. font-weight: 400;
  649. white-space: nowrap;
  650. margin-right: 16rpx;
  651. padding: 14rpx 16rpx;
  652. cursor: pointer;
  653. }
  654. .ele-active {
  655. color: #303133;
  656. font-weight: 500;
  657. &::after {
  658. content: "";
  659. position: absolute;
  660. left: 50%;
  661. transform: translateX(-50%);
  662. bottom: 4rpx;
  663. display: block;
  664. width: 30rpx;
  665. height: 8rpx;
  666. border-radius: 4rpx;
  667. background: #1fc676;
  668. }
  669. }
  670. .fold-icon {
  671. width: 48rpx;
  672. border-left: 2px solid #999;
  673. padding-left: 16rpx;
  674. margin-left: 16rpx;
  675. cursor: pointer;
  676. display: none;
  677. }
  678. }
  679. /* 展开后的要素容器样式 */
  680. .expanded-elements {
  681. position: absolute;
  682. top: 126rpx;
  683. left: 0;
  684. right: 0;
  685. background: #f5f7fa;
  686. border-radius: 12rpx;
  687. padding: 16rpx;
  688. display: flex;
  689. flex-wrap: wrap;
  690. gap: 16rpx;
  691. z-index: 10;
  692. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  693. }
  694. .expanded-item {
  695. flex: 0 0 calc(33.33% - 16rpx);
  696. text-align: center;
  697. padding: 16rpx 0;
  698. border-radius: 8rpx;
  699. color: #999999;
  700. font-size: 26rpx;
  701. background: #ffffff;
  702. cursor: pointer;
  703. transition: all 0.3s ease;
  704. }
  705. .expanded-item.ele-active {
  706. color: #1fc676;
  707. background: #e6f7ef;
  708. font-weight: 500;
  709. }
  710. .chart-container {
  711. border-radius: 16rpx;
  712. margin-bottom: 24rpx;
  713. position: relative;
  714. padding: 10rpx;
  715. overflow: hidden;
  716. height: 336rpx;
  717. z-index:0;
  718. .chart {
  719. width: 100%;
  720. height: 336rpx;
  721. }
  722. }
  723. .no-data {
  724. border-radius: 16rpx;
  725. margin-bottom: 24rpx;
  726. padding: 10rpx;
  727. height: 336rpx;
  728. display: flex;
  729. align-items: center;
  730. justify-content: center;
  731. background-color: #f9f9f9;
  732. text {
  733. color: #999;
  734. font-size: 32rpx;
  735. }
  736. }
  737. .stats-panel {
  738. display: flex;
  739. justify-content: space-between;
  740. background-color: #f1f4f8;
  741. border-radius: 16rpx;
  742. padding: 24rpx 64rpx;
  743. .stat-item {
  744. display: flex;
  745. flex-direction: column;
  746. align-items: center;
  747. .label {
  748. color: #656565;
  749. font-size: 24rpx;
  750. font-weight: 400;
  751. }
  752. .value {
  753. font-size: 40rpx;
  754. font-weight: bold;
  755. }
  756. .unit {
  757. color: #999999;
  758. font-size: 20rpx;
  759. }
  760. .red {
  761. color: #fb4e52;
  762. }
  763. }
  764. }
  765. }
  766. .history-section {
  767. background-color: #fff;
  768. border-radius: 16rpx;
  769. padding: 24rpx 32rpx;
  770. .section-title {
  771. font-size: 28rpx;
  772. font-weight: bold;
  773. margin-bottom: 16rpx;
  774. color: #303133;
  775. }
  776. .table-wrap {
  777. display: flex;
  778. width: 100%;
  779. box-sizing: border-box;
  780. margin: 20rpx 0;
  781. color: #042118;
  782. font-family: "Source Han Sans CN VF";
  783. font-size: 24rpx;
  784. font-style: normal;
  785. font-weight: 400;
  786. line-height: normal;
  787. .fixed-column {
  788. width: 250rpx;
  789. .table-cell {
  790. width: 100%;
  791. white-space: nowrap;
  792. }
  793. }
  794. .table-bg {
  795. width: 16rpx;
  796. background: linear-gradient(270deg, #ffffff33 0%, #9598a433 100%);
  797. }
  798. .scroll-column {
  799. flex: 1;
  800. height: auto;
  801. overflow: hidden;
  802. margin-left: -10rpx;
  803. }
  804. .scroll-content {
  805. min-width: 100%;
  806. width: fit-content;
  807. display: flex;
  808. flex-direction: column;
  809. .table-row {
  810. display: flex;
  811. .table-cell {
  812. flex: 1;
  813. }
  814. }
  815. }
  816. .table-cell {
  817. width: 160rpx;
  818. height: 64rpx;
  819. line-height: 64rpx;
  820. text-align: center;
  821. border-bottom: 2px solid #e4e7ed;
  822. box-sizing: border-box;
  823. }
  824. .table-cell.header {
  825. background: #f6f8fc;
  826. }
  827. }
  828. .pagination {
  829. display: flex;
  830. align-items: center;
  831. justify-content: space-between;
  832. margin-top: 24rpx;
  833. .page-item {
  834. width: 104rpx;
  835. height: 48rpx;
  836. color: #656565;
  837. font-size: 24rpx;
  838. line-height: 48rpx;
  839. text-align: center;
  840. border: 2rpx solid #e4e7ed;
  841. border-radius: 8rpx;
  842. }
  843. .page-item.disabled {
  844. color: #999;
  845. border-color: #e4e7ed;
  846. background-color: #f5f7fa;
  847. }
  848. .next {
  849. color: #0bbc58;
  850. border: 2rpx solid #0bbc58;
  851. }
  852. .next.disabled {
  853. color: #999;
  854. border-color: #e4e7ed;
  855. background-color: #f5f7fa;
  856. }
  857. .page-info {
  858. color: #999999;
  859. font-family: "Source Han Sans CN VF";
  860. font-size: 24rpx;
  861. font-style: normal;
  862. font-weight: 400;
  863. line-height: normal;
  864. .curret-page {
  865. color: #303133;
  866. }
  867. }
  868. }
  869. }
  870. ::v-deep .u-calendar__action {
  871. display: flex;
  872. justify-content: center;
  873. }
  874. </style>