devicePhoto.vue 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  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. }
  798. .image-container {
  799. position: relative;
  800. width: 100%;
  801. }
  802. .main-photo-image {
  803. width: 100%;
  804. display: block;
  805. }
  806. /* 虫子标记层 */
  807. .bug-markers {
  808. position: absolute;
  809. top: 0;
  810. left: 0;
  811. width: 100%;
  812. height: 100%;
  813. pointer-events: none;
  814. }
  815. /* 虫子标记 */
  816. .bug-marker {
  817. position: absolute;
  818. border: 2rpx solid;
  819. border-radius: 4rpx;
  820. }
  821. /* 虫子标记标签 */
  822. .bug-label {
  823. position: absolute;
  824. top: -24rpx;
  825. left: 0;
  826. padding: 2rpx 6rpx;
  827. border-radius: 4rpx;
  828. font-size: 20rpx;
  829. color: white;
  830. white-space: nowrap;
  831. }
  832. /* 虫子标记层 */
  833. .bug-markers {
  834. position: absolute;
  835. top: 0;
  836. left: 0;
  837. width: 100%;
  838. height: 100%;
  839. pointer-events: none;
  840. }
  841. /* 虫子标记 */
  842. .bug-marker {
  843. position: absolute;
  844. border: 2rpx solid;
  845. border-radius: 4rpx;
  846. pointer-events: auto;
  847. }
  848. /* 虫子标记标签 */
  849. .bug-label {
  850. position: absolute;
  851. top: -30rpx;
  852. left: 0;
  853. padding: 4rpx 8rpx;
  854. border-radius: 4rpx;
  855. font-size: 20rpx;
  856. font-weight: bold;
  857. }
  858. .photo-timestamp {
  859. position: absolute;
  860. bottom: 0rpx;
  861. left: 0rpx;
  862. width:100%;
  863. background-color: rgba(0, 0, 0, 0.6);
  864. color: #FFFFFF;
  865. padding: 8rpx 16rpx;
  866. border-radius: 8rpx;
  867. font-size: 24rpx;
  868. }
  869. }
  870. /* 缩略图预览 */
  871. .thumbnail-preview {
  872. margin: 0 24rpx 24rpx;
  873. .thumbnail-scroll {
  874. gap: 16rpx;
  875. padding-bottom: 16rpx;
  876. box-sizing: border-box;
  877. white-space: nowrap;
  878. overflow-x: auto;
  879. overflow-y: hidden;
  880. width: 100%;
  881. // 隐藏滚动条
  882. -ms-overflow-style: none;
  883. scrollbar-width: none;
  884. .thumbnail-item {
  885. width: 120rpx;
  886. height: 120rpx;
  887. border-radius: 8rpx;
  888. overflow: hidden;
  889. border: 2rpx solid transparent;
  890. display: inline-block;
  891. box-sizing: border-box;
  892. margin-right: 10rpx;
  893. &.active {
  894. border-color: #0bbc58;
  895. }
  896. .thumbnail-image {
  897. width: 100%;
  898. height: 100%;
  899. object-fit: cover;
  900. }
  901. }
  902. }
  903. }
  904. /* 识别结果 */
  905. .recognition-result {
  906. margin: 0 24rpx 24rpx;
  907. background-color: #FFFFFF;
  908. border-radius: 16rpx;
  909. padding: 24rpx;
  910. .result-title {
  911. font-size: 28rpx;
  912. font-weight: 700;
  913. color: #042118;
  914. margin-bottom: 24rpx;
  915. }
  916. /* 害虫类别 */
  917. .pest-category {
  918. margin-bottom: 32rpx;
  919. border: 1px solid #E4E7ED;
  920. border-radius: 16rpx;
  921. &:last-child {
  922. margin-bottom: 0;
  923. }
  924. .category-header {
  925. display: flex;
  926. justify-content: space-between;
  927. margin-bottom: 16rpx;
  928. background: #F6F8FC;
  929. padding: 18rpx 24rpx;
  930. .category-title {
  931. font-size: 24rpx;
  932. font-weight: 500;
  933. color: #042118;
  934. }
  935. .category-count {
  936. font-size: 24rpx;
  937. color: #999999;
  938. }
  939. }
  940. .pest-item {
  941. display: flex;
  942. justify-content: space-between;
  943. align-items: center;
  944. margin-bottom: 12rpx;
  945. padding: 6rpx 24rpx;
  946. &:last-child {
  947. margin-bottom: 0;
  948. }
  949. .pest-info {
  950. display: flex;
  951. align-items: center;
  952. .pest-color {
  953. width: 16rpx;
  954. height: 16rpx;
  955. border-radius: 50%;
  956. margin-right: 12rpx;
  957. }
  958. .pest-name {
  959. font-size: 24rpx;
  960. color: #042118;
  961. }
  962. }
  963. .pest-count {
  964. font-size: 24rpx;
  965. color: #042118;
  966. }
  967. }
  968. }
  969. }
  970. }
  971. </style>