devicePhoto.vue 29 KB

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