epcSimpleDetailOne.vue 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. <template>
  2. <view class="box">
  3. <view class="zdyNavBox">
  4. <view class="status_bar" :style="{height: iStatusBarHeight + 'px'}"></view>
  5. <view class="zdyNav">
  6. <!-- <view class="zdyNavLeft">
  7. <div @click="goback" class="uni-page-head-btn"><i class="uni-btn-icon"
  8. style="color: rgb(0, 0, 0); font-size: 27px;"></i></div>
  9. </view>
  10. <view class="xx" style="position: absolute;left:100rpx;color: black;font-size: 23px;" @click="goToSelectCarModel">X</view> -->
  11. <view class="zdyNavLeft">
  12. <div @click="goback" class="uni-page-head-btn"><i class="uni-btn-icon"
  13. style="color: #333333; font-size: 27px;" ></i></div>
  14. </view>
  15. <view class="xx" style="margin-left: -70rpx;" @click="goToSelectCarModel">
  16. <image src="/static/img/group2.png" style="width: 27rpx;height: 27rpx;"></image>
  17. </view>
  18. <view class="tab-box" @click="changeIsShow()">
  19. <view class="title">{{chlildObj.title}}</view>
  20. <view class="sanjiao" v-if="isShowAll == 1">▲</view>
  21. <view class="sanjiao" :class="{shangjian:isShowAll == 0}" v-if="isShowAll == 0">▼</view>
  22. </view>
  23. <!-- <view class="zdyNavTitle" @click="changeIsShow()">
  24. <view class="title">{{chlildObj.title}}</view>
  25. <view class="sanjiao" v-if="isShowAll == 1">▲</view>
  26. <view class="sanjiao" v-if="isShowAll == 0">▼</view>
  27. </view> -->
  28. <view style="width: 120rpx;"></view>
  29. </view>
  30. <view class="title-box" v-if="isShowAll == 1" style="z-index: 999;">
  31. <view class="item" v-for="(item,index) in allChildrenObj" :key="index" @click="clickTitle(item)">
  32. <view class="item-image">
  33. <image :src="item.image" mode="aspectFit" style="width: 100%;height: 180rpx;"></image>
  34. </view>
  35. <view class="item-title" >
  36. {{item.title}}
  37. </view>
  38. </view>
  39. <view v-if="isShowAll == 1" style="height: 100vh;;z-index: 998;" @click="closeTitleTab()"></view>
  40. <view v-if="isShowAll == 1" style="height: 100vh;;z-index: 998;" @click="closeTitleTab()"></view>
  41. <view v-if="isShowAll == 1" style="height: 100vh;;z-index: 998;" @click="closeTitleTab()"></view>
  42. </view>
  43. </view>
  44. <view class="box-image-big" v-if="showType == 0">
  45. <view class="image-big">
  46. <image :src="chlildObj.image" mode="aspectFit" style="width: 100%;height: 80vh"></image>
  47. <canvas canvas-id="big-this-image" class="drawing-canvas" id="big-this-image" type="2d"
  48. @touchstart="handleTouchStartBig"
  49. @touchmove="handleTouchMoveBig"
  50. @touchend="handleTouchEndBig"></canvas>
  51. </view>
  52. <view class="box-btn">
  53. <view class="leftBtn" @click="getOther(-1)"><</view>
  54. <view class="rightBtn"@click="getOther(1)">></view>
  55. </view>
  56. </view>
  57. <view class="box-image-small" v-if="showType == 1" @tap="handleImageClick">
  58. <view class="small-image" >
  59. <image class="small-this-image" :src="chlildObj.image" mode="aspectFill" ></image>
  60. <!-- Canvas遮罩层 -->
  61. <canvas
  62. canvas-id="mask-canvas"
  63. class="mask-canvas"
  64. :style="{
  65. position: 'absolute',
  66. top: 0,
  67. left: 0,
  68. width: '100%',
  69. height: '100%',
  70. zIndex: 1,
  71. pointerEvents: 'none'
  72. }"
  73. ></canvas>
  74. </view>
  75. </view>
  76. <view class="box-jiantou">
  77. <view class="jian-span " :class="{'jian-down':isRotated}" @click="changeIsRotated()">》</view>
  78. </view>
  79. <view class="box-lab">
  80. <scroll-view scroll-x=true enable-flex=true show-scrollbar=false class="box-lab-scr" style="white-space: nowrap;height: 90rpx;" :scroll-into-view="scrollIntoId">
  81. <view class="lab-titles" :class="{'clickLab':labtitile==index}" :id="'item-' + index" @click="labTitile(item,index)" v-for="(item,index) in chlildObj.children" :key="index">
  82. {{item.title}}
  83. </view>
  84. </scroll-view>
  85. </view>
  86. <view class="lab-grid" v-if="showType == 1">
  87. <view class="lab-child" v-for="(item,index) in labChildObj" :key="index" @click="labChildClick(item)">
  88. <view class="lab-child-image">
  89. <image :src="item.image" mode="aspectFit" style="width: 160rpx;height: 150rpx;"></image>
  90. </view>
  91. <view class="lab-child-title">
  92. {{item.title}}
  93. </view>
  94. </view>
  95. </view>
  96. </view>
  97. </template>
  98. <script>
  99. export default {
  100. data() {
  101. return {
  102. toke:"",
  103. param:"",
  104. access_time:"",
  105. epc_id:"",
  106. brand_id:"",
  107. iStatusBarHeight: '',
  108. chlildObj:null,
  109. allChildrenObj:null,
  110. isShowAll:0,
  111. isRotated: false,
  112. labtitile: 0,
  113. labChildObj:null,
  114. showType:0, //0大图1小图
  115. ctxBig: null, //大画布
  116. pointsBig: [], // 存储所有点的坐标数组
  117. isDrawingBig: false, // 是否正在绘制的标志
  118. currentPathBig: [], // 当前路径的点数组
  119. ctxMask: null, //小画布
  120. pointsSmall: [], // 存储所有点的坐标数组
  121. isDrawingSmall: false, // 是否正在绘制的标志
  122. currentPathSmall: [], // 当前路径的点数组
  123. hitChild:[],//圈选中对象
  124. thisWidth: 0,//当前屏幕宽度
  125. scrollIntoId: '', //工东严肃定位
  126. specifiedAreas: [], //预定义位置
  127. unmaskAreas: [], // 已取消遮罩的区域
  128. unmaskSize: 80,
  129. brand_name:'',
  130. caption:'',
  131. vin:'',
  132. }
  133. },
  134. onLoad(opt) {
  135. this.iStatusBarHeight = uni.getSystemInfoSync().statusBarHeight;
  136. //当前对象
  137. this.chlildObj = uni.getStorageSync('epcChildren');
  138. //全部对象
  139. this.allChildrenObj = uni.getStorageSync('epcAllChildren');
  140. //当前详细
  141. this.labChildObj = this.chlildObj.children[this.labtitile].children;
  142. this.token = opt.token;
  143. this.param = opt.param;
  144. this.access_time = opt.access_time;
  145. this.epc_id = opt.epc_id;
  146. this.brand_id = opt.brand_id;
  147. this.brand_name = opt.brand_name;
  148. this.caption = opt.caption;
  149. this.vin = opt.vin;
  150. this.getSmileDate();
  151. //禁止下拉
  152. },
  153. onReady(){
  154. this.initCanvas();
  155. },
  156. onShow() {
  157. this.initCanvas(); // 确保重新加载数据
  158. },
  159. methods: {
  160. goback() {
  161. uni.navigateBack({})
  162. },
  163. //返回选品牌
  164. goToSelectCarModel(){
  165. let vin = this.vin;
  166. if(vin ==undefined || vin =='' ||vin == 'undefined' ){
  167. uni.navigateTo({url:'SelectCarModel'});
  168. }else{
  169. uni.navigateTo({url:'index'});
  170. }
  171. },
  172. //标题title点击事件
  173. changeIsShow(){
  174. this.isShowAll = this.isShowAll == 0 ? 1 : 0;
  175. },
  176. //上下箭头点击事件
  177. changeIsRotated(){
  178. this.isRotated = !this.isRotated;
  179. this.showType = this.isRotated ? 1:0;
  180. if (this.showType == 1) {
  181. this.$nextTick(() => {
  182. this.initSpecifiedAreasPush();
  183. });
  184. }else{
  185. this.clearUnmaskAreas()
  186. }
  187. },
  188. //左右按钮点击
  189. getOther(typeNum){
  190. //获取顺序
  191. let index = 0;
  192. for (var i = 0; i < this.allChildrenObj.length; i++) {
  193. let item = this.allChildrenObj[i]
  194. if (item.id == this.chlildObj.id){
  195. index = i;
  196. }
  197. }
  198. if(typeNum ==-1){
  199. index = index-1 < 0 ? this.allChildrenObj.length-1 : index-1 ;
  200. }else{
  201. index = index+1 > this.allChildrenObj.length-1 ? 0 : index+1 ;
  202. }
  203. this.chlildObj = this.allChildrenObj[index];
  204. this.labChildObj = this.chlildObj.children[index].children;
  205. },
  206. //标题下拉图片点击
  207. clickTitle(item){
  208. this.chlildObj = item;
  209. this.isShowAll = 0;
  210. this.showType = 0;
  211. this.initCanvas();
  212. this.getSmileDate();
  213. this.initSpecifiedAreasPush();
  214. },
  215. //labtitiel的点击事件
  216. labTitile(item,index){
  217. this.showType = 1;
  218. this.isRotated = true;
  219. this.labtitile = index;
  220. this.labChildObj = this.chlildObj.children[this.labtitile].children;
  221. this.scrollIntoId = 'item-' + index;
  222. //set 小图坐标
  223. //this.specifiedAreasPush(item);
  224. //延时等待渲染效果
  225. setTimeout(()=>{
  226. this.initSpecifiedAreasPush();
  227. },100)
  228. },
  229. //下布局grid点击图片事件
  230. labChildClick(item){
  231. var that = this;
  232. console.log(item);
  233. let url = "/simpleEpc/getCustomDetail";
  234. let data = {
  235. brand_id : this.brand_id,
  236. epc_id : this.epc_id,
  237. custom_id : item.id,
  238. token : this.token,
  239. access_time : this.access_time,
  240. param : this.param,
  241. vin:''
  242. };
  243. // let data = {
  244. // brand_id : this.brand_id,
  245. // epc_id : this.epc_id,
  246. // pid:item.id
  247. // };
  248. let res = this.textJieKou(url,data);
  249. res.then(response =>{
  250. let number = response.data.number;
  251. if(number != 200 ){
  252. uni.navigateTo({
  253. url: 'SimpleOemSearch?brand=' + undefined + '&token=' + this.token + '&param=' +
  254. this.param + '&access_time=' + this.access_time + '&title=' + item.title+'&epc_id='+this.epc_id+"&brand_name="+this.brand_name
  255. +"&caption="+this.caption
  256. })
  257. return;
  258. }
  259. let data = response.data.result.data.list;
  260. if(data.length > 1){//跳子组页
  261. uni.navigateTo({
  262. url: 'modelTwoSimple?brand=' + undefined + '&token=' + this.token + '&param=' +
  263. this.param + '&zzTime=' + this.access_time + '&title=' + item.title+'&epc_id='+this.epc_id+"&brand_name="+this.brand_name
  264. +"&caption="+this.caption
  265. })
  266. }else if(data.length == 1){//跳配件展示页
  267. uni.navigateTo({
  268. url: 'vinDetail?brand=' + undefined + '&token=' + data[0].token + '&param=' +
  269. data[0].param + '&access_time=' + response.data.result.data.access_time + '&title=' + item.title+'&epc_id='+this.epc_id+"&brand_name="+this.brand_name
  270. +"&tabIndex=0"
  271. })
  272. }
  273. })
  274. },
  275. //关闭title 下拉
  276. closeTitleTab(){
  277. this.isShowAll = 0;
  278. },
  279. //初始化画布
  280. initCanvas(){
  281. this.ctxBig = uni.createCanvasContext('big-this-image',this);
  282. //this.ctxSmall = uni.createCanvasContext('small-this-image',this);
  283. this.ctxMask = uni.createCanvasContext('mask-canvas', this);
  284. // 设置线条颜色
  285. this.ctxBig.setStrokeStyle("red");
  286. // 设置线宽
  287. this.ctxBig.setLineWidth(5);
  288. // 设置线条末端为圆形
  289. this.ctxBig.setLineCap("round");
  290. // 设置线条连接处为圆形
  291. this.ctxBig.setLineJoin("round");
  292. this.ctxBig.setLineDash([]); // 确保不是虚线
  293. // 设置更高的绘制质量
  294. this.ctxBig.setGlobalAlpha(1);
  295. this.ctxBig.setShadow(0, 0, 0, 'transparent'); // 清除阴影避免性能问题
  296. this.drawFullMask();
  297. },
  298. //小图遮罩初始化显示 正前
  299. initSpecifiedAreasPush(){
  300. let id = 0;
  301. //获取这是第几个labtitle
  302. const element = document.querySelector('.clickLab');
  303. if (element) {
  304. id = element.id.split("-")[1];
  305. }
  306. let tetxx = this.chlildObj.children[id].title;
  307. let isAreas = {"title":tetxx};
  308. this.specifiedAreasPush(isAreas);
  309. },
  310. // 处理触摸开始事件
  311. handleTouchStartBig(e) {
  312. // 获取触摸点的x,y坐标
  313. const x = e.touches[0].x;
  314. const y = e.touches[0].y;
  315. // 设置正在绘制标志
  316. this.isDrawingBig = true;
  317. // 初始化当前路径数组,并添加第一个点
  318. this.currentPathBig = [{x, y}];
  319. // 开始新路径
  320. this.ctxBig.beginPath();
  321. // 移动画笔到起始点
  322. this.ctxBig.moveTo(x, y);
  323. },
  324. // 处理触摸移动事件
  325. handleTouchMoveBig(e) {
  326. // 如果不是绘制状态则返回
  327. if (!this.isDrawingBig) return;
  328. // 获取当前触摸点的坐标
  329. const x = e.touches[0].x;
  330. const y = e.touches[0].y;
  331. // 将当前点添加到当前路径数组
  332. this.currentPathBig.push({x, y});
  333. // 当有足够点数时使用贝塞尔曲线
  334. if (this.currentPathBig.length >= 3) {
  335. this.drawSmoothLine();
  336. } else {
  337. this.drawStraightLine();
  338. }
  339. },
  340. // 绘制平滑曲线
  341. drawSmoothLine() {
  342. const points = this.currentPathBig;
  343. const len = points.length;
  344. if (len < 3) return;
  345. this.ctxBig.beginPath();
  346. this.ctxBig.moveTo(points[0].x, points[0].y);
  347. // 使用二次贝塞尔曲线平滑连接
  348. for (let i = 1; i < len - 2; i++) {
  349. const controlX = (points[i].x + points[i + 1].x) / 2;
  350. const controlY = (points[i].y + points[i + 1].y) / 2;
  351. this.ctxBig.quadraticCurveTo(points[i].x, points[i].y, controlX, controlY);
  352. }
  353. // 处理最后两个点
  354. if (len >= 3) {
  355. this.ctxBig.quadraticCurveTo(
  356. points[len - 2].x,
  357. points[len - 2].y,
  358. points[len - 1].x,
  359. points[len - 1].y
  360. );
  361. }
  362. this.ctxBig.stroke();
  363. this.ctxBig.draw(true);
  364. },
  365. // 绘制直线(点数不足时)
  366. drawStraightLine() {
  367. if (this.currentPathBig.length < 2) return;
  368. this.ctxBig.beginPath();
  369. this.ctxBig.moveTo(this.currentPathBig[0].x, this.currentPathBig[0].y);
  370. this.ctxBig.lineTo(this.currentPathBig[1].x, this.currentPathBig[1].y);
  371. this.ctxBig.stroke();
  372. this.ctxBig.draw(true);
  373. },
  374. // 处理触摸结束事件
  375. handleTouchEndBig() {
  376. // 如果不是绘制状态则返回
  377. if (!this.isDrawingBig) return;
  378. // 将当前路径的所有点合并到总点数组中
  379. this.pointsBig = [...this.pointsBig, ...this.currentPathBig];
  380. // 清空当前路径
  381. //this.currentPathBig = [];
  382. // 重置绘制状态
  383. this.isDrawingBig = false;
  384. //清空圈选对象
  385. this.hitChild = [];
  386. setTimeout(()=>{
  387. const width = this.ctxBig.width || 1024; // 如果没有设置width属性,使用默认值
  388. const height = this.ctxBig.height || 1024;
  389. this.ctxBig.clearRect(0,0,width,height);
  390. this.ctxBig.draw(true);
  391. this.isDrawingBig = true;
  392. this.toDetailByCanvas();
  393. },1000)
  394. },
  395. //筛选圈选的位置
  396. toDetailByCanvas(){
  397. //坐标集合
  398. let zuobiao = this.currentPathBig,
  399. //当前对象
  400. chlildObj = this.labChildObj;
  401. //击中对象
  402. const oneSet = new Set();
  403. zuobiao.forEach(x =>{
  404. //判断当前坐标是否在当前位置
  405. this.ifClickObj(x.x,x.y);
  406. console.log(this.hitChild);
  407. //判断是否跳转
  408. console.log(this.isGoTwo());
  409. if(this.isGoTwo()){
  410. //跳转
  411. uni.setStorageSync('epcChildrenTwo', this.hitChild);
  412. uni.navigateTo({
  413. url: 'epcSimpleDetailTwo?token='+this.token+'&param='+this.param+'&access_time='+this.access_time+'&epc_id='+this.epc_id+"&title="+this.chlildObj.title
  414. +'&brand_id='+this.brand_id+"&brand_name="+this.brand_name+"&caption="+this.caption+"&vin="+this.vin
  415. })
  416. }
  417. })
  418. },
  419. isGoTwo(){
  420. if (this.hitChild.length === 0) {
  421. return false;
  422. } else {
  423. return this.hitChild.some(x => x.children.length > 0);
  424. }
  425. },
  426. /**
  427. * @param {Object} x
  428. * @param {Object} y
  429. * 要遍历多次,第一次为外面的大类确定
  430. * 第二次为大类里面的包含的小件
  431. */
  432. //根据坐标判断是否击中集合
  433. ifClickObj(x,y){
  434. let isDelete = 0;
  435. let chlildObj = this.chlildObj; //顶级对象
  436. this.thisWidth = uni.getSystemInfoSync().screenWidth;
  437. chlildObj.children.forEach(item => {
  438. let item_width = item.width,
  439. item_top = item.top,
  440. item_left = item.left,
  441. item_height = item.height,
  442. image_width = chlildObj.width,
  443. image_height = chlildObj.height;
  444. //计算实际坐标
  445. let x1 = 0,//x头
  446. x2 = 0,//x尾
  447. y1 = 0,//y头
  448. y2 = 0;//y尾
  449. x1 = this.pxToX(item_left,image_width);
  450. x2 = x1+this.pxToX(item_width,image_width);
  451. y1 =this.pxToY(item_top,image_width,image_height);
  452. y2 = y1+this.pxToY(item_height,image_width,image_height);
  453. //命中父级组件
  454. if(x>x1 && x<x2 && y>y1 && y<y2){
  455. let childrenParent = this.hitChild.find(x1 => x1.title === item.title);
  456. if(childrenParent==undefined || childrenParent.length == "" ){
  457. childrenParent = {
  458. "id":item.id,
  459. "children":[],
  460. "image":item.image,
  461. "pid":item.pid,
  462. "title":item.title,
  463. "is_visible":item.is_visible,
  464. "height":item.height,
  465. "top":item.top,
  466. "left":item.left,
  467. "width":item.width
  468. };
  469. this.hitChild.push(childrenParent);
  470. }
  471. //判断命中小组建
  472. item.children.forEach(childItem =>{
  473. let child_width = childItem.width,
  474. child_top = childItem.top,
  475. child_left = childItem.left,
  476. child_height = childItem.height,
  477. child_x = 0,
  478. child_x2 = 0,
  479. child_y = 0,
  480. child_y2 = 0;
  481. child_x = this.pxToX(child_width,image_width);
  482. child_x2 = child_x+this.pxToX(child_width,image_width);
  483. child_y = this.pxToY(child_top,image_width,image_height);
  484. child_y2 = child_y+this.pxToY(child_height,image_width,image_height);
  485. //命中小组件
  486. if(x>child_x && x<child_x2 && y>child_y && y<child_y2){
  487. //判断是否存在
  488. let hitchilditem = childrenParent.children.find(mm => mm.title === childItem.title);
  489. if(hitchilditem == undefined || hitchilditem == ""){
  490. childrenParent.children.push(childItem);
  491. }
  492. }
  493. })
  494. }
  495. })
  496. },
  497. /**
  498. * 像素等比例转换X轴
  499. * @param {Object} px 标记像素
  500. * @param {Object} widch 图片宽度
  501. */
  502. pxToX(px,width){
  503. let pxx = Math.floor((px / width) * this.thisWidth);
  504. return pxx;
  505. },
  506. /**
  507. * 像素等比例转换Y轴
  508. * @param {Object} px 标记像素
  509. * @param {Object} height 图片高度
  510. */
  511. pxToY(px,width,height){
  512. //计算等比例高度
  513. const scaledHeight = (this.thisWidth / width) * height;
  514. let pxx = Math.floor(( px/ height) * scaledHeight);
  515. return pxx;
  516. },
  517. // 清除所有取消遮罩的区域
  518. getSmileDate(){
  519. this.chlildObj.children.forEach(item => {
  520. let chlildObj = this.chlildObj;
  521. let item_width = item.width,
  522. item_top = item.top,
  523. item_left = item.left,
  524. item_height = item.height,
  525. image_width = chlildObj.width,
  526. image_height = chlildObj.height;
  527. let x1 = 0,
  528. x2 = 0,
  529. y1 = 0,
  530. y2 = 0;
  531. let oldx = item_left,
  532. oldy = item_top,
  533. oldx2 = item_width*1,
  534. oldy2 = item_height*1;
  535. x1 = oldy*1;
  536. x2 = oldy*1 + oldy2*1;
  537. y1 = image_width*1 - oldx*1;
  538. y2 = image_width*1 - oldx*1 - oldx2*1;
  539. // y1 = item_left;
  540. // x1 = item_top;
  541. // y2 = item_left*1 + item_width*1;
  542. // x2 = item_top*1 + item_height*1;
  543. let zb1 = this.shuToHeng(image_width,image_height,x1,y1);
  544. let zb2 = this.shuToHeng(image_width,image_height,x2,y2);
  545. this.specifiedAreas.push({
  546. "x1":zb1.x,"y1":zb1.y,"x2":zb2.x,"y2":zb2.y,id:'area'+item.title
  547. })
  548. })
  549. },
  550. //push 取消遮罩
  551. specifiedAreasPush(item){
  552. let allAreas = this.specifiedAreas;
  553. //获取tab坐标
  554. let isAreas = allAreas.find(x => x.id == "area"+item.title);
  555. this.unmaskAreas = [];
  556. this.unmaskAreas.push({
  557. id: isAreas.id,
  558. x1: isAreas.x1,
  559. y1: isAreas.y1,
  560. x2: isAreas.x2,
  561. y2: isAreas.y2,
  562. radius: this.unmaskSize/2
  563. });
  564. // 重绘Canvas遮罩
  565. this.updateMaskCanvas();
  566. },
  567. //计算横置小图的坐标,竖坐标转化成横坐标,宽高也改变了
  568. /**
  569. * @param {Object} width 图片宽度
  570. * @param {Object} height 图片高度
  571. * @param {Object} x 坐标
  572. * @param {Object} y 坐标
  573. * @param {Object} thisWidth 当前屏幕宽度
  574. */
  575. shuToHeng(width,height,x,y){
  576. //转换后的宽度
  577. let thisWidth = uni.getSystemInfoSync().screenWidth;
  578. //选装的高度
  579. let thisHeight = (thisWidth / height) * width;
  580. //旋转后的y轴
  581. let pxy = Math.floor(( y/ width) * thisHeight);
  582. //旋转后的X轴
  583. let pxx = Math.floor((x / height) * thisWidth);
  584. return {"x":pxx,"y":pxy};
  585. },
  586. //start
  587. handleImageClick(event) {
  588. const query = uni.createSelectorQuery().in(this);
  589. query.select('.small-image').boundingClientRect(data => {
  590. if (data) {
  591. const clickX = event.touches[0].clientX - data.left;
  592. const clickY = event.touches[0].clientY - data.top;
  593. // 检查是否点击在指定区域内
  594. const hitArea = this.checkClickInSpecifiedArea(clickX, clickY);
  595. if (hitArea) {
  596. // 如果点击在指定区域内,取消该区域遮罩
  597. this.addUnmaskArea(hitArea, clickX, clickY);
  598. }
  599. }
  600. }).exec();
  601. },
  602. // 检查点击是否在指定区域内
  603. checkClickInSpecifiedArea(clickX, clickY) {
  604. for (let area of this.specifiedAreas) {
  605. // 检查点击坐标是否在区域内
  606. if (clickX >= area.x1 &&
  607. clickX <= area.x2 &&
  608. clickY >= area.y2 &&
  609. clickY <= area.y1) {
  610. return area;
  611. }
  612. }
  613. return null;
  614. },
  615. // 添加取消遮罩区域
  616. addUnmaskArea(area, x, y) {
  617. const exists = this.unmaskAreas.some(item => item.id === area.id);
  618. if (!exists) {
  619. this.unmaskAreas = [];
  620. this.unmaskAreas.push({
  621. id: area.id,
  622. x1: area.x1,
  623. y1: area.y1,
  624. x2: area.x2,
  625. y2: area.y2,
  626. radius: this.unmaskSize/2
  627. });
  628. //lab标题获取焦点
  629. let childrenFor = this.chlildObj.children;
  630. let index = 0;
  631. for (var i = 0; i < childrenFor.length; i++) {
  632. let item = childrenFor[i];
  633. if(item.title == area.id.split("area")[1]){
  634. index = i;
  635. this.labTitile(item,i);
  636. }
  637. }
  638. }
  639. },
  640. // 更新Canvas遮罩
  641. updateMaskCanvas() {
  642. const ctx = this.ctxMask;
  643. // 1. 清除画布(动态获取尺寸)
  644. const { windowWidth, windowHeight } = uni.getSystemInfoSync();
  645. ctx.clearRect(0, 0, windowWidth, windowHeight);
  646. // 2. 绘制全屏半透明遮罩
  647. ctx.setFillStyle('rgba(255, 255, 255, 0.5)');
  648. ctx.fillRect(0, 0, windowWidth, windowHeight);
  649. // 3. 关键!设置合成模式为“擦除”
  650. ctx.globalCompositeOperation = 'destination-out';
  651. ctx.setFillStyle('rgba(0, 0, 0, 1)'); // 颜色不重要,alpha通道生效
  652. // 4. 绘制取消遮罩的区域(矩形示例)
  653. this.unmaskAreas.forEach(area => {
  654. ctx.beginPath();
  655. ctx.moveTo(area.x1, area.y1);
  656. ctx.lineTo(area.x2, area.y1);
  657. ctx.lineTo(area.x2, area.y2);
  658. ctx.lineTo(area.x1, area.y2);
  659. ctx.closePath();
  660. ctx.fill();
  661. });
  662. // 5. 恢复默认合成模式
  663. ctx.globalCompositeOperation = 'source-over';
  664. // 6. 提交绘制(微信小程序需调用 draw)
  665. ctx.draw(true); // 注意:微信小程序中需传 true 表示异步绘制
  666. },
  667. // 清除所有取消遮罩的区域
  668. clearUnmaskAreas() {
  669. this.unmaskAreas = [];
  670. const ctx = this.ctxMask;
  671. const { windowWidth, windowHeight } = uni.getSystemInfoSync();
  672. // 1. 清除画布(使用实际尺寸)
  673. ctx.clearRect(0, 0, windowWidth * 2, windowHeight * 2); // 考虑retina屏幕
  674. ctx.draw(false, () => {
  675. uni.hideLoading(); // 如果之前有loading可以隐藏
  676. });
  677. },
  678. // 绘制全屏遮罩
  679. drawFullMask() {
  680. const ctx = this.ctxMask;
  681. // 获取实际屏幕尺寸(更精确的方式)
  682. const { windowWidth, windowHeight } = uni.getSystemInfoSync();
  683. // 1. 清除画布(使用实际尺寸)
  684. ctx.clearRect(0, 0, windowWidth * 2, windowHeight * 2); // 考虑retina屏幕
  685. // 2. 绘制全屏遮罩
  686. ctx.setFillStyle('rgba(255, 255, 255, 0.5)');
  687. ctx.fillRect(0, 0, windowWidth * 2, windowHeight * 2);
  688. ctx.draw(false, () => {
  689. uni.hideLoading(); // 如果之前有loading可以隐藏
  690. });
  691. },
  692. //测试接口
  693. async textJieKou(url,data){
  694. let returnRes ;
  695. await this.$http(url,data, 'GET').then(res => {
  696. returnRes = res;
  697. console.log("方法内部返回:",res);
  698. });
  699. return returnRes;
  700. },
  701. }
  702. }
  703. </script>
  704. <style scoped>
  705. .zdyNavBox {
  706. width: 100vw;
  707. background: #FFFFFF;
  708. position: fixed;
  709. top: 0;
  710. left: 0;
  711. z-index: 9999999;
  712. }
  713. .zdyNav {
  714. display: flex;
  715. justify-content: space-between;
  716. align-items: center;
  717. padding: 14rpx 6rpx;
  718. }
  719. .zdyNavLeft {
  720. width: 120rpx;
  721. }
  722. .tab-box{
  723. width: 360rpx;
  724. display: flex;
  725. height: 65rpx;
  726. align-items: center;
  727. justify-content: center;
  728. }
  729. .zdyNavRight {
  730. background: #FFFFFF;
  731. text-align: center;
  732. font-size: 28rpx;
  733. color: #3F90F7;
  734. width: 120rpx;
  735. }
  736. .box{
  737. }
  738. .title{
  739. background: #FFFFFF;
  740. text-align: center;
  741. font-size: 32rpx;
  742. color: #1777FF;
  743. }
  744. .sanjiao{
  745. font-size: 14rpx;
  746. color: #1777FF;
  747. padding: 10rpx 0 0 8rpx;
  748. /* right: 42%;
  749. top: 40%; */
  750. }
  751. .shangjian{
  752. padding: 0rpx 0 0 8rpx;
  753. }
  754. .title-box{
  755. display: grid;
  756. grid-template-columns: repeat(3,1fr);
  757. gap: 5rpx;
  758. padding-bottom: 20rpx;
  759. .item{
  760. margin: 30rpx 10rpx;
  761. height: 220rpx;
  762. .item-image{
  763. border: solid 1rpx #999999;
  764. border-radius: 10rpx;
  765. padding-bottom: 10rpx;
  766. }
  767. .item-title{
  768. text-align: center;
  769. }
  770. }
  771. }
  772. .box-image-big{
  773. background: #ffffff;
  774. height: 80vh;
  775. .image-big{
  776. position: relative;
  777. width: 100%;
  778. height: 80vh;
  779. }
  780. .drawing-canvas {
  781. position: absolute;
  782. top: 0;
  783. left: 0;
  784. width: 100%;
  785. height: 100%;
  786. z-index: 10;
  787. }
  788. .box-btn{
  789. display: flex;
  790. justify-content: space-between;
  791. position: relative;
  792. top: -50%;
  793. z-index: 99999;
  794. .leftBtn{
  795. width: 40rpx;
  796. height: 100rpx;
  797. background: #999999;
  798. color: #ffffff;
  799. font-size: 35rpx;
  800. line-height: 100rpx;
  801. text-align: center;
  802. border-radius: 0 15rpx 15rpx 0;
  803. }
  804. .rightBtn{
  805. width: 40rpx;
  806. height: 100rpx;
  807. background: #999999;
  808. color: #ffffff;
  809. font-size: 35rpx;
  810. line-height: 100rpx;
  811. text-align: center;
  812. border-radius: 15rpx 0 0 15rpx;
  813. }
  814. }
  815. }
  816. .box-image-small{
  817. z-index: 999;
  818. .small-image{
  819. width: 100%;
  820. height: 420rpx;
  821. background: #ffffff;
  822. position: relative; /* 关键:为遮罩层提供定位基准 */
  823. .small-this-image{
  824. transform: rotate(-90deg);
  825. height: 750rpx;
  826. width: 420rpx;
  827. margin: -150rpx 165rpx;
  828. }
  829. }
  830. }
  831. .image-mask {
  832. position: absolute;
  833. top: 0;
  834. left: 0;
  835. width: 100%;
  836. height: 100%;
  837. background: rgba(255, 255, 255, 0.5);
  838. pointer-events: none;
  839. z-index: 1; /* 确保在取消区域之下 */
  840. }
  841. /* 双箭头核心实现 */
  842. .box-jiantou {
  843. background: #ffffff;
  844. text-align: center;
  845. margin-top: -5rpx;
  846. .jian-span{
  847. width: 20rpx;
  848. height: 40rpx;
  849. line-height: 40rpx;
  850. transform: rotate(-90deg);
  851. transform-origin: center;
  852. margin: 0 auto;
  853. color: #999;
  854. }
  855. .jian-down{
  856. transform: rotate(90deg);
  857. transform-origin: center;
  858. }
  859. }
  860. .box-lab{
  861. background: #ffffff;
  862. z-index: 999;
  863. }
  864. .box-lab-scr{
  865. .uni-scroll-view-content{
  866. display: flex;
  867. flex-direction: row;
  868. flex-wrap: nowrap;
  869. white-space: nowrap;
  870. }
  871. }
  872. .lab-titles{
  873. /* width: 130rpx !important; */
  874. /* min-width: 130rpx !important; */
  875. height: 60rpx;
  876. font-size: 35rpx;
  877. color: #999999;
  878. padding-left: 10rpx;
  879. margin: 10rpx;
  880. padding-right: 8rpx;
  881. text-align: center;
  882. }
  883. .clickLab{
  884. color: #1777FF;
  885. border-bottom: 4rpx solid #1777FF;
  886. }
  887. .lab-grid{
  888. display: grid;
  889. grid-template-columns: repeat(4,1fr);
  890. background: #ffffff;
  891. gap: 1rpx;
  892. .lab-child{
  893. margin: 10rpx;
  894. .lab-child-image{
  895. border: 1rpx solid #E2E2E2;
  896. border-radius: 14rpx;
  897. }
  898. .lab-child-title{
  899. text-align: center;
  900. font-size: 24rpx;
  901. }
  902. }
  903. }
  904. </style>