devicePhoto.vue 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  1. <template>
  2. <view class="device-photo-page">
  3. <!-- 顶部导航栏 -->
  4. <view class="device-detail__header">
  5. <u-icon
  6. size="36"
  7. class="arrow-left"
  8. name="arrow-left"
  9. @click="handleBack"
  10. ></u-icon>
  11. {{ title }}
  12. </view>
  13. <!-- 孢子仪2.1:文件夹列表,点击切换文件夹 -->
  14. <view class="tabs-fold" v-if="sporeTwo && folderList.length">
  15. <view
  16. class="tabs-fold-item"
  17. :class="{ active: folderId == item.id }"
  18. v-for="(item, index) in folderList"
  19. :key="index"
  20. @click="handleFolderClick(item)"
  21. >
  22. <image src="https://s3.hnyfwlw.com/webstaticimg/bigdata_app/image/file.png" class="tabs-fold-icon" />
  23. <view>{{ formatFold(item.file_fold) }}</view>
  24. </view>
  25. </view>
  26. <!-- 日期选择器 -->
  27. <!-- <view class="date-container">
  28. <view class="date-picker" @click="show = true">
  29. <view class="date-input">
  30. <text class="date-label">{{ time_begin || '-' }}</text>
  31. </view>
  32. <u-icon name="calendar" class="calendar"></u-icon>
  33. </view>
  34. </view> -->
  35. <!-- 主图片区域 -->
  36. <view class="main-photo" v-if="currentImg.addr">
  37. <movable-area class="movable-area" :style="{ height: containerHeight }">
  38. <movable-view
  39. class="movable-view"
  40. direction="all"
  41. scale="true"
  42. scale-min="1"
  43. scale-max="5"
  44. >
  45. <view class="image-container">
  46. <!-- 原图 -->
  47. <image
  48. :src="currentImg.addr"
  49. class="main-photo-image"
  50. mode="widthFix"
  51. @load="onImageLoad"
  52. />
  53. <!-- 虫子标记层 -->
  54. <view class="bug-markers" v-if="bugMarkers.length > 0">
  55. <view
  56. v-for="(marker, index) in bugMarkers"
  57. :key="index"
  58. class="bug-marker"
  59. :style="{
  60. left: marker.x + '%',
  61. top: marker.y + '%',
  62. width: marker.width + '%',
  63. height: marker.height + '%',
  64. borderColor: marker.color
  65. }"
  66. >
  67. <view class="bug-label" :style="{ color: marker.color }">
  68. {{ getPestName(marker.id) }}
  69. </view>
  70. </view>
  71. </view>
  72. </view>
  73. </movable-view>
  74. </movable-area>
  75. <view class="photo-timestamp">{{ sporeTwo ? formatItemTime(currentImg) : formatDate(currentImg.addtime) }}</view>
  76. </view>
  77. <!-- 缩略图预览 -->
  78. <view class="thumbnail-preview">
  79. <view class="thumbnail-scroll">
  80. <view
  81. class="thumbnail-item"
  82. v-for="(item, index) in thumbnails"
  83. :key="index"
  84. :class="{ active: sporeTwo ? currentIndex == index : img_id == item.ids }"
  85. @click="onThumbnailClick(item, index)"
  86. >
  87. <image
  88. :src="item.addr"
  89. class="thumbnail-image"
  90. />
  91. </view>
  92. </view>
  93. </view>
  94. <!-- 识别结果 -->
  95. <view class="recognition-result" v-if="pestList.length">
  96. <view class="result-title">当前图片识别结果</view>
  97. <!-- 一类害虫 -->
  98. <view class="pest-category">
  99. <view class="category-header">
  100. <text class="category-title">孢子名称</text>
  101. <text class="category-count">数量</text>
  102. </view>
  103. <view class="pest-item" v-for="(pest,index) in pestList" :key="index">
  104. <view class="pest-info">
  105. <view class="pest-color"></view>
  106. <text class="pest-name">{{ pest.text }}</text>
  107. </view>
  108. <text class="pest-count">{{ pest.count }}</text>
  109. </view>
  110. </view>
  111. </view>
  112. <u-empty v-else text="暂无数据"></u-empty>
  113. <u-calendar
  114. v-model="show"
  115. :mode="mode"
  116. @change="handleChange"
  117. range-color="#999"
  118. btn-type="success"
  119. active-bg-color="#0bbc58"
  120. range-bg-color="rgba(11,188,88,0.13)"
  121. :defaultDate="defaultDate"
  122. ></u-calendar>
  123. <u-picker v-model="showPicker" mode="selector" :range="selectorRange" range-key="id" :default-selector="[0]" @confirm="confirmHandler"></u-picker>
  124. </view>
  125. </template>
  126. <script>
  127. export default {
  128. data() {
  129. return {
  130. show: false,
  131. mode: 'date',
  132. maxDate: this.formatTime(new Date()),
  133. minDate: this.formatTime(new Date(new Date().getFullYear(), 0, 1)),
  134. defaultDate: this.formatTime(new Date(new Date().getFullYear(), 0, 1)),
  135. showPicker: false,
  136. selectorRange: [],
  137. title: '查看图片',
  138. currentThumbnail: 2,
  139. pestYype:{},
  140. device_id: '',
  141. time_begin: this.formatTime(new Date(new Date().getFullYear(), 0, 1)),
  142. time_end: this.formatTime(new Date()),
  143. img_id: '',
  144. pest_list:[],
  145. pestList:[],
  146. colors:[
  147. '#FF5951',
  148. '#66EDED',
  149. '#E67B3E',
  150. '#6DE28B',
  151. '#FFC97A',
  152. '#E7EB4B',
  153. '#1561F3',
  154. '#FA73F5',
  155. '#159AFF',
  156. '#FA73F5'
  157. ],
  158. currentYear:'',
  159. pest_list_arr:[],
  160. thumbnails: [],
  161. addtime:'',
  162. currentImg:{},
  163. bugMarkers: [],
  164. imageWidth: 0,
  165. imageHeight: 0,
  166. imgOld_id:'',
  167. containerHeight: '300px',
  168. pestDict: {}, // 虫子字典 {id: name}
  169. pestColorMap: {}, // 害虫颜色映射 {pestName: color}
  170. // 孢子仪2.1(device_code=1):本地渲染整组图片
  171. sporeTwo: false,
  172. currentIndex: 0,
  173. folderList: [],
  174. folderId: '',
  175. };
  176. },
  177. async onLoad(options) {
  178. const {id,img_id,currentYear,addtime} = options
  179. this.device_id = id
  180. this.imgOld_id = img_id
  181. this.img_id = img_id
  182. this.currentYear = currentYear
  183. // 孢子仪2.1:图片列表从 storage 读取,不走接口
  184. this.sporeTwo = options.spore_two == 1;
  185. if (this.sporeTwo) {
  186. this.folderId = options.fold_id || '';
  187. this.initSporePhotos();
  188. this.fetchSporeFolders();
  189. return;
  190. }
  191. this.addtime = this.formatTime(Number(addtime)*1000);
  192. this.time_begin = this.addtime;
  193. this.defaultDate = this.addtime;
  194. this.time_end = this.addtime;
  195. this.selectorRange = [];
  196. const nowYear = new Date().getFullYear();
  197. for(let i = 0;i<50;i++){
  198. const item = {
  199. label: nowYear - i,
  200. id: nowYear - i
  201. }
  202. this.selectorRange.push(item);
  203. }
  204. this.pestList = [];
  205. // await this.getPestDict();
  206. // this.getPestLevelMap();
  207. this.getDevicePhotoDetails();
  208. // this.getDevicePhotoDetails();
  209. },
  210. methods: {
  211. // 孢子仪2.1:读取列表页带过来的整组图片,定位到点击的那张
  212. initSporePhotos() {
  213. try {
  214. this.thumbnails = JSON.parse(uni.getStorageSync('bzy_two_folder_imgs') || '[]');
  215. } catch (error) {
  216. this.thumbnails = [];
  217. }
  218. uni.removeStorageSync('bzy_two_folder_imgs');
  219. this.thumbnails = (this.thumbnails || []).filter(img => img && img.addr);
  220. const idx = this.thumbnails.findIndex(img => String(img.id) === String(this.img_id));
  221. this.currentIndex = idx > -1 ? idx : 0;
  222. this.currentImg = this.thumbnails[this.currentIndex] || {};
  223. this.getPestDict();
  224. },
  225. // 优先取文件名里的拍摄时间(20260905110150z0x0_sharp_1.jpg),孢子仪2.1所有图的addtime都是文件夹时间
  226. formatItemTime(item) {
  227. const match = String(item.addr || '').match(/(\d{14})/);
  228. if (match) {
  229. const s = match[1];
  230. return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)} ${s.slice(8, 10)}:${s.slice(10, 12)}:${s.slice(12, 14)}`;
  231. }
  232. return this.formatDate(item.addtime);
  233. },
  234. // 孢子仪2.1:拉取文件夹列表
  235. async fetchSporeFolders() {
  236. try {
  237. const res = await this.$myRequest({
  238. url: '/api/api_gateway?method=forecast.bzy.bzy_file_fold',
  239. method: 'POST',
  240. data: {
  241. device_id: this.device_id,
  242. page: 1,
  243. size: 999,
  244. },
  245. });
  246. this.folderList = res?.data || [];
  247. } catch (error) {
  248. this.folderList = [];
  249. }
  250. },
  251. // 孢子仪2.1:切换文件夹,重新加载该文件夹的图片
  252. async handleFolderClick(item) {
  253. if (!item || !item.id || item.id == this.folderId) return;
  254. this.folderId = item.id;
  255. uni.showLoading({ title: '加载中' });
  256. try {
  257. const res = await this.$myRequest({
  258. url: '/api/api_gateway?method=forecast.bzy.bzy_two_percent_one_photo',
  259. method: 'POST',
  260. data: {
  261. id: item.id,
  262. page: 1,
  263. size: 32,
  264. },
  265. });
  266. this.thumbnails = (res?.data || []).filter(img => img && img.addr);
  267. this.currentIndex = 0;
  268. this.currentImg = this.thumbnails[0] || {};
  269. // 重置图片尺寸,等新图加载后重算识别框
  270. this.imageWidth = 0;
  271. this.imageHeight = 0;
  272. this.processSporePhoto();
  273. } catch (error) {
  274. this.thumbnails = [];
  275. } finally {
  276. uni.hideLoading();
  277. }
  278. },
  279. // 20260905110035 → 2026-09-05 11:00
  280. formatFold(str) {
  281. const s = String(str || '');
  282. if (s.length < 12) return s;
  283. return `${s.slice(0, 4)}${s.slice(4, 6)}${s.slice(6, 8)}_${s.slice(8, 10)}${s.slice(10, 12)}`;
  284. },
  285. // 孢子仪2.1:缩略图点击切换当前图片
  286. onThumbnailClick(item, index) {
  287. if (this.sporeTwo) {
  288. if (index === this.currentIndex) return;
  289. this.currentIndex = index;
  290. this.currentImg = this.thumbnails[index] || {};
  291. this.processSporePhoto();
  292. return;
  293. }
  294. this.selectThumbnail(item);
  295. },
  296. // 孢子仪2.1:根据 label 里的识别框生成标记层和识别结果(label: [{"类目id": [x1,y1,x2,y2,置信度]}])
  297. processSporePhoto() {
  298. const img = this.currentImg || {};
  299. let label = img.label || [];
  300. if (typeof label === 'string') {
  301. try {
  302. label = JSON.parse(label.replace(/'/g, '"'));
  303. } catch (error) {
  304. label = [];
  305. }
  306. }
  307. if (!Array.isArray(label)) label = [];
  308. // 图片未加载完成时先给默认值,加载完成后 onImageLoad 会重算
  309. if (!this.imageWidth || !this.imageHeight) {
  310. this.imageWidth = 1000;
  311. this.imageHeight = 1000;
  312. }
  313. const markers = [];
  314. const counts = new Map();
  315. label.forEach(item => {
  316. for (let key in item) {
  317. const box = item[key] || [];
  318. const [x1, y1, x2, y2] = box;
  319. const name = this.pestDict[key] || this.pestDict[String(key)] || key;
  320. counts.set(name, (counts.get(name) || 0) + 1);
  321. if (x1 === undefined || !this.imageWidth) continue;
  322. markers.push({
  323. id: name,
  324. x: (x1 / this.imageWidth) * 100,
  325. y: (y1 / this.imageHeight) * 100,
  326. width: ((x2 - x1) / this.imageWidth) * 100,
  327. height: ((y2 - y1) / this.imageHeight) * 100,
  328. color: this.getPestColor(name),
  329. text: name
  330. });
  331. }
  332. });
  333. this.bugMarkers = markers;
  334. this.pestList = [...counts.entries()].map(([text, count]) => ({ text, count }));
  335. },
  336. getPestName(id){
  337. return this.pestDict[id] || id;
  338. },
  339. async confirmHandler(e){
  340. this.currentYear = this.selectorRange[e].id;
  341. if(this.currentYear == new Date().getFullYear()){
  342. this.time_begin = this.formatTime(new Date(new Date().getFullYear(), 0, 1));
  343. this.time_end = this.formatTime(new Date());
  344. this.maxDate = this.formatTime(new Date());
  345. this.minDate = this.formatTime(new Date(new Date().getFullYear(), 0, 1));
  346. }else{
  347. this.time_begin = this.formatTime(new Date(this.currentYear, 0, 1));
  348. this.time_end = this.formatTime(new Date(this.currentYear, 11, 31));
  349. this.maxDate = this.formatTime(new Date(this.currentYear, 11, 31));
  350. this.minDate = this.formatTime(new Date(this.currentYear, 0, 1));
  351. this.defaultDate = this.minDate;
  352. }
  353. this.showPicker = false;
  354. this.pestList = [];
  355. this.thumbnails = [];
  356. await this.getDevicePhotoDetails();
  357. },
  358. async handleChange(e){
  359. console.log(e,'e')
  360. this.time_begin = e.result;
  361. this.time_end = e.result;
  362. this.pestList = [];
  363. await this.getDevicePhotoDetails()
  364. },
  365. getPestColor(pestName) {
  366. // 如果该害虫已有颜色,直接返回
  367. if (this.pestColorMap[pestName]) {
  368. return this.pestColorMap[pestName];
  369. }
  370. // 为新害虫分配颜色
  371. const usedColors = Object.values(this.pestColorMap);
  372. // 找一个未使用的颜色
  373. let color;
  374. for (const c of this.colors) {
  375. if (!usedColors.includes(c)) {
  376. color = c;
  377. break;
  378. }
  379. }
  380. // 如果所有颜色都用完了,使用哈希值确定颜色
  381. if (!color) {
  382. const hash = pestName.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
  383. color = this.colors[hash % this.colors.length];
  384. }
  385. this.pestColorMap[pestName] = color;
  386. return color;
  387. },
  388. getLevelDisplayName(level) {
  389. const levelMap = {
  390. '-1': '其他害虫',
  391. 1: '一类害虫',
  392. 2: '二类害虫',
  393. 3: '三类害虫',
  394. 4: '四类害虫',
  395. 5: '五类害虫'
  396. };
  397. return levelMap[String(level)] || `${level}`;
  398. },
  399. formatTime(dateString) {
  400. const date = new Date(dateString);
  401. const year = date.getFullYear();
  402. const month = String(date.getMonth() + 1).padStart(2, '0');
  403. const day = String(date.getDate()).padStart(2, '0');
  404. return `${year}-${month}-${day}`;
  405. },
  406. async getPestLevelMap() {
  407. const res = await this.$myRequest({
  408. url: '/api/api_gateway?method=forecast.new_cbd.pest_level_map',
  409. method: 'POST',
  410. });
  411. const pestYype = {}
  412. for(let key in res){
  413. pestYype[this.getLevelDisplayName(key)] = res[key]
  414. }
  415. this.pestYype = pestYype
  416. },
  417. // 获取虫子字典
  418. async getPestDict() {
  419. try {
  420. const res = await this.$myRequest({
  421. url: '/api/api_gateway?method=forecast.pest_info.pest_dict',
  422. method: 'POST',
  423. data:{
  424. type_name: '1'
  425. }
  426. });
  427. this.pestDict = res || {};
  428. } catch (error) {
  429. console.error('获取虫子字典失败:', error);
  430. this.pestDict = {};
  431. }
  432. },
  433. formatDate(dateString) {
  434. const date = new Date(dateString*1000);
  435. const year = date.getFullYear();
  436. const month = String(date.getMonth() + 1).padStart(2, '0');
  437. const day = String(date.getDate()).padStart(2, '0');
  438. const hour = String(date.getHours()).padStart(2, '0');
  439. const minute = String(date.getMinutes()).padStart(2, '0');
  440. const second = String(date.getSeconds()).padStart(2, '0');
  441. return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
  442. },
  443. async getDevicePhotoDetails() {
  444. const res = await this.$myRequest({
  445. url: '/api/api_gateway?method=forecast.forecast_system.bzy_photo_detail',
  446. method: 'POST',
  447. data: {
  448. cmd:'bzy',
  449. img_id: this.img_id,
  450. },
  451. });
  452. this.currentImg = res;
  453. console.log(this.currentImg,'thiscurrentImg')
  454. // 处理label参数生成bugMarkers
  455. this.processBugMarkers();
  456. const pest_list = res.pest_list || [];
  457. const pestArr = new Map()
  458. const pesAlltList = []
  459. for(let key in this.pestYype){
  460. for(let j in this.pestYype[key]){
  461. const pestName = this.pestYype[key][j]
  462. pesAlltList.push(pestName)
  463. pest_list?.forEach(item => {
  464. if(item.pest_name == pestName){
  465. if(pestArr.has(key)){
  466. pestArr.get(key).push(item)
  467. }else{
  468. pestArr.set(key, [item])
  469. }
  470. }
  471. })
  472. }
  473. }
  474. },
  475. onImageLoad(e) {
  476. this.imageWidth = e.detail.width;
  477. this.imageHeight = e.detail.height;
  478. // 计算容器显示高度(根据屏幕宽度和图片宽高比)
  479. const systemInfo = uni.getSystemInfoSync();
  480. const screenWidth = systemInfo.windowWidth;
  481. const containerWidth = screenWidth - 24; // 减去左右margin (24rpx ≈ 12px)
  482. const displayHeight = (containerWidth / this.imageWidth) * this.imageHeight;
  483. this.containerHeight = displayHeight + 'px';
  484. console.log('图片尺寸:', this.imageWidth, this.imageHeight, '容器高度:', this.containerHeight);
  485. // 图片加载完成后处理bugMarkers
  486. if (this.sporeTwo) {
  487. this.processSporePhoto();
  488. return;
  489. }
  490. this.processBugMarkers();
  491. },
  492. processBugMarkers() {
  493. console.log('22222', this.currentImg);
  494. if(this.currentImg.is_mark){
  495. if(!this.currentImg.mark){
  496. this.bugMarkers = [];
  497. return;
  498. }
  499. }else{
  500. if(!this.currentImg.label){
  501. this.bugMarkers = [];
  502. return;
  503. }
  504. }
  505. // 如果没有图片尺寸,先设置默认值
  506. if (!this.imageWidth || !this.imageHeight) {
  507. console.log('没有图片尺寸,使用默认值');
  508. this.imageWidth = 1000;
  509. this.imageHeight = 1000;
  510. }
  511. try {
  512. // 处理label参数,将单引号替换为双引号
  513. // let labelStr = this.currentImg.label;
  514. // let label
  515. // if(labelStr){
  516. // labelStr = labelStr?.replace(/'/g, '"');
  517. // console.log(labelStr,'labelStrlabelStr')
  518. // label = JSON.parse(labelStr);
  519. // }
  520. const markers = [];
  521. // 根据原图宽度计算缩放比例
  522. let scaleRatio = 3.1;
  523. if (this.imageWidth >= 5000) {
  524. scaleRatio = 4;
  525. } else if (this.imageWidth >= 4000) {
  526. scaleRatio = 1;
  527. } else {
  528. scaleRatio = 2.39;
  529. }
  530. console.log('原图宽度:', this.imageWidth, '缩放比例:', scaleRatio);
  531. const mark = this.currentImg.mark || []
  532. if(this.currentImg.is_mark){
  533. mark.forEach(item=>{
  534. const { startX, startY,text } = item;
  535. const x = item.width > 0 ? (startX * scaleRatio / this.imageWidth) * 105 : ((startX + item.width) * scaleRatio / this.imageWidth) * 105;
  536. const y = item.height > 0 ? (startY * scaleRatio / this.imageHeight) * 105 : ((startY + item.height) * scaleRatio / this.imageHeight) * 105;
  537. const width = (item.width > 0 ? item.width : -item.width) * scaleRatio / this.imageWidth * 100;
  538. const height = (item.height > 0 ? item.height : -item.height) * scaleRatio / this.imageHeight * 100;
  539. markers.push({
  540. id: item.text, // 显示虫子名字
  541. x,
  542. y,
  543. width,
  544. height,
  545. text:item.text,
  546. color:this.getPestColor(item.text)
  547. });
  548. })
  549. }else{
  550. label?.forEach(item => {
  551. for (let key in item) {
  552. const [x1, y1, x2, y2] = item[key];
  553. // 使用缩放比例计算标记位置和尺寸
  554. const x = (x1 * scaleRatio / this.imageWidth) * 100;
  555. const y = (y1 * scaleRatio / this.imageHeight) * 100;
  556. const width = ((x2 - x1) * scaleRatio / this.imageWidth) * 100;
  557. const height = ((y2 - y1) * scaleRatio / this.imageHeight) * 100;
  558. // 从字典中获取虫子名字(尝试字符串和数字两种类型)
  559. const pestName = this.pestDict[key] || this.pestDict[String(key)] || key;
  560. const color = this.getPestColor(pestName);
  561. markers.push({
  562. id: pestName, // 显示虫子名字
  563. x,
  564. y,
  565. width,
  566. height,
  567. color,
  568. text:pestName,
  569. });
  570. }
  571. });
  572. }
  573. console.log(markers,'markersmarkers')
  574. this.bugMarkers = markers;
  575. // 改成按照text合并,并把数量也合并
  576. const pestArr = new Map()
  577. markers.forEach(item=>{
  578. if(!pestArr.has(item.text)){
  579. pestArr.set(item.text, 1)
  580. }else{
  581. pestArr.set(item.text, pestArr.get(item.text) + 1)
  582. }
  583. })
  584. console.log(pestArr,'pestArr')
  585. const arr = []
  586. for(let [key, value] of pestArr){
  587. arr.push({
  588. text:key,
  589. count:value
  590. })
  591. }
  592. this.pestList = [...arr];
  593. } catch (error) {
  594. console.error('处理label参数失败:', error);
  595. this.bugMarkers = [];
  596. }
  597. },
  598. handleBack() {
  599. uni.navigateBack({
  600. delta: 1
  601. });
  602. },
  603. goBack() {
  604. uni.navigateBack();
  605. },
  606. selectThumbnail(item) {
  607. this.img_id = item.ids;
  608. this.getDevicePhotoDetails();
  609. }
  610. }
  611. };
  612. </script>
  613. <style lang="scss" scoped>
  614. ::v-deep .u-calendar__action{
  615. display:flex;
  616. justify-content: space-between;
  617. }
  618. ::v-deep .u-hover-class{
  619. .u-calendar__content__item__inner{
  620. color:#aaa !important;
  621. }
  622. }
  623. .device-photo-page {
  624. background-color: #F5F5F5;
  625. min-height: 100vh;
  626. width: 100%;
  627. padding-top: 112rpx;
  628. background: linear-gradient(180deg, #ffffff00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
  629. .device-detail__header {
  630. width: 100%;
  631. font-size: 28rpx;
  632. color: #999;
  633. color: #042118;
  634. font-family: 'Source Han Sans CN VF';
  635. font-weight: 700;
  636. position: relative;
  637. text-align: center;
  638. .arrow-left {
  639. position: absolute;
  640. left: 32rpx;
  641. margin-right: 12rpx;
  642. }
  643. }
  644. // 孢子仪2.1:文件夹列表
  645. .tabs-fold {
  646. display: flex;
  647. align-items: center;
  648. text-align: center;
  649. // 可左右滚动
  650. overflow-x: auto;
  651. white-space: nowrap;
  652. padding-bottom: 24rpx;
  653. margin-top: 48rpx;
  654. // 隐藏滚动条
  655. -ms-overflow-style: none;
  656. scrollbar-width: none;
  657. .tabs-fold-item {
  658. display: flex;
  659. align-items: center;
  660. justify-content: center;
  661. flex-direction: column;
  662. flex-shrink: 0; // 不压缩子项,超出容器宽度才能横向滚动
  663. background: #FFFFFF;
  664. border-radius: 24rpx;
  665. border: 4rpx solid transparent;
  666. box-sizing: border-box;
  667. width: 180rpx;
  668. padding: 16rpx 0;
  669. margin: 0 12rpx;
  670. text-align: center;
  671. font-size: 24rpx;
  672. font-weight: 400;
  673. color: #656565;
  674. font-family: 'Source Han Sans CN VF';
  675. &.active {
  676. border-color: #0BBC58;
  677. }
  678. .tabs-fold-icon {
  679. width: 48rpx;
  680. height: 48rpx;
  681. margin-bottom: 12rpx;
  682. }
  683. }
  684. }
  685. /* 顶部导航栏 */
  686. .nav-bar {
  687. display: flex;
  688. align-items: center;
  689. justify-content: space-between;
  690. padding: 24rpx;
  691. background-color: #E6F7EF;
  692. .nav-left {
  693. .back-icon {
  694. font-size: 36rpx;
  695. color: #042118;
  696. }
  697. }
  698. .nav-title {
  699. font-size: 32rpx;
  700. font-weight: 700;
  701. color: #042118;
  702. }
  703. .nav-right {
  704. display: flex;
  705. align-items: center;
  706. .more-icon {
  707. font-size: 32rpx;
  708. color: #042118;
  709. margin-right: 24rpx;
  710. }
  711. .eye-icon {
  712. font-size: 32rpx;
  713. }
  714. }
  715. }
  716. .date-container{
  717. padding: 0 20rpx;
  718. display: flex;
  719. align-items: center;
  720. justify-content: center;
  721. .select-year{
  722. width: 150rpx;
  723. height: 80rpx;
  724. display: flex;
  725. align-items: center;
  726. justify-content: space-around;
  727. color: #656565;
  728. font-size: 26rpx;
  729. line-height: 80rpx;
  730. text-align: center;
  731. border-radius: 48rpx;
  732. background-color: #FFFFFF;
  733. margin-right: 10rpx;
  734. padding: 0 26rpx;
  735. .select-year-item{
  736. width: 100%;
  737. display: flex;
  738. align-items: center;
  739. justify-content: space-around;
  740. }
  741. }
  742. }
  743. /* 日期选择器 */
  744. .date-picker {
  745. display: flex;
  746. align-items: center;
  747. padding: 20rpx 24rpx;
  748. background-color: #FFFFFF;
  749. margin-bottom: 24rpx;
  750. width: 100%;
  751. margin:20rpx auto;
  752. border-radius: 48rpx;
  753. position: relative;
  754. .date-input {
  755. display: flex;
  756. flex-direction: column;
  757. width: 100%;
  758. text-align: center;
  759. .date-label {
  760. font-size: 24rpx;
  761. color: #999999;
  762. margin-bottom: 8rpx;
  763. }
  764. .date-value {
  765. font-size: 28rpx;
  766. color: #042118;
  767. font-weight: 500;
  768. }
  769. }
  770. .calendar {
  771. position: absolute;
  772. right: 24rpx;
  773. top: 50%;
  774. transform: translateY(-50%);
  775. font-size: 32rpx;
  776. color: #999999;
  777. }
  778. .date-separator {
  779. margin: 0 24rpx;
  780. font-size: 28rpx;
  781. color: #999999;
  782. }
  783. }
  784. /* 主图片区域 */
  785. .main-photo {
  786. position: relative;
  787. margin: 24rpx 24rpx 24rpx;
  788. background-color: #FFFFFF;
  789. border-radius: 16rpx;
  790. overflow: hidden;
  791. .movable-area {
  792. width: 100%;
  793. overflow: hidden;
  794. }
  795. .movable-view {
  796. width: 100%;
  797. // 显式高度:movable-view 依赖宽高计算各方向拖动边界,高度缺失会导致放大后无法上下拖动
  798. height: 100%;
  799. }
  800. .image-container {
  801. position: relative;
  802. width: 100%;
  803. }
  804. .main-photo-image {
  805. width: 100%;
  806. display: block;
  807. }
  808. /* 虫子标记层 */
  809. .bug-markers {
  810. position: absolute;
  811. top: 0;
  812. left: 0;
  813. width: 100%;
  814. height: 100%;
  815. pointer-events: none;
  816. }
  817. /* 虫子标记 */
  818. .bug-marker {
  819. position: absolute;
  820. border: 2rpx solid;
  821. border-radius: 4rpx;
  822. }
  823. /* 虫子标记标签 */
  824. .bug-label {
  825. position: absolute;
  826. top: -24rpx;
  827. left: 0;
  828. padding: 2rpx 6rpx;
  829. border-radius: 4rpx;
  830. font-size: 20rpx;
  831. color: white;
  832. white-space: nowrap;
  833. }
  834. /* 虫子标记层 */
  835. .bug-markers {
  836. position: absolute;
  837. top: 0;
  838. left: 0;
  839. width: 100%;
  840. height: 100%;
  841. pointer-events: none;
  842. }
  843. /* 虫子标记 */
  844. .bug-marker {
  845. position: absolute;
  846. border: 2rpx solid;
  847. border-radius: 4rpx;
  848. pointer-events: auto;
  849. }
  850. /* 虫子标记标签 */
  851. .bug-label {
  852. position: absolute;
  853. top: -30rpx;
  854. left: 0;
  855. padding: 4rpx 8rpx;
  856. border-radius: 4rpx;
  857. font-size: 20rpx;
  858. font-weight: bold;
  859. }
  860. .photo-timestamp {
  861. position: absolute;
  862. bottom: 0rpx;
  863. left: 0rpx;
  864. width:100%;
  865. background-color: rgba(0, 0, 0, 0.6);
  866. color: #FFFFFF;
  867. padding: 8rpx 16rpx;
  868. border-radius: 8rpx;
  869. font-size: 24rpx;
  870. }
  871. }
  872. /* 缩略图预览 */
  873. .thumbnail-preview {
  874. margin: 0 24rpx 24rpx;
  875. .thumbnail-scroll {
  876. gap: 16rpx;
  877. padding-bottom: 16rpx;
  878. box-sizing: border-box;
  879. white-space: nowrap;
  880. overflow-x: auto;
  881. overflow-y: hidden;
  882. width: 100%;
  883. // 隐藏滚动条
  884. -ms-overflow-style: none;
  885. scrollbar-width: none;
  886. .thumbnail-item {
  887. width: 120rpx;
  888. height: 120rpx;
  889. border-radius: 8rpx;
  890. overflow: hidden;
  891. border: 2rpx solid transparent;
  892. display: inline-block;
  893. box-sizing: border-box;
  894. margin-right: 10rpx;
  895. &.active {
  896. border-color: #0bbc58;
  897. }
  898. .thumbnail-image {
  899. width: 100%;
  900. height: 100%;
  901. object-fit: cover;
  902. }
  903. }
  904. }
  905. }
  906. /* 识别结果 */
  907. .recognition-result {
  908. margin: 0 24rpx 24rpx;
  909. background-color: #FFFFFF;
  910. border-radius: 16rpx;
  911. padding: 24rpx;
  912. .result-title {
  913. font-size: 28rpx;
  914. font-weight: 700;
  915. color: #042118;
  916. margin-bottom: 24rpx;
  917. }
  918. /* 害虫类别 */
  919. .pest-category {
  920. margin-bottom: 32rpx;
  921. border: 1px solid #E4E7ED;
  922. border-radius: 16rpx;
  923. &:last-child {
  924. margin-bottom: 0;
  925. }
  926. .category-header {
  927. display: flex;
  928. justify-content: space-between;
  929. margin-bottom: 16rpx;
  930. background: #F6F8FC;
  931. padding: 18rpx 24rpx;
  932. .category-title {
  933. font-size: 24rpx;
  934. font-weight: 500;
  935. color: #042118;
  936. }
  937. .category-count {
  938. font-size: 24rpx;
  939. color: #999999;
  940. }
  941. }
  942. .pest-item {
  943. display: flex;
  944. justify-content: space-between;
  945. align-items: center;
  946. margin-bottom: 12rpx;
  947. padding: 6rpx 24rpx;
  948. &:last-child {
  949. margin-bottom: 0;
  950. }
  951. .pest-info {
  952. display: flex;
  953. align-items: center;
  954. .pest-color {
  955. width: 16rpx;
  956. height: 16rpx;
  957. border-radius: 50%;
  958. margin-right: 12rpx;
  959. }
  960. .pest-name {
  961. font-size: 24rpx;
  962. color: #042118;
  963. }
  964. }
  965. .pest-count {
  966. font-size: 24rpx;
  967. color: #042118;
  968. }
  969. }
  970. }
  971. }
  972. }
  973. </style>