epcSimpleDetailOne.vue 27 KB

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