rodentAnalysis.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. <template>
  2. <view class="rodent-analysis">
  3. <!-- 发生趋势分析 -->
  4. <view class="analysis-card">
  5. <view class="analysis-card__header">
  6. <view class="analysis-card__title">发生趋势分析</view>
  7. <!-- <view class="range-tabs">
  8. <view
  9. class="range-tab"
  10. :class="{ active: trendRange === 'week' }"
  11. @click="switchTrendRange('week')"
  12. >周</view>
  13. <view
  14. class="range-tab"
  15. :class="{ active: trendRange === 'month' }"
  16. @click="switchTrendRange('month')"
  17. >月</view>
  18. <view
  19. class="range-tab"
  20. :class="{ active: trendRange === 'year' }"
  21. @click="switchTrendRange('year')"
  22. >年</view>
  23. </view> -->
  24. </view>
  25. <view class="trend-legend">
  26. <view class="trend-legend__item" v-for="item in trendLegend" :key="item.name">
  27. <view class="trend-legend__line" :style="{ background: item.color }">
  28. <view class="trend-legend__dot" :style="{ borderColor: item.color }"></view>
  29. </view>
  30. <text class="trend-legend__text">{{ item.name }}</text>
  31. </view>
  32. </view>
  33. <view class="trend-axis-label">鼠害数量:只</view>
  34. <view class="chart-wrap chart-wrap--trend">
  35. <qiun-data-charts
  36. type="line"
  37. :chartData="trendChart"
  38. :opts="trendOpts"
  39. :reshow="active"
  40. :canvas2d="true"
  41. :inScrollView="true"
  42. :ontouch="true"
  43. />
  44. </view>
  45. </view>
  46. <!-- 鼠害活动时段分析 -->
  47. <view class="analysis-card">
  48. <view class="analysis-card__header">
  49. <view class="analysis-card__title">鼠害活动时段分析</view>
  50. <view class="analysis-card__extra">
  51. <text class="peak-label">活跃高峰</text>
  52. <text class="peak-value">{{ peakRange }}</text>
  53. </view>
  54. </view>
  55. <view class="chart-legend">
  56. <view class="chart-legend__dot chart-legend__dot--bar"></view>
  57. <text class="chart-legend__text">鼠害数量</text>
  58. </view>
  59. <view class="chart-wrap chart-wrap--rhythm">
  60. <qiun-data-charts
  61. type="column"
  62. :chartData="rhythmChart"
  63. :opts="rhythmOpts"
  64. :reshow="active"
  65. :canvas2d="true"
  66. :inScrollView="true"
  67. :ontouch="true"
  68. />
  69. </view>
  70. </view>
  71. <!-- 鼠害占比 -->
  72. <view class="analysis-card">
  73. <view class="analysis-card__header">
  74. <view class="analysis-card__title">鼠害占比</view>
  75. </view>
  76. <view class="community-content">
  77. <view class="chart-wrap chart-wrap--ring">
  78. <qiun-data-charts
  79. type="ring"
  80. :chartData="communityChart"
  81. :opts="communityOpts"
  82. :reshow="active"
  83. :canvas2d="true"
  84. :inScrollView="true"
  85. :ontouch="true"
  86. :tooltip-show="false"
  87. @getIndex="onRingIndex"
  88. />
  89. </view>
  90. <view class="community-total">
  91. <text>总鼠害数量</text>
  92. <text class="total-value">{{ totalCount }}
  93. <text class="unit">只</text>
  94. </text>
  95. </view>
  96. <view class="community-list">
  97. <view
  98. class="community-item"
  99. :class="{ active: communitySelected === index }"
  100. v-for="(item, index) in communityList"
  101. :key="index"
  102. @click="onCommunityClick(index)"
  103. >
  104. <view class="community-item__dot" :style="{ background: item.color }"></view>
  105. <view class="community-item__name">{{ item.name }}</view>
  106. <view class="community-item__count">{{ item.count }}只</view>
  107. <view class="community-item__percent">{{ item.percent }}%</view>
  108. </view>
  109. </view>
  110. </view>
  111. </view>
  112. <!-- 鼠情发生量与气象关系 -->
  113. <view class="analysis-card">
  114. <view class="analysis-card__header">
  115. <view class="analysis-card__title">鼠情发生量与气象关系</view>
  116. <!-- <view class="range-tabs">
  117. <view
  118. class="range-tab"
  119. :class="{ active: weatherRange === 'week' }"
  120. @click="switchRange('week')"
  121. >周</view>
  122. <view
  123. class="range-tab"
  124. :class="{ active: weatherRange === 'month' }"
  125. @click="switchRange('month')"
  126. >月</view>
  127. <view
  128. class="range-tab"
  129. :class="{ active: weatherRange === 'year' }"
  130. @click="switchRange('year')"
  131. >年</view>
  132. </view> -->
  133. </view>
  134. <view class="chart-wrap chart-wrap--weather">
  135. <qiun-data-charts
  136. type="mix"
  137. :chartData="weatherChart"
  138. :opts="weatherOpts"
  139. :reshow="active"
  140. :canvas2d="true"
  141. :inScrollView="true"
  142. :ontouch="true"
  143. />
  144. </view>
  145. </view>
  146. </view>
  147. </template>
  148. <script>
  149. // 发生趋势分析 / 鼠情发生量与气象关系 共用接口(按天统计)
  150. const ANALYSIS_URL = '/api/api_gateway?method=forecast.shuhai.shuhai_two_camera_photo_day_analysis';
  151. // 鼠害活动时段分析 / 鼠害占比 共用接口(按时段统计)
  152. const PERIOD_URL = '/api/api_gateway?method=forecast.shuhai.shuhai_two_camera_photo_period_analysis';
  153. // 周/月/年 对应的统计天数
  154. const RANGE_DAYS = { week: 7, month: 30, year: 365 };
  155. // 趋势图配色:系列为 target_detail 里的各物种,数量不定,循环取色
  156. const TREND_PALETTE = ['#FF5252', '#0BBC58', '#3D5AFE', '#FFC107', '#9C27B0', '#00BCD4', '#FF7043', '#8BC34A', '#7986CB', '#F06292'];
  157. export default {
  158. name: 'RodentAnalysis',
  159. props: {
  160. // 当前 tab 是否激活(tab 用 v-show 切换时,隐藏状态下 canvas 容器测量为 0,需激活后重绘)
  161. active: {
  162. type: Boolean,
  163. default: true
  164. },
  165. // 设备 id:发生趋势 / 气象关系接口入参
  166. deviceId: {
  167. type: [String, Number],
  168. default: ''
  169. }
  170. },
  171. data() {
  172. return {
  173. // 发生趋势
  174. trendRange: 'month',
  175. trendChart: null,
  176. trendOpts: {
  177. // 颜色顺序与 series 一一对应
  178. color: ['#FF5252', '#0BBC58', '#3D5AFE'],
  179. dataLabel: false,
  180. // 不绘制折线上的数据点圆点
  181. dataPointShape: false,
  182. // 图例用卡片内自定义样式(见 template 的 trend-legend)
  183. legend: { show: false },
  184. xAxis: {
  185. disableGrid: true,
  186. fontSize: 9,
  187. fontColor: '#656565',
  188. // 'justify' 点落在刻度线上(线条铺满左右);'center' 点位于两刻度中间
  189. boundaryGap: 'justify',
  190. labelCount: 6
  191. },
  192. yAxis: {
  193. disableGrid: false,
  194. gridType: 'dash',
  195. gridColor: '#EEEEEE',
  196. data: [{
  197. min: 0,
  198. max: 50,
  199. splitNumber: 5,
  200. fontSize: 10,
  201. fontColor: '#656565',
  202. tofix: 0
  203. }]
  204. },
  205. extra: {
  206. line: {
  207. type: 'curve',
  208. width: 2,
  209. activeType: 'none'
  210. }
  211. }
  212. },
  213. // 活动节律
  214. peakRange: '--',
  215. rhythmChart: null,
  216. rhythmOpts: {
  217. color: ['#FFC107'],
  218. dataLabel: false,
  219. // 背景轨道系列会进 uCharts 图例,改用卡片内自定义图例(见 template 的 chart-legend)
  220. legend: { show: false },
  221. xAxis: {
  222. disableGrid: true,
  223. fontSize: 9,
  224. fontColor: '#656565'
  225. },
  226. yAxis: {
  227. showTitle: true,
  228. disableGrid: false,
  229. gridType: 'dash',
  230. gridColor: '#EEEEEE',
  231. data: [{
  232. title: '数量:只',
  233. titleFontSize: 10,
  234. titleFontColor: '#656565',
  235. titleOffsetX: -10,
  236. titleOffsetY: -6,
  237. min: 0,
  238. max: 100,
  239. splitNumber: 5,
  240. tofix: 0
  241. }]
  242. },
  243. extra: {
  244. column: {
  245. // 温度计模式:系列[0]绘制为整柱浅色背景轨道,系列[1]叠加实际数据柱
  246. type: 'meter',
  247. meterBorder: 0,
  248. meterFillColor: '#d6dbed66',
  249. width: 14,
  250. barBorderCircle: false,
  251. linearType: 'none'
  252. }
  253. }
  254. },
  255. // 群落结构
  256. communityList: [],
  257. // 当前选中的占比列表项索引(-1 为未选中)
  258. communitySelected: -1,
  259. communityChart: null,
  260. communityOpts: {
  261. color: ['#E53935', '#FFC107', '#2196F3', '#9E9E9E'],
  262. // 点击联动会触发整图重绘(canvas 无局部更新),关掉动画避免每次点击重放入场动画
  263. animation: false,
  264. legend: { show: false },
  265. // 环形图:中心显示当前占比最高的物种
  266. dataLabel: false,
  267. // uCharts 以 opts.tooltip.index 命中扇区高亮(-1 为不高亮),点击列表时更新
  268. tooltip: { index: -1 },
  269. title: {
  270. name: '0%',
  271. fontSize: 16,
  272. color: '#E53935',
  273. offsetY: -6
  274. },
  275. subtitle: {
  276. name: '--',
  277. fontSize: 10,
  278. color: '#333333',
  279. offsetY: 8
  280. },
  281. extra: {
  282. ring: {
  283. // 环粗细约为半径的三分之一
  284. ringWidth: 16,
  285. offsetAngle: 0,
  286. // 去掉分段之间的白色分隔线(qiun-data-charts 的 ring 默认 border: true)
  287. border: false,
  288. // 关闭点击分段时外扩+半透明的高亮效果(默认 activeRadius: 10 / activeOpacity: 0.5)
  289. activeRadius: 5,
  290. activeOpacity: 1
  291. }
  292. }
  293. },
  294. // 总量
  295. totalCount: 0,
  296. weekOverWeek: '+12.8%',
  297. alarmThreshold: 100,
  298. barMax: 130,
  299. // 鼠情与气象(mock)
  300. weatherRange: 'month',
  301. weatherChart: null,
  302. weatherOpts: {
  303. color: ['#FF5252', '#FFC107', '#2196F3'],
  304. dataLabel: false,
  305. legend: {
  306. show: true,
  307. position: 'bottom',
  308. fontSize: 10,
  309. fontColor: '#656565',
  310. itemGap: 15,
  311. lineHeight: 15
  312. },
  313. xAxis: {
  314. disableGrid: true,
  315. fontSize: 9,
  316. fontColor: '#656565'
  317. },
  318. yAxis: {
  319. showTitle: true,
  320. disableGrid: false,
  321. gridType: 'dash',
  322. gridColor: '#EEEEEE',
  323. data: [
  324. {
  325. title: '鼠害数量:只',
  326. titleFontSize: 10,
  327. titleFontColor: '#656565',
  328. titleOffsetX: -10,
  329. titleOffsetY: -6,
  330. position: 'left',
  331. min: 0,
  332. max: 10,
  333. splitNumber: 5,
  334. // util.toFixed 会把 tofix:0 当 falsy 回退成 2 位小数,改用 formatter 保证整数刻度
  335. formatter: val => Math.round(val) + ''
  336. },
  337. {
  338. title: '湿度:%',
  339. titleFontSize: 10,
  340. titleFontColor: '#656565',
  341. titleOffsetX: 10,
  342. titleOffsetY: -6,
  343. position: 'right',
  344. min: 0,
  345. splitNumber: 5,
  346. formatter: val => Math.round(val) + ''
  347. }
  348. ]
  349. },
  350. extra: {
  351. mix: {
  352. line: { width: 2 },
  353. area: { opacity: 0.15, gradient: true }
  354. },
  355. tooltip: {
  356. showBox: true
  357. }
  358. }
  359. }
  360. };
  361. },
  362. computed: {
  363. // 趋势图图例:跟随接口返回的物种系列
  364. trendLegend() {
  365. if (!this.trendChart) return [];
  366. return this.trendChart.series.map(s => ({ name: s.name, color: s.color }));
  367. },
  368. // 进度条填充:总量/量程
  369. fillPercent() {
  370. return Math.min((this.totalCount / this.barMax) * 100, 100);
  371. },
  372. // 告警阈值标记位置
  373. thresholdPercent() {
  374. return (this.alarmThreshold / this.barMax) * 100;
  375. }
  376. },
  377. mounted() {
  378. // 延迟赋值图表数据:等页面布局稳定后再初始化 canvas,避免容器尺寸测量为 0
  379. setTimeout(() => {
  380. this.initChartData();
  381. }, 300);
  382. },
  383. watch: {
  384. // 设备信息晚于组件挂载到达时重新拉取
  385. deviceId(val) {
  386. if (!val) return;
  387. this.fetchAnalysis(this.trendRange, 'trend');
  388. this.fetchAnalysis(this.weatherRange, 'weather');
  389. this.fetchPeriod();
  390. }
  391. },
  392. methods: {
  393. initChartData() {
  394. this.fetchAnalysis(this.trendRange, 'trend');
  395. this.fetchAnalysis(this.weatherRange, 'weather');
  396. this.fetchPeriod();
  397. },
  398. switchRange(range) {
  399. if (this.weatherRange === range) return;
  400. this.weatherRange = range;
  401. this.fetchAnalysis(range, 'weather');
  402. },
  403. switchTrendRange(range) {
  404. if (this.trendRange === range) return;
  405. this.trendRange = range;
  406. this.fetchAnalysis(range, 'trend');
  407. },
  408. // 拉取按天分析数据,chartKey 区分构建趋势图还是气象图
  409. async fetchAnalysis(range, chartKey) {
  410. if (!this.deviceId) return;
  411. const { start, end } = this.getRangeTime(range);
  412. try {
  413. const res = await this.$myRequest({
  414. url: ANALYSIS_URL,
  415. method: 'POST',
  416. data: {
  417. device_id: this.deviceId,
  418. start_time: start,
  419. end_time: end
  420. }
  421. });
  422. const dayList = (res && res.day_list) || [];
  423. if (!dayList.length) return;
  424. if (chartKey === 'trend') {
  425. this.trendChart = this.buildTrendChart(dayList);
  426. } else {
  427. this.weatherChart = this.buildWeatherChart(dayList);
  428. }
  429. } catch (e) {
  430. // $myRequest 已统一 toast 错误信息
  431. }
  432. },
  433. // 周/月/年 对应的起止时间戳(秒)
  434. getRangeTime(range) {
  435. const end = Math.floor(Date.now() / 1000);
  436. const start = end - (RANGE_DAYS[range] || 30) * 24 * 60 * 60;
  437. return { start, end };
  438. },
  439. // 发生趋势:x 轴为日期,系列为 target_detail 里的各物种
  440. buildTrendChart(dayList) {
  441. const categories = [];
  442. const detailRows = [];
  443. dayList.forEach(day => {
  444. categories.push((day.date || '').slice(5));
  445. detailRows.push(day.target_detail || {});
  446. });
  447. // 汇总所有出现过的物种(按首次出现顺序)
  448. const names = [];
  449. detailRows.forEach(row => {
  450. Object.keys(row).forEach(name => {
  451. if (names.indexOf(name) === -1) names.push(name);
  452. });
  453. });
  454. const series = names.map((name, index) => ({
  455. name: name,
  456. color: TREND_PALETTE[index % TREND_PALETTE.length],
  457. data: detailRows.map(row => row[name] || 0)
  458. }));
  459. // Y 轴上限随数据量调整,向上取整到 10 的倍数
  460. let maxVal = 0;
  461. series.forEach(s => {
  462. s.data.forEach(v => {
  463. if (v > maxVal) maxVal = v;
  464. });
  465. });
  466. this.trendOpts.yAxis.data[0].max = Math.max(10, Math.ceil(maxVal / 10) * 10);
  467. // 单天数据画不出折线,改为显示数据点;boundaryGap 切为 'center' 让点位/刻度居中,多天时 'justify' 贴边铺满
  468. this.trendOpts.dataPointShape = dayList.length === 1;
  469. this.trendOpts.xAxis.boundaryGap = dayList.length === 1 ? 'center' : 'justify';
  470. return { categories, series };
  471. },
  472. // 气象关系:鼠害总数 + 温度 + 湿度
  473. buildWeatherChart(dayList) {
  474. // mix 图单点数据画不出折线,addPoint 让其显示为数据点
  475. const single = dayList.length === 1;
  476. // 左轴上限向上取整到 5 的倍数,保证 5 等分后刻度为整数
  477. let maxTotal = 0;
  478. dayList.forEach(day => {
  479. if ((day.total || 0) > maxTotal) maxTotal = day.total || 0;
  480. });
  481. this.weatherOpts.yAxis.data[0].max = Math.max(10, Math.ceil(maxTotal / 5) * 5);
  482. return {
  483. categories: dayList.map(day => (day.date || '').slice(5)),
  484. series: [
  485. { name: '鼠害总数', type: 'area', style: 'curve', index: 0, addPoint: single, data: dayList.map(day => day.total || 0) },
  486. { name: '温度', type: 'line', style: 'curve', index: 1, addPoint: single, data: dayList.map(day => day.temperature || 0) },
  487. { name: '湿度', type: 'line', style: 'curve', index: 1, addPoint: single, data: dayList.map(day => day.humidity || 0) }
  488. ]
  489. };
  490. },
  491. // 拉取时段分析数据(活动时段 + 鼠害占比共用),默认统计近 30 天
  492. async fetchPeriod() {
  493. if (!this.deviceId) return;
  494. const { start, end } = this.getRangeTime('month');
  495. try {
  496. const res = await this.$myRequest({
  497. url: PERIOD_URL,
  498. method: 'POST',
  499. data: {
  500. device_id: this.deviceId,
  501. start_time: start,
  502. end_time: end
  503. }
  504. });
  505. if (!res) return;
  506. this.buildRhythmChart(res.period_list || []);
  507. this.buildCommunity(res.total || 0, res.target_detail || {});
  508. } catch (e) {
  509. // $myRequest 已统一 toast 错误信息
  510. }
  511. },
  512. // 活动时段:meter 柱状图,系列[0]为背景轨道,系列[1]为各时段数量
  513. buildRhythmChart(periodList) {
  514. if (!periodList.length) return;
  515. const categories = [];
  516. const totals = [];
  517. let maxTotal = 0;
  518. let peakIndex = -1;
  519. periodList.forEach((item, index) => {
  520. categories.push(this.formatHour(item.hour));
  521. const total = item.total || 0;
  522. totals.push(total);
  523. if (total > maxTotal) {
  524. maxTotal = total;
  525. peakIndex = index;
  526. }
  527. });
  528. // 活跃高峰:数量最多的时段
  529. this.peakRange = peakIndex > -1 ? (periodList[peakIndex].period || this.formatHour(periodList[peakIndex].hour)) : '--';
  530. // 量程随数据调整,背景轨道与量程等高
  531. const rangeMax = Math.max(10, Math.ceil(maxTotal / 10) * 10);
  532. this.rhythmOpts.yAxis.data[0].max = rangeMax;
  533. this.rhythmChart = {
  534. categories,
  535. series: [
  536. { name: '背景', color: '#D6DBED', data: periodList.map(() => rangeMax) },
  537. { name: '鼠害数量', color: '#FFC107', data: totals }
  538. ]
  539. };
  540. },
  541. // 0 → '00:00'
  542. formatHour(hour) {
  543. const h = hour || 0;
  544. return (h < 10 ? '0' + h : '' + h) + ':00';
  545. },
  546. // 鼠害占比:环形图 + 列表,物种来自 target_detail
  547. buildCommunity(total, detail) {
  548. const names = Object.keys(detail);
  549. const list = names.map((name, index) => {
  550. const count = detail[name] || 0;
  551. return {
  552. name: name,
  553. count: count,
  554. percent: total ? Math.round((count / total) * 100) : 0,
  555. color: TREND_PALETTE[index % TREND_PALETTE.length]
  556. };
  557. });
  558. this.communityList = list;
  559. this.communitySelected = -1;
  560. this.totalCount = total;
  561. this.communityOpts.tooltip = { index: -1 };
  562. this.communityChart = {
  563. series: [{
  564. data: list.map(item => ({ name: item.name, value: item.count }))
  565. }]
  566. };
  567. // 环形中心显示占比最高的物种
  568. if (list.length) {
  569. const top = list.reduce((a, b) => (b.count > a.count ? b : a), list[0]);
  570. this.communityOpts.title.name = (total ? Math.round((top.count / total) * 100) : 0) + '%';
  571. this.communityOpts.subtitle.name = top.name;
  572. }
  573. },
  574. // 点击占比列表:环形图高亮对应扇区,中心数据联动;再次点击取消选中
  575. onCommunityClick(index) {
  576. const item = this.communityList[index];
  577. if (!item) return;
  578. const next = this.communitySelected === index ? -1 : index;
  579. this.communitySelected = next;
  580. const target = next > -1
  581. ? item
  582. : this.communityList.reduce((a, b) => (b.count > a.count ? b : a), this.communityList[0]);
  583. this.communityOpts.title.name = target.percent + '%';
  584. this.communityOpts.subtitle.name = target.name;
  585. this.communityOpts.tooltip = { index: next };
  586. },
  587. // 点击环形图扇区:中心数据联动,复用列表点击逻辑
  588. onRingIndex(e) {
  589. if (!e || typeof e.currentIndex !== 'number' || e.currentIndex < 0) return;
  590. this.onCommunityClick(e.currentIndex);
  591. }
  592. }
  593. };
  594. </script>
  595. <style lang="scss" scoped>
  596. .rodent-analysis {
  597. width: 100%;
  598. }
  599. .analysis-card {
  600. width: 100%;
  601. padding: 32rpx;
  602. box-sizing: border-box;
  603. background: #fff;
  604. border-radius: 16rpx;
  605. margin-bottom: 24rpx;
  606. &:last-child {
  607. margin-bottom: 0;
  608. }
  609. }
  610. .analysis-card__header {
  611. display: flex;
  612. align-items: center;
  613. justify-content: space-between;
  614. margin-bottom: 20rpx;
  615. }
  616. .analysis-card__title {
  617. position: relative;
  618. font-size: 32rpx;
  619. color: #042118;
  620. font-family: 'Source Han Sans CN VF';
  621. font-weight: 700;
  622. }
  623. .analysis-card__extra {
  624. display: flex;
  625. align-items: center;
  626. .peak-label {
  627. font-size: 24rpx;
  628. color: #656565;
  629. font-family: 'Source Han Sans CN VF';
  630. margin-right: 8rpx;
  631. }
  632. .peak-value {
  633. font-size: 24rpx;
  634. color: #E53935;
  635. font-family: 'Source Han Sans CN VF';
  636. font-weight: 700;
  637. }
  638. }
  639. .chart-legend {
  640. display: flex;
  641. align-items: center;
  642. justify-content: flex-end;
  643. margin-bottom:-20rpx;
  644. .chart-legend__dot--bar {
  645. width: 16rpx;
  646. height: 16rpx;
  647. border-radius: 4rpx;
  648. background: #FFC107;
  649. margin-right: 8rpx;
  650. }
  651. .chart-legend__text {
  652. font-size: 20rpx;
  653. color: #656565;
  654. font-family: 'Source Han Sans CN VF';
  655. }
  656. }
  657. .chart-wrap {
  658. position: relative;
  659. width: 100%;
  660. }
  661. .chart-wrap--rhythm {
  662. height: 400rpx;
  663. }
  664. .chart-wrap--ring {
  665. width: 360rpx;
  666. height: 320rpx;
  667. flex-shrink: 0;
  668. }
  669. .chart-wrap--weather {
  670. height: 420rpx;
  671. }
  672. // 发生趋势分析:自定义图例(曲线图例:短线 + 空心圆点)
  673. .trend-legend {
  674. display: flex;
  675. flex-wrap: wrap;
  676. padding: 20rpx 24rpx;
  677. background: #F5F6FA;
  678. border-radius: 12rpx;
  679. margin-bottom: 20rpx;
  680. }
  681. .trend-legend__item {
  682. display: flex;
  683. align-items: center;
  684. width: 33.33%;
  685. box-sizing: border-box;
  686. padding: 8rpx 0;
  687. }
  688. .trend-legend__line {
  689. position: relative;
  690. width: 40rpx;
  691. height: 4rpx;
  692. margin-right: 12rpx;
  693. flex-shrink: 0;
  694. }
  695. .trend-legend__dot {
  696. position: absolute;
  697. left: 50%;
  698. top: 50%;
  699. transform: translate(-50%, -50%);
  700. width: 16rpx;
  701. height: 16rpx;
  702. border-radius: 50%;
  703. background: #fff;
  704. border: 4rpx solid #fff;
  705. box-sizing: border-box;
  706. }
  707. .trend-legend__text {
  708. font-size: 26rpx;
  709. color: #042118;
  710. font-family: 'Source Han Sans CN VF';
  711. }
  712. .trend-axis-label {
  713. font-size: 22rpx;
  714. color: #B3B9C2;
  715. font-family: 'Source Han Sans CN VF';
  716. margin-bottom: 4rpx;
  717. }
  718. .chart-wrap--trend {
  719. height: 420rpx;
  720. }
  721. // 群落结构:上下布局,环形图在上,图例列表在下
  722. .community-content {
  723. display: flex;
  724. flex-direction: column;
  725. align-items: center;
  726. }
  727. .community-total{
  728. width: 90%;
  729. display: flex;
  730. align-items: center;
  731. justify-content: space-between;
  732. font-family: 'Source Han Sans CN VF';
  733. .total-value {
  734. font-size: 32rpx;
  735. color: #042118;
  736. font-weight: 700;
  737. line-height: 1;
  738. }
  739. .unit {
  740. font-size: 28rpx;
  741. font-weight: 400;
  742. color:#999999;
  743. margin-left: 8rpx;
  744. }
  745. }
  746. .community-list {
  747. width: 90%;
  748. margin-top: 16rpx;
  749. }
  750. .community-item {
  751. display: flex;
  752. align-items: center;
  753. padding: 12rpx 12rpx;
  754. border-radius: 8rpx;
  755. font-family: 'Source Han Sans CN VF';
  756. &.active {
  757. background: #F5F6FA;
  758. }
  759. .community-item__dot {
  760. width: 16rpx;
  761. height: 16rpx;
  762. border-radius: 4rpx;
  763. margin-right: 12rpx;
  764. flex-shrink: 0;
  765. }
  766. .community-item__name {
  767. flex: 1;
  768. font-size: 26rpx;
  769. color: #042118;
  770. }
  771. .community-item__count {
  772. font-size: 26rpx;
  773. color: #042118;
  774. margin-right: 16rpx;
  775. }
  776. .community-item__percent {
  777. font-size: 26rpx;
  778. color: #656565;
  779. width: 72rpx;
  780. text-align: right;
  781. }
  782. }
  783. // 总量害虫数量
  784. .total-main {
  785. display: flex;
  786. align-items: flex-end;
  787. margin-bottom: 24rpx;
  788. font-family: 'Source Han Sans CN VF';
  789. .total-value {
  790. font-size: 56rpx;
  791. color: #042118;
  792. font-weight: 700;
  793. line-height: 1;
  794. .total-unit {
  795. font-size: 28rpx;
  796. font-weight: 500;
  797. margin-left: 8rpx;
  798. }
  799. }
  800. .total-compare {
  801. font-size: 24rpx;
  802. color: #656565;
  803. margin-left: 24rpx;
  804. padding-bottom: 4rpx;
  805. .total-compare__value {
  806. color: #E53935;
  807. font-weight: 700;
  808. }
  809. }
  810. }
  811. .total-bar {
  812. .total-bar__track {
  813. position: relative;
  814. height: 16rpx;
  815. border-radius: 8rpx;
  816. background: #F1F4F8;
  817. overflow: visible;
  818. }
  819. .total-bar__fill {
  820. height: 100%;
  821. border-radius: 8rpx;
  822. background: #0BBC58;
  823. }
  824. .total-bar__marker {
  825. position: absolute;
  826. top: -6rpx;
  827. width: 4rpx;
  828. height: 28rpx;
  829. border-radius: 2rpx;
  830. background: #E53935;
  831. transform: translateX(-50%);
  832. }
  833. .total-bar__scale {
  834. display: flex;
  835. justify-content: space-between;
  836. margin-top: 8rpx;
  837. font-size: 22rpx;
  838. color: #999999;
  839. font-family: 'Source Han Sans CN VF';
  840. }
  841. }
  842. // 周/月/年切换
  843. .range-tabs {
  844. display: flex;
  845. align-items: center;
  846. .range-tab {
  847. width: 64rpx;
  848. height: 48rpx;
  849. line-height: 44rpx;
  850. text-align: center;
  851. font-size: 24rpx;
  852. color: #042118;
  853. font-family: 'Source Han Sans CN VF';
  854. background: #fff;
  855. border: 2rpx solid #E4E7ED;
  856. box-sizing: border-box;
  857. &:first-child {
  858. border-radius: 8rpx 0 0 8rpx;
  859. }
  860. &:last-child {
  861. border-radius: 0 8rpx 8rpx 0;
  862. }
  863. &.active {
  864. background: #0BBC58;
  865. border-color: #0BBC58;
  866. color: #fff;
  867. font-weight: 700;
  868. }
  869. }
  870. }
  871. </style>