devicePhoto.vue 28 KB

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