devicePhoto2.vue 29 KB

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