detail.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. <template>
  2. <view class="device-detail">
  3. <view class="device-detail__header">
  4. <u-icon
  5. size="36"
  6. class="arrow-left"
  7. name="arrow-left"
  8. @click="handleBack"
  9. ></u-icon>
  10. {{ deviceInfo.name }}
  11. </view>
  12. <view class="operation" @click.stop="isShowOperation = !isShowOperation">操作</view>
  13. <view class="operation-container" v-if="isShowOperation" @click="closeOperationHandler">
  14. <view class="operation-background"></view>
  15. <view class="operation-content">
  16. <view class="operation-item" v-if="isShowPhoto" @click="handlePhotoClick">
  17. <image :src="photoIcon" class="operation-icon"></image>
  18. 拍照
  19. </view>
  20. <!-- <view class="operation-item" @click="handleServiceClick">
  21. <image :src="serviceIcon" class="operation-icon"></image>
  22. 维修
  23. </view> -->
  24. <view class="operation-item" @click="handleSettingClick">
  25. <image :src="settingIcon" class="operation-icon"></image>
  26. 设置
  27. </view>
  28. <view class="operation-item" @click="handleSimClick">
  29. <image :src="simIcon" class="operation-icon"></image>
  30. SIM卡
  31. </view>
  32. <view class="operation-item" @click="modification">
  33. <image :src="editIcon" class="operation-icon"></image>
  34. 修改
  35. </view>
  36. </view>
  37. </view>
  38. <view class="device-detail__body">
  39. <DeviceCard
  40. :dataSource="deviceInfo"
  41. :collectTime="time"
  42. :title="deviceInfo.name"
  43. :deviceType="deviceType"
  44. />
  45. <view class="tabs">
  46. <view class="tab-container" v-if="isShowTab">
  47. <view class="tab-item" :class="activeTab === 'viewImage'?'active':''" @click="handleTabClick('viewImage')">
  48. 查看图片
  49. </view>
  50. <view class="tab-item" :class="activeTab === 'pestAnalysis'?'active':''" @click="handleTabClick('pestAnalysis')">
  51. 孢子分析
  52. </view>
  53. <view class="tab-item" :class="activeTab === 'deviceData'?'active':''" @click="handleTabClick('deviceData')">
  54. 设备数据
  55. </view>
  56. </view>
  57. <view class="select-timer-container">
  58. <view class="select-year">
  59. <view class="select-year-item" @click="showPicker = true">
  60. {{ currentYear }}
  61. <u-icon name="arrow-down" size="18" class="arrow-down"></u-icon>
  62. </view>
  63. </view>
  64. <view class="tabs-timer-container" @click="showCalendar">
  65. <view class="tabs-timer-item">
  66. {{startDate}}
  67. </view>
  68. 至
  69. <view class="tabs-timer-item">
  70. {{endDate}}
  71. </view>
  72. <u-icon name="calendar" size="36" class="calendar-icon"></u-icon>
  73. </view>
  74. </view>
  75. </view>
  76. <view v-show="activeTab === 'viewImage'">
  77. <!-- 孢子仪2.1(device_code=1):文件夹列表,file_fold 作为名称 -->
  78. <view class="tabs-fold" v-if="isSporeTwo && folderList.length">
  79. <view
  80. class="tabs-fold-item"
  81. :class="{ active: index === folderIndex }"
  82. v-for="(item, index) in folderList"
  83. :key="index"
  84. @click="handleFolderSelect(index)"
  85. >
  86. <image src="https://s3.hnyfwlw.com/webstaticimg/bigdata_app/image/file.png" class="tabs-fold-icon" />
  87. <view>{{ formatFold(item.file_fold) }}</view>
  88. </view>
  89. </view>
  90. <photoImage
  91. :images="imageList"
  92. :pestList="pestList"
  93. :disableShow="disableShow"
  94. @changeTab="changeTab"
  95. :currentYear="currentYear"
  96. :deviceInfo="deviceInfo"
  97. :sporeTwo="isSporeTwo"
  98. :folderId="folderList[folderIndex] ? folderList[folderIndex].id : ''"
  99. />
  100. </view>
  101. <view v-show="activeTab === 'deviceData'">
  102. <DeviceData
  103. :deviceStatic="deviceStatic"
  104. :deviceInfo="deviceInfo"
  105. :polylineList="polylineList"
  106. :deviceHistoryList="deviceHistoryList"
  107. :totalPage="totalPage"
  108. :currentPage="page"
  109. :page_size="page_size"
  110. @prevPage="prevPage"
  111. @nextPage="nextPage"
  112. />
  113. </view>
  114. <view v-show="activeTab === 'pestAnalysis'" class="spore-analysis">
  115. <!-- 孢子识别 -->
  116. <view class="analysis-card">
  117. <view class="card-title">孢子识别</view>
  118. <view class="pest-rank">
  119. <view class="pest-rank__item" v-for="(item, index) in pestRankList" :key="index">
  120. <text class="pest-rank__name">{{ item.name }}</text>
  121. <view class="pest-rank__track">
  122. <view class="pest-rank__fill" :style="{ width: rankPercent(item) + '%', background: rankColors[index % rankColors.length] }"></view>
  123. </view>
  124. <text class="pest-rank__count">{{ item.count }}</text>
  125. </view>
  126. </view>
  127. </view>
  128. <!-- 孢子趋势 -->
  129. <view class="analysis-card">
  130. <view class="card-title">孢子趋势</view>
  131. <view class="chart-wrap--spore">
  132. <qiun-data-charts
  133. type="mix"
  134. :chartData="sporeChart"
  135. :opts="sporeOpts"
  136. :reshow="activeTab === 'pestAnalysis'"
  137. :canvas2d="true"
  138. :inScrollView="true"
  139. :ontouch="true"
  140. />
  141. </view>
  142. </view>
  143. </view>
  144. </view>
  145. <u-calendar v-model="show" :mode="mode" range-color="#999" btn-type="success" active-bg-color="#0bbc58" range-bg-color="rgba(11,188,88,0.13)" @change="handleChange" :max-date="maxDate" :min-date="minDate"></u-calendar>
  146. <u-picker v-model="showPicker" mode="selector" :range="selectorRange" range-key="id" :default-selector="[0]" @confirm="confirmHandler"></u-picker>
  147. </view>
  148. </template>
  149. <script>
  150. import DeviceCard from './components/DeviceCard.vue';
  151. import PestDiscern from './components/pestDiscern.vue';
  152. import PestEchart from './components/pestEchart.vue';
  153. import PestArchive from './components/pestArchive.vue';
  154. import photoImage from './components/photoImage.vue';
  155. import photoImage2 from './components/photoImage2.vue';
  156. import DeviceData from './components/deviceData.vue';
  157. export default {
  158. components: {
  159. DeviceCard,
  160. PestDiscern,
  161. PestEchart,
  162. PestArchive,
  163. photoImage,
  164. photoImage2,
  165. DeviceData,
  166. },
  167. data(){
  168. return {
  169. isShowTab:false,
  170. showPicker: false,
  171. disableShow: false,
  172. isShowOperation: false,
  173. photoIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/photoIcon.png',
  174. editIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/editIcon.png',
  175. serviceIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/serviceIcon.png',
  176. simIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/simIcon.png',
  177. settingIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/settingIcon.png',
  178. totalPage:0,
  179. currentYear: new Date().getFullYear(),
  180. selectorRange: [],
  181. maxDate: this.formatDate(new Date()),
  182. minDate: this.formatDate(new Date(new Date().getFullYear(), 0, 1)),
  183. show: false,
  184. showSim: false,
  185. // 当前日期向前推30天 格式2026-01-01 月份和日期小于10的时候前面加个0
  186. startDate: this.formatDate(new Date(new Date().getTime() - 30 * 24 * 60 * 60 * 1000)),
  187. endDate: this.formatDate(new Date()),
  188. mode: 'range',
  189. imageList: [],
  190. pest_order:{},
  191. deviceInfo: {},
  192. pestInfo: {},
  193. time: '',
  194. activeTab: 'viewImage',
  195. deviceType: '',
  196. location: '',
  197. total: 0,
  198. day: [],
  199. pest: [],
  200. page:1,
  201. is_pest:'',
  202. page_size:24,
  203. pestList:[],
  204. pest_names:'',
  205. pests:[],
  206. // 孢子分析
  207. rankColors: ['#4FD8E0', '#FF7A45', '#5AD8A6', '#FFC53D', '#F8E71C', '#5B8FF9', '#E8684A', '#6DC8EC', '#FF9DC6', '#7EDB8F'],
  208. pestRankList: [],
  209. // 各物种每日数量/浓度(char_data)
  210. sporeCharData: {},
  211. sporeOpts: {
  212. dataPointShape: true,
  213. // 图例置顶,点击可隐藏/显示对应线(同 PC 图例交互)
  214. legend: {
  215. show: true,
  216. fontSize: 10,
  217. itemGap: 12
  218. },
  219. xAxis: {
  220. disableGrid: false,
  221. gridType: 'dash',
  222. gridColor: '#EEEEEE',
  223. fontSize: 10,
  224. fontColor: '#666666',
  225. itemCount: 4,
  226. scrollShow: true
  227. },
  228. // 双 Y 轴:左轴虫数,右轴浓度
  229. yAxis: {
  230. showTitle: true,
  231. disableGrid: false,
  232. gridType: 'dash',
  233. gridColor: '#EEEEEE',
  234. data: [
  235. {
  236. title: '虫数:头',
  237. titleFontSize: 10,
  238. titleFontColor: '#666666',
  239. position: 'left',
  240. min: 0,
  241. splitNumber: 4,
  242. formatter: val => Math.round(val) + ''
  243. },
  244. {
  245. title: '浓度:个/L',
  246. titleFontSize: 10,
  247. titleFontColor: '#666666',
  248. position: 'right',
  249. min: 0,
  250. splitNumber: 4,
  251. formatter: val => Number(val).toFixed(4)
  252. }
  253. ]
  254. },
  255. extra: {
  256. mix: {
  257. line: { width: 2 }
  258. },
  259. tooltip: { showBox: true }
  260. },
  261. enableScroll: true
  262. },
  263. polylineList:[],
  264. isShowPhoto:false,
  265. deviceHistoryList:[],
  266. deviceStatic:{},
  267. // 孢子仪2.1 文件夹列表
  268. folderList: [],
  269. folderIndex: 0,
  270. // 设备代号:1=孢子仪2.1,其他=2.0(走原接口)
  271. deviceCode: ''
  272. }
  273. },
  274. computed: {
  275. // 孢子仪2.1
  276. isSporeTwo() {
  277. return this.deviceCode == 1;
  278. }
  279. },
  280. onLoad(options){
  281. this.deviceInfo = JSON.parse(options.info);
  282. console.log(this.deviceInfo,'deviceInfod2eviceInfo')
  283. // 列表跳转时带过来的设备代号:1=孢子仪2.1
  284. this.deviceCode = options.device_code || this.deviceInfo.device_code || '';
  285. // 诊断:确认列表带过来的 device_code 是否有值
  286. console.log('[bzy2] device_code =', options.device_code, ', deviceInfo =', JSON.stringify(this.deviceInfo));
  287. // 延迟初始化:等布局稳定后再测 canvas 尺寸
  288. setTimeout(() => {
  289. this.getSporeAnalysis();
  290. }, 300);
  291. // this.getPestAnalysis();
  292. this.isShow();
  293. const currentYear = new Date().getFullYear();
  294. this.selectorRange = [];
  295. for(let i = 0;i<50;i++){
  296. const item = {
  297. label: currentYear - i,
  298. id: currentYear - i
  299. }
  300. this.selectorRange.push(item);
  301. }
  302. },
  303. methods: {
  304. handleSimClick(){
  305. // this.showSim = true;
  306. //`/pages/deviceDetails/weatherStation/${type}?deviceInfo=${encodeURIComponent(JSON.stringify(this.deviceInfo))}`
  307. uni.navigateTo({
  308. url:
  309. '/pages/deviceDetails/weatherStation/simDetail?deviceInfo=' +
  310. encodeURIComponent(JSON.stringify(this.deviceInfo))
  311. });
  312. },
  313. modification() {
  314. uni.navigateTo({
  315. url:
  316. '/pages/equipList2/seabox/modification?data=' +
  317. JSON.stringify(this.deviceInfo) +
  318. '&id=' +
  319. this.deviceInfo.type,
  320. });
  321. },
  322. async handlePhotoClick(){
  323. const res = await this.$myRequest({
  324. url: '/api/api_gateway?method=forecast.send_control.admin_device_control',
  325. method: 'POST',
  326. data: {
  327. device_type_id: this.deviceInfo.type,
  328. d_id: this.deviceInfo.d_id,
  329. cmd: 'takephoto',
  330. },
  331. });
  332. if(res){
  333. this.$u.toast('拍照成功')
  334. }
  335. },
  336. handleServiceClick(){
  337. uni.navigateTo({
  338. url: '/pages/afterSale/addafter?d_id=' + this.deviceInfo.d_id +'&device_id='+this.deviceInfo.id + '&device_type=' + this.deviceInfo.type,
  339. })
  340. },
  341. handleSettingClick(){
  342. uni.navigateTo({
  343. url: '/pages/bzy/deviceControl?deviceId=' + this.deviceInfo.id + '&d_id=' + this.deviceInfo.d_id + '&device_model=' + this.deviceInfo.device_model,
  344. });
  345. },
  346. closeOperationHandler(){
  347. this.isShowOperation = false;
  348. },
  349. isShow(){
  350. // disable == 0 或者 device_model == 15 表示不可以查看
  351. if(!this.deviceInfo.d_id){
  352. return false;
  353. }
  354. let showStatus = true;
  355. if(this.deviceInfo.device_model == 15 || this.deviceInfo.device_model == 12){
  356. showStatus = false;
  357. }
  358. if(showStatus){
  359. this.activeTab = 'viewImage';
  360. }else{
  361. this.activeTab = 'deviceData';
  362. }
  363. this.initAction();
  364. this.isShowTab = showStatus;
  365. if(this.deviceInfo.disable == 0){
  366. this.disableShow = false;
  367. if(showStatus){
  368. this.activeTab = 'viewImage';
  369. this.handleTabClick('viewImage');
  370. }
  371. }else{
  372. this.disableShow = true;
  373. }
  374. },
  375. prevPage(e){
  376. if(e == 1){
  377. return
  378. }
  379. this.page = e-=1;
  380. this.getDeviceHistoryData();
  381. },
  382. nextPage(e){
  383. if(e * this.page_size >= this.totalPage){
  384. return
  385. }
  386. this.page = e+=1;
  387. this.getDeviceHistoryData();
  388. },
  389. changeTab(pestList){
  390. let pest_names = pestList.map(item => item.pest_name);
  391. this.pest_names = pest_names.join(',');
  392. this.initImageList();
  393. },
  394. confirmHandler(e){
  395. this.currentYear = this.selectorRange[e].id;
  396. if(this.currentYear == new Date().getFullYear()){
  397. // 结束日期为this.endDate的月份和日期加上选择的年份
  398. const timeDate = this.currentYear + '-' + this.endDate.split('-')[1] + '-' + this.endDate.split('-')[2];
  399. this.endDate = this.formatDate(new Date(timeDate));
  400. // 开始日期为结束日期前30天
  401. this.startDate = this.formatDate(new Date(new Date(this.endDate).getTime() - 30 * 24 * 60 * 60 * 1000));
  402. this.maxDate = this.formatDate(new Date());
  403. this.minDate = this.formatDate(new Date(new Date().getFullYear(), 0, 1));
  404. }else{
  405. // 结束日期为this.endDate的月份和日期加上选择的年份
  406. const timeDate = this.currentYear + '-' + this.endDate.split('-')[1] + '-' + this.endDate.split('-')[2];
  407. this.endDate = this.formatDate(new Date(timeDate));
  408. // 开始日期为结束日期前30天
  409. this.startDate = this.formatDate(new Date(new Date(this.endDate).getTime() - 30 * 24 * 60 * 60 * 1000));
  410. this.maxDate = this.formatDate(new Date(this.currentYear, 11, 31));
  411. this.minDate = this.formatDate(new Date(this.currentYear, 0, 1));
  412. }
  413. this.initAction();
  414. this.showPicker = false;
  415. },
  416. getInfo(info){
  417. this.pestInfo = info;
  418. },
  419. // 格式化日期为YYYY-MM-DD格式,月份和日期小于10时前面加0
  420. formatDate(date) {
  421. const year = date.getFullYear();
  422. const month = String(date.getMonth() + 1).padStart(2, '0');
  423. const day = String(date.getDate()).padStart(2, '0');
  424. return `${year}-${month}-${day}`;
  425. },
  426. formatTime(date){
  427. const time = new Date(date).getTime() / 1000;
  428. return time.toFixed(0);
  429. },
  430. handleChange(e){
  431. this.startDate = e.startDate;
  432. this.endDate = e.endDate;
  433. this.page = 1;
  434. this.initAction();
  435. },
  436. showCalendar(){
  437. this.show = true;
  438. },
  439. handleBack() {
  440. uni.navigateBack({
  441. delta: 1
  442. });
  443. },
  444. initAction(){
  445. this.pest_order = {}
  446. if(this.activeTab === 'viewImage'){
  447. // this.initPest();
  448. this.initImageList();
  449. }else if(this.activeTab === 'deviceData'){
  450. // this.getDeviceData();
  451. this.getPolylineData();
  452. this.getDeviceHistoryData();
  453. }else if(this.activeTab === 'pestAnalysis'){
  454. this.getSporeAnalysis();
  455. }
  456. },
  457. handleTabClick(tab) {
  458. this.activeTab = tab;
  459. this.initAction();
  460. },
  461. // 拉取孢子分析数据(虫害识别 + 孢子趋势)
  462. async getSporeAnalysis() {
  463. const res = await this.$myRequest({
  464. url: '/api/api_gateway?method=forecast.bzy.pest_order_and_char',
  465. method: 'POST',
  466. data: {
  467. id: this.deviceInfo.d_id,
  468. hour_st: '00',
  469. hour_ed: '00',
  470. device_type_id: this.deviceInfo.type_id || this.deviceInfo.type,
  471. start: this.formatTime(this.startDate + ' 00:00:00'),
  472. end: this.formatTime(this.endDate + ' 23:59:59')
  473. }
  474. });
  475. // 虫害识别:pest_total 按数量降序
  476. const pestTotal = (res && res.pest_total) || {};
  477. this.pestRankList = Object.keys(pestTotal)
  478. .map(name => ({ name: name, count: pestTotal[name] || 0 }))
  479. .sort((a, b) => b.count - a.count);
  480. // 各物种每日数量/浓度(char_data)
  481. const charData = (res && res.char_data) || {};
  482. const charRows = {};
  483. Object.keys(charData).forEach(name => {
  484. charRows[name] = (charData[name] || []).map(item => ({
  485. date: this.formatDay(item.addtime),
  486. value: item.sum || 0,
  487. ctn: item.ctn || 0
  488. }));
  489. });
  490. this.sporeCharData = charRows;
  491. this.buildSporeChart();
  492. },
  493. // 秒级时间戳 → YYYY-MM-DD
  494. formatDay(seconds) {
  495. return this.formatDate(new Date(Number(seconds) * 1000));
  496. },
  497. // 孢子趋势:所有物种的数量(左轴)+ 浓度(右轴)画在同一张图,点图例可隐藏/显示
  498. buildSporeChart() {
  499. const names = Object.keys(this.sporeCharData);
  500. // 汇总所有日期作为 x 轴
  501. const categories = [];
  502. names.forEach(name => {
  503. this.sporeCharData[name].forEach(row => {
  504. if (categories.indexOf(row.date) === -1) categories.push(row.date);
  505. });
  506. });
  507. categories.sort();
  508. const series = [];
  509. names.forEach(name => {
  510. const map = {};
  511. this.sporeCharData[name].forEach(row => {
  512. map[row.date] = row;
  513. });
  514. series.push({
  515. name: name,
  516. type: 'line',
  517. style: 'curve',
  518. index: 0,
  519. data: categories.map(d => (map[d] ? map[d].value : null))
  520. });
  521. series.push({
  522. name: name + '浓度',
  523. type: 'line',
  524. style: 'curve',
  525. index: 1,
  526. data: categories.map(d => (map[d] ? map[d].ctn : null))
  527. });
  528. });
  529. this.sporeChart = { categories: categories, series: series };
  530. },
  531. // 虫害识别进度条宽度:占总量百分比,保底 4% 便于查看
  532. rankPercent(item) {
  533. const total = this.pestRankList.reduce((sum, cur) => sum + cur.count, 0);
  534. if (!total) return 0;
  535. return Math.max(4, Math.round((item.count / total) * 100));
  536. },
  537. async getDeviceData(){
  538. const res = await this.$myRequest({
  539. url: '/api/api_gateway?method=forecast.worm_lamp.device_status_data',
  540. method: 'POST',
  541. data: {
  542. device_id: this.deviceInfo.id,
  543. },
  544. });
  545. this.deviceStatic = res;
  546. },
  547. async initImageList(){
  548. // 孢子仪2.1(device_code=1)走文件夹列表;2.0 走原接口
  549. if (!this.isSporeTwo) {
  550. const res = await this.$myRequest({
  551. url: '/api/api_gateway?method=forecast.forecast_system.equip_photo',
  552. method: 'POST',
  553. data: {
  554. page:this.page,
  555. page_number:this.page_size,
  556. device_id: this.deviceInfo.id,
  557. ret:'list',
  558. time_begin: this.formatTime(this.formatDate(new Date(this.startDate)) + ' 00:00:00'),// 格式化开始时间YYYY-MM-DD HH:MM:SS
  559. time_end: this.formatTime(this.formatDate(new Date(this.endDate)) + ' 23:59:59'),// 格式化结束时间YYYY-MM-DD HH:MM:SS
  560. },
  561. });
  562. const data = res?.data || [];
  563. this.imageList = data
  564. return;
  565. }
  566. await this.initFolderList();
  567. },
  568. // 孢子仪2.1:拉取文件夹列表,默认选中第一个并加载其图片
  569. async initFolderList(){
  570. uni.showLoading({ title: '加载中' });
  571. try {
  572. const res = await this.$myRequest({
  573. url: '/api/api_gateway?method=forecast.bzy.bzy_file_fold',
  574. method: 'POST',
  575. data: {
  576. device_id: this.deviceInfo.id,
  577. page: 1,
  578. size: 999,
  579. },
  580. });
  581. this.folderList = res?.data || [];
  582. // 默认选中第一个文件夹并加载其图片
  583. this.folderIndex = 0;
  584. if (this.folderList.length) {
  585. await this.loadFolderPhotos();
  586. } else {
  587. this.imageList = [];
  588. }
  589. } catch (e) {
  590. console.log('bzy_file_fold error:', e);
  591. this.folderList = [];
  592. } finally {
  593. uni.hideLoading();
  594. }
  595. },
  596. // 20260905110035 → 2026-09-05 11:00
  597. formatFold(str){
  598. const s = String(str || '');
  599. if (s.length < 12) return s;
  600. return `${s.slice(0, 4)}${s.slice(4, 6)}${s.slice(6, 8)}_${s.slice(8, 10)}${s.slice(10, 12)}`;
  601. },
  602. handleFolderSelect(index){
  603. if (index === this.folderIndex) return;
  604. this.folderIndex = index;
  605. this.loadFolderPhotos();
  606. },
  607. // 孢子仪2.1:拉取选中文件夹内的图片列表
  608. async loadFolderPhotos(){
  609. const folder = this.folderList[this.folderIndex] || {};
  610. if (!folder.id) {
  611. this.imageList = [];
  612. return;
  613. }
  614. uni.showLoading({ title: '加载中' });
  615. try {
  616. const res = await this.$myRequest({
  617. url: '/api/api_gateway?method=forecast.bzy.bzy_two_percent_one_photo',
  618. method: 'POST',
  619. data: {
  620. id: folder.id,
  621. page: 1,
  622. size: 32,
  623. },
  624. });
  625. console.log('bzy_two_percent_one_photo res:', JSON.stringify(res).slice(0, 500));
  626. this.imageList = res?.data || [];
  627. } catch (e) {
  628. console.log('bzy_two_percent_one_photo error:', e);
  629. this.imageList = [];
  630. } finally {
  631. uni.hideLoading();
  632. }
  633. },
  634. async initPest(){
  635. const res = await this.$myRequest({
  636. url: '/api/api_gateway?method=forecast.new_cbd.pest_type_list',
  637. method: 'POST',
  638. data:{
  639. page:1,
  640. page_size:999999,
  641. device_id: this.deviceInfo.id,
  642. identify_model: 'B',
  643. time_begin: this.formatTime(this.formatDate(new Date(this.startDate)) + ' 00:00:00'),// 格式化开始时间YYYY-MM-DD HH:MM:SS
  644. time_end: this.formatTime(this.formatDate(new Date(this.endDate)) + ' 23:59:59'),// 格式化结束时间YYYY-MM-DD HH:MM:SS
  645. },
  646. });
  647. const data = res?.data || [];
  648. this.pestList = data
  649. },
  650. async getPolylineData(){
  651. const res = await this.$myRequest({
  652. url: '/api/api_gateway?method=forecast.worm_lamp.device_polyline_data',
  653. method: 'POST',
  654. data: {
  655. device_type_id: this.deviceInfo.type_id,
  656. d_id: this.deviceInfo.d_id,
  657. start_time: new Date(this.startDate + ' 00:00:00').getTime()/1000,// 转成毫秒
  658. end_time: new Date(this.endDate + ' 23:59:59').getTime()/1000,// 转成毫秒
  659. },
  660. });
  661. const data = res || [];
  662. this.polylineList = data
  663. },
  664. async getDeviceHistoryData(){
  665. const res = await this.$myRequest({
  666. url: '/api/api_gateway?method=forecast.worm_lamp.device_history_data',
  667. method: 'POST',
  668. data: {
  669. device_type_id: this.deviceInfo.type_id,
  670. device_id: this.deviceInfo.id,
  671. start_time: new Date(this.startDate + ' 00:00:00').getTime()/1000,
  672. end_time: new Date(this.endDate + ' 23:59:59').getTime()/1000,
  673. page: this.page,
  674. page_size: this.page_size,
  675. },
  676. });
  677. const data = res?.data || [];
  678. this.totalPage = res.counts;
  679. this.deviceHistoryList = data
  680. },
  681. async getPestAnalysis(){
  682. const res = await this.$myRequest({
  683. url: '/api/api_gateway?method=forecast.cbd_analysis.analysis_pest_result',
  684. method: 'POST',
  685. data: {
  686. d_id: this.deviceInfo.d_id,
  687. start: this.startDate,
  688. end: this.endDate,
  689. model: 'B'
  690. },
  691. });
  692. const pest_order = res?.pest_order;
  693. this.getInfo({});
  694. let total = 0;
  695. this.pests = [];
  696. for(let key in pest_order){
  697. total += pest_order[key] || 0;
  698. this.pests.push({
  699. name: key,
  700. percent: (pest_order[key] / total) * 100
  701. })
  702. }
  703. this.pest_order = pest_order;
  704. this.day = res?.day || [];
  705. const pest = res?.pest || [];
  706. this.pest = pest;
  707. // pest.forEach(p =>{
  708. // for(let i = 0;i< p.length;i++){
  709. // this.pest.push(p[i]);
  710. // }
  711. // })
  712. this.total = total;
  713. }
  714. }
  715. }
  716. </script>
  717. <style scoped lang="scss">
  718. ::v-deep .u-calendar__action{
  719. display:flex;
  720. justify-content: space-between;
  721. }
  722. ::v-deep .u-hover-class{
  723. .u-calendar__content__item__inner{
  724. color:#aaa !important;
  725. }
  726. }
  727. .operation-container{
  728. position: fixed;
  729. right: 0;
  730. top: 0;
  731. width: 100%;
  732. height: 100%;
  733. z-index: 99;
  734. }
  735. .operation{
  736. position: fixed;
  737. top: 260rpx;
  738. right: 0;
  739. z-index: 999;
  740. width:48rpx;
  741. height: 100rpx;
  742. line-height: 50rpx;
  743. border-radius: 8px 0 0 8px;
  744. border-top: 4rpx solid #FFF;
  745. border-bottom: 4rpx solid #FFF;
  746. border-left: 4rpx solid #FFF;
  747. background: #dddfe6a3;
  748. color: #515153;
  749. text-align: center;
  750. font-family: "Source Han Sans CN VF";
  751. font-size: 24rpx;
  752. font-weight: 500;
  753. writing-mode: vertical-rl;
  754. }
  755. .operation-background{
  756. position: fixed;
  757. right: 0;
  758. top: 0;
  759. width: 100%;
  760. height: 100%;
  761. z-index: 998;
  762. background: #00000040;
  763. }
  764. .operation-content{
  765. position: fixed;
  766. top: 240rpx;
  767. right: 80rpx;
  768. height: 126rpx;
  769. display: flex;
  770. padding: 16rpx 32rpx;
  771. align-items: center;
  772. gap: 64rpx;
  773. z-index: 999;
  774. border-radius: 32rpx;
  775. border: 2rpx solid #FFF;
  776. background: #FFF;
  777. backdrop-filter: blur(8rpx);
  778. .operation-item{
  779. display: flex;
  780. flex-direction: column;
  781. align-items: center;
  782. justify-content: center;
  783. color: #333333;
  784. text-align: center;
  785. font-family: "Source Han Sans CN VF";
  786. font-size: 24rpx;
  787. font-weight: 400;
  788. }
  789. .operation-icon{
  790. width: 58rpx;
  791. height: 58rpx;
  792. }
  793. }
  794. .device-detail {
  795. display: flex;
  796. width: 100%;
  797. height: calc(100vh - 112rpx);
  798. padding-top: 112rpx;
  799. flex-direction: column;
  800. align-items: center;
  801. overflow-y: scroll;
  802. // 孢子分析
  803. .spore-analysis {
  804. width: 100%;
  805. .analysis-card {
  806. width: 100%;
  807. padding: 32rpx;
  808. box-sizing: border-box;
  809. background: #fff;
  810. border-radius: 16rpx;
  811. margin-bottom: 24rpx;
  812. &:last-child {
  813. margin-bottom: 0;
  814. }
  815. }
  816. .card-title {
  817. font-size: 32rpx;
  818. color: #042118;
  819. font-family: 'Source Han Sans CN VF';
  820. font-weight: 700;
  821. margin-bottom: 24rpx;
  822. }
  823. .pest-rank {
  824. &__item {
  825. display: flex;
  826. align-items: center;
  827. padding: 14rpx 0;
  828. }
  829. &__name {
  830. width: 160rpx;
  831. flex-shrink: 0;
  832. font-size: 26rpx;
  833. color: #042118;
  834. font-family: 'Source Han Sans CN VF';
  835. overflow: hidden;
  836. text-overflow: ellipsis;
  837. white-space: nowrap;
  838. }
  839. &__track {
  840. flex: 1;
  841. height: 12rpx;
  842. border-radius: 6rpx;
  843. background: #F0F2F5;
  844. overflow: hidden;
  845. margin: 0 24rpx;
  846. }
  847. &__fill {
  848. height: 100%;
  849. border-radius: 6rpx;
  850. }
  851. &__count {
  852. width: 80rpx;
  853. flex-shrink: 0;
  854. text-align: right;
  855. font-size: 26rpx;
  856. color: #042118;
  857. font-family: 'Source Han Sans CN VF';
  858. }
  859. }
  860. .chart-wrap--spore {
  861. width: 100%;
  862. height: 420rpx;
  863. }
  864. }
  865. background: linear-gradient(180deg, #ffffff00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
  866. .device-detail__header {
  867. width: 100%;
  868. font-size: 28rpx;
  869. color: #999;
  870. color: #042118;
  871. font-family: 'Source Han Sans CN VF';
  872. font-weight: 700;
  873. position: relative;
  874. text-align: center;
  875. .arrow-left {
  876. position: absolute;
  877. left: 32rpx;
  878. margin-right: 12rpx;
  879. }
  880. }
  881. .device-detail__body {
  882. width: calc(100% - 64rpx);
  883. margin: 0 auto;
  884. border-radius: 16rpx;
  885. overflow-x: hidden;
  886. overflow-y: auto;
  887. // 隐藏滚动条
  888. -ms-overflow-style: none;
  889. scrollbar-width: none;
  890. }
  891. .tab-content{
  892. width: 100%;
  893. padding-bottom: 32rpx;
  894. }
  895. .tabs {
  896. background: #ffffff;
  897. margin: 24rpx 0;
  898. border-radius: 16rpx;
  899. padding: 16rpx 0;
  900. padding-top: 0;
  901. .tab-container{
  902. display: flex;
  903. width: 100%;
  904. height: 88rpx;
  905. line-height: 88rpx;
  906. text-align: center;
  907. font-size: 28rpx;
  908. font-weight: 700;
  909. color: #042118;
  910. font-family: 'Source Han Sans CN VF';
  911. }
  912. .select-timer-container{
  913. display:flex;
  914. align-items: center;
  915. padding-left: 32rpx;
  916. .select-year{
  917. width: 110rpx;
  918. text-align: center;
  919. height: 64rpx;
  920. border-radius: 32rpx;
  921. font-family: 'Source Han Sans CN VF';
  922. line-height: 64rpx;
  923. background: #F1F4F8;
  924. padding: 0 32rpx;
  925. .select-year-item{
  926. color: #656565;
  927. font-size: 24rpx;
  928. display: flex;
  929. align-items: center;
  930. justify-content: space-around;
  931. }
  932. }
  933. }
  934. .tabs-timer-container{
  935. display: flex;
  936. align-items: center;
  937. width: calc(100% - 256rpx);
  938. height: 64rpx;
  939. line-height: 64rpx;
  940. text-align: center;
  941. font-size: 24rpx;
  942. font-weight: 500;
  943. font-family: 'Source Han Sans CN VF';
  944. border-radius: 32rpx;
  945. background: #F1F4F8;
  946. padding: 0 32rpx;
  947. color: #656565;
  948. margin: 16rpx;
  949. position: relative;
  950. .tabs-timer-item{
  951. width: 42%;
  952. color: #656565;
  953. text-align: center;
  954. font-family: "Source Han Sans CN VF";
  955. font-size: 24rpx;
  956. font-weight: 400;
  957. }
  958. .calendar-icon{
  959. margin-left: 24rpx;
  960. }
  961. }
  962. .tab-item {
  963. flex: 1;
  964. color: #999999;
  965. text-align: center;
  966. font-family: "Source Han Sans CN VF";
  967. font-size: 28rpx;
  968. font-weight: 400;
  969. }
  970. .active{
  971. position: relative;
  972. color: #303133;
  973. text-align: center;
  974. font-family: "Source Han Sans CN VF";
  975. font-size: 28rpx;
  976. font-weight: 700;
  977. &::after {
  978. content: '';
  979. position: absolute;
  980. bottom: 0;
  981. left: 50%;
  982. -webkit-transform: translateX(-50%);
  983. transform: translateX(-50%);
  984. width: 36rpx;
  985. height: 36rpx;
  986. border: 6rpx solid #0bbc58;
  987. border-radius: 50%;
  988. border-color: transparent;
  989. border-bottom-color: #0bbc58;
  990. }
  991. }
  992. }
  993. .tabs-fold{
  994. display: flex;
  995. align-items: center;
  996. text-align: center;
  997. font-size: 28rpx;
  998. font-weight: 700;
  999. color: #042118;
  1000. font-family: 'Source Han Sans CN VF';
  1001. // 滚动条
  1002. overflow-x: auto;
  1003. white-space: nowrap;
  1004. padding-bottom: 24rpx;
  1005. // 隐藏滚动条
  1006. -ms-overflow-style: none;
  1007. scrollbar-width: none;
  1008. .tabs-fold-item{
  1009. display: flex;
  1010. align-items: center;
  1011. justify-content: center;
  1012. flex-direction: column;
  1013. flex-shrink: 0; // 不压缩子项,超出容器宽度才能横向滚动
  1014. background: #FFFFFF;
  1015. border-radius: 24rpx;
  1016. border: 4rpx solid transparent;
  1017. box-sizing: border-box;
  1018. width: 180rpx;
  1019. padding: 16rpx 0;
  1020. &.active {
  1021. border-color: #0BBC58;
  1022. }
  1023. text-align: center;
  1024. font-size: 24rpx;
  1025. font-weight: 400;
  1026. color: #656565;
  1027. font-family: 'Source Han Sans CN VF';
  1028. margin: 0 14rpx;
  1029. border: 1rpx solid #E5E5E5;
  1030. .tabs-fold-icon{
  1031. width: 48rpx;
  1032. height: 48rpx;
  1033. margin-bottom: 12rpx;
  1034. }
  1035. }
  1036. .tabs-fold-item.active{
  1037. border-color: #0bbc58;
  1038. }
  1039. }
  1040. }
  1041. </style>