lvkun996 пре 1 година
родитељ
комит
2b6db80d73

BIN
src/assets/dire.gif


BIN
src/assets/magnifier-close.png


BIN
src/assets/magnifier-open.png


+ 110 - 19
src/components/luojigou-board/luojigou-board.vue

@@ -56,10 +56,17 @@
         </view> -->
       </view>
      
-      <view class="ans"  id="ansRef">
+      <view class="ans"  id="ansRef" :style="{zIndex: viewZindex.quesView}">
         <image :src="props.board.ansUrl" alt="" />
       </view>
 
+      <canvas
+        type="2d" 
+        canvas-id="magnifier-canvas" 
+        id="magnifier-canvas" 
+        :style="{width: '300px', height: '386px', zIndex: viewZindex.magnifierView}"
+      ></canvas>
+
       <movable-area 
       v-if="props.cardType !== undefined" 
       class="movable-area"
@@ -105,26 +112,35 @@
         />
       </movable-view>
       </movable-area>
-
     </view>
-
   </view>
 
-
-  <!-- <audio :src="audioSrc" autoplay ></audio> -->
+  <!-- 实现放大镜功能 -->
+  <!-- <view class="magnifier" >
+    <view class="magnifier-navbar" :style="{width: '100vw', height: navbarInfo.height}" ></view>
+    <view class="magnifier-card" >
+      <canvas
+        type="2d"
+        canvas-id="magnifier-canvas" 
+        id="magnifier-canvas" 
+        style="width: 300px;height:386px"
+      ></canvas>
+    </view>
+  </view> -->
 
 </template>
 
 
 <script setup lang="ts" name="luojigou-board" >
 import { defineProps, reactive, ref, computed, getCurrentInstance, defineEmits, watch } from 'vue'
-import { useStaticImg, useSchedulerOnce, useQueryElInfo, useAdaptationIpadAndPhone } from '@/hooks/index'
+import { useStaticImg, useNavbarInfo, useSchedulerOnce, useQueryElInfo, useAdaptationIpadAndPhone, useMagnifier } from '@/hooks/index'
 import type { CardModeEnum } from '@/enum/constant';
 import { useCalcQuantityStore } from '@/store/index';
 import { AudioController } from '@/controller/AudioController';
 import Hammer from 'hammerjs'
 import { onMounted } from 'vue';
 
+
 interface Buttons {
   id: number,
   x: number,
@@ -147,7 +163,8 @@ interface IProps {
   playLoading: boolean,
   getExpose: (records: any) => void
   tipsButton: number
-  audioSrc: string
+  audioSrc: string,
+  magnifierState: boolean
 }
 
 const staticImg = useStaticImg()
@@ -160,6 +177,8 @@ const props = defineProps<IProps>()
 
 const emits = defineEmits(['playAudio', 'submit'])
 
+const navbarInfo = useNavbarInfo()
+
 const state = reactive({
   zIndex: 0, // 暂存zIndex
   scale: 1
@@ -167,7 +186,8 @@ const state = reactive({
 
 const viewZindex = ref({
   moveView: 30,
-  quesView: 31
+  magnifierView: 31,
+  quesView: 29
 })
 
 
@@ -218,14 +238,9 @@ const TPos = (x: number, y: number) => {
   
 }
 
-const getButtonIndex = (_y: number) =>  {
-  console.log('ansItemHeight.value', ansItemHeight.value);
-  
- return Math.floor(_y / ansItemHeight.value)
-}
+const getButtonIndex = (_y: number) => Math.floor(_y / ansItemHeight.value)
 
 const getButtonPosByIndex = (index: number) => {
-  console.log('getButtonPosByIndex:',  ansRef.value?.width!,  quesRef.value?.width! );
   
   const x = ansRef.value?.width! + quesRef.value?.width! 
  
@@ -256,7 +271,6 @@ buttons.splice(props.cardType == 1 ?  buttons.length : buttons.length - 2, butto
 
 // 让按钮回到原位
 const disPatchButtonGoInitPos = (index: number) => {
-    
   useSchedulerOnce(() => {
     buttons[index].x = buttons[index].initX + Math.random()
     buttons[index].y = buttons[index].initY + Math.random()
@@ -266,7 +280,8 @@ const disPatchButtonGoInitPos = (index: number) => {
 }
 
 const touchStart = (item: Buttons) => {
-  viewZindex.value.moveView = viewZindex.value.quesView + 1
+  // viewZindex.value.moveView = viewZindex.value.quesView + 1
+  viewZindex.value.moveView = viewZindex.value.magnifierView + 1
   useSchedulerOnce(() => {
     state.zIndex ++
     item.zIndex = state.zIndex
@@ -332,7 +347,10 @@ const touchend = (ev: TouchEvent, item: Buttons) => {
 
 
   // 每次松手后把题卡的zIndex提高1
-  viewZindex.value.quesView = viewZindex.value.moveView + 1
+  // viewZindex.value.quesView = viewZindex.value.moveView + 1
+
+  // 每次松手后把放大镜的zIndex提高1
+  viewZindex.value.magnifierView = viewZindex.value.moveView + 1
 
 }
 
@@ -384,7 +402,11 @@ const checkAns = (index: number, itemData: Buttons) => {
 
 
 
-// 让题卡图片支持双指捏合
+/**
+ * @description 放在onMounted生命周期里就可以生效,功能:把题卡放大缩小
+ * @description 需要在touchStart时 设置 viewZindex.value.moveView = viewZindex.value.quesView + 1 
+ * @description 需要在touchend时 设置  viewZindex.value.quesView = viewZindex.value.moveView + 1
+ */
 const createHammer = () => {
   var square = document.querySelector('#ques-url');
   var hammer = new Hammer(square);
@@ -460,8 +482,43 @@ const createHammer = () => {
 
 }
 
+let a = () => {}
+let b = () => {}
+
+/**
+ * @description 放大镜功能
+ */
+
+
+// 动态设置canvas的宽高
+
+watch(
+  () => props.magnifierState,
+  () => {
+    if (props.magnifierState) {
+      a()
+    } else {
+      b()
+    }
+    console.log(props.magnifierState);
+    
+  }
+)
+
 onMounted(() => {
-  createHammer()
+  useMagnifier(
+    document.getElementById('magnifier-canvas') as HTMLCanvasElement, 
+    'magnifier-canvas',
+    {
+      ansUrl:  props.board.ansUrl,
+      quesUrl:  props.board.quesUrl,
+      instance: getCurrentInstance()!
+    }
+).then(r => {
+  console.log(r.draw);
+  a = r.draw
+  b = r.clear
+})
 })
 
 </script>
@@ -580,6 +637,16 @@ onMounted(() => {
           }
       }
 
+      #magnifier-canvas {
+        position: absolute;
+        top: 206rpx;
+        left: 26rpx;
+        z-index: 30;
+        /deep/ .uni-canvas-canvas {
+          border-radius: 40rpx;
+        }
+      }
+
       .ans .ans-item:last-child {
         border-bottom: none
       }
@@ -695,4 +762,28 @@ onMounted(() => {
   align-items: center;
 }
 
+// .magnifier {
+//   width: 100vw;
+//   height: 100vh;
+//   background-color: rgba(0, 0, 0, 0.3);
+//   position: fixed;
+//   top: 0;
+//   left: 0;
+//   z-index: 1000;
+//   display: flex;
+//   flex-direction: column;
+//   align-items: center;
+//   .magnifier-navbar {
+//     width: 100%;
+//     position: relative;
+//   }
+//   .magnifier-card {
+//     width: 714rpx;
+
+//   }
+
+// }
+
+
+
 </style>

+ 45 - 18
src/components/navbar/navbar.vue

@@ -2,20 +2,25 @@
   <view class="navbar" :style="{height: navbarInfo.height}"  >
     <view class="deco" >
       <view class="content" :style="contentStyles" >
-        <view class="back-icon" @click="goback">
-          <img :src="staticImg.arowLeft" alt="">
+        <view class="left" >
+          <view class="back-icon" @click="goback">
+            <img :src="staticImg.arowLeft" alt="">
+          </view>
+          <view class="level-count">
+            <view
+              class="level-dot"
+              v-for="item in props.levelCount"
+              :key="item"
+              :style="{backgroundColor: item <= props.progress ? '#1191E7' : '#FFCC6A' }"
+            />
+          </view>
+          <view class="countdown" >
+            <image :src="staticImg.clock" />
+            {{props.countdownTotal}}
+          </view>
         </view>
-        <view class="level-count">
-          <view
-            class="level-dot"
-            v-for="item in props.levelCount"
-            :key="item"
-            :style="{backgroundColor: item <= props.progress ? '#1191E7' : '#FFCC6A' }"
-          />
-        </view>
-        <view class="countdown" >
-          <image :src="staticImg.clock" />
-          {{props.countdownTotal}}
+        <view class="right" >
+          <img class="magnifier" @click="opraMagnifier" :src="state.magnifierState ?staticImg.magnifierClose : staticImg.magnifierOpen" alt="">
         </view>
       </view>
     </view>
@@ -23,7 +28,7 @@
 </template>
 
 <script lang="ts" setup name="navbar" >
-import { defineProps } from "vue"
+import { defineProps, reactive, defineEmits } from "vue"
 import { useNavbarInfo, useStaticImg } from '@/hooks/index';
 import type { CardModeEnum } from "@/enum/constant";
 
@@ -40,6 +45,8 @@ interface IPorps {
 
 const props = defineProps<IPorps>()
 
+const emits = defineEmits(['goback', 'changeMagnifier'])
+
 console.log(navbarInfo.height);
 
 const contentStyles = {
@@ -48,10 +55,16 @@ const contentStyles = {
   left: navbarInfo.left
 }
 
-const emit = defineEmits(['goback'])
+const state = reactive({
+  magnifierState: false
+})
 
-const goback = () => emit('goback')
+const goback = () => emits('goback')
 
+const opraMagnifier = () => {
+  state.magnifierState = !state.magnifierState
+  emits('changeMagnifier', state.magnifierState)
+}
 
 </script>
 
@@ -66,14 +79,21 @@ const goback = () => emit('goback')
     height: 100%;
     background: #F9BF4F;
     border-radius: 0px 0px 40rpx 40rpx;
+    padding: 0 26rpx;
+    box-sizing: border-box;
     .content {
+      width: calc(100vw - 104rpx);
       display: flex;
       align-items: center;
+      justify-content: space-between;
       position: absolute;
-      .back-icon {
+      .left {
+        display: flex;
+        align-items: center;
+        .back-icon {
         width: 24rpx;
         height: 40rpx;
-        margin-right: 13px;
+        margin-right: 26rpx;
         img {
           width: 100%;
           height: 100%;
@@ -119,6 +139,13 @@ const goback = () => emit('goback')
           display: block;
         }
       }
+      }
+      .right {
+        .magnifier {
+          width: 96rpx;
+          height: 96rpx;
+        }
+      }
     }
     
   }

+ 168 - 1
src/hooks/index.ts

@@ -250,4 +250,171 @@ export const useBase64 = () => {
     encode: (records: API.P): string => encode(JSON.stringify(records))
   }
 
-}
+}
+
+
+/**
+ * @description 获取设备方向
+ * @param cb
+ */
+export const useDeviceDire = (cb: (dire: 'H' | 'V') => void) => {
+  const getDire = () => {
+    if (window.orientation === 180 || window.orientation === 0) {
+      cb('V')
+    }
+    if (window.orientation === 90 || window.orientation === -90) {
+      cb('H')
+    }
+  }
+
+  window.addEventListener('orientationchange', getDire)
+
+  onMounted(getDire)
+
+  onUnmounted(() => {
+    window.removeEventListener('orientationchange', () => {})
+  })
+}
+
+
+/**
+ * @description 题卡的放大镜功能
+ */
+export const useMagnifier = async (
+  canvas: HTMLCanvasElement, 
+  canvasId: string, 
+  { ansUrl,  quesUrl, instance  }: { ansUrl: string,  quesUrl: string, instance: ComponentInternalInstance}
+) => {
+  
+  let ctx: UniApp.CanvasContext = uni.createCanvasContext(canvasId)
+
+  const draw = async () => {
+    ctx.drawImage(quesUrl, 0, 0, 225, 386)
+    ctx.save()
+    ctx.drawImage(ansUrl, 225, 0, 75, 386)
+    ctx.save()
+    ctx.draw()
+    ctx.restore();
+    // 创建 Image 对象并加载图片
+    
+    // 初始化放大镜的参数
+    const magnifierSize = 150; // 放大镜尺寸
+    const magnification = 1.5; // 放大倍数
+    const lineWidth = 4 // 放大镜边框宽度
+
+    await new Promise( resolve => {
+      useSchedulerOnce(() => {
+        resolve(true)
+      }, 1000)
+    })
+
+    const imageStr = await new Promise((resolve) => {
+      uni.canvasToTempFilePath({
+        canvasId: canvasId,
+        fileType: 'png',
+        quality: 1,
+        destWidth: 300,
+        destHeight: 386,
+        success: (res) => resolve( res.tempFilePath),
+        fail: (error) => {
+          console.error('canvasToTempFilePath failed: ', error);
+        }
+      }, instance);
+    }) as string
+
+      function initMagnifier () {
+        // 计算放大镜区域的位置和尺寸
+        const startX = 0;
+        const startY = 0;
+        const width = magnifierSize;
+        const height = magnifierSize;
+    
+        // 绘制放大镜效果
+        ctx.save();
+        ctx.beginPath();
+        ctx.arc(
+          magnifierSize / 2,
+          magnifierSize / 2,
+          magnifierSize / 2,
+          0,
+          2 * Math.PI
+        );
+        ctx.strokeStyle = 'yellow'; // 设置边框颜色为黄色
+        ctx.lineWidth = lineWidth; // 设置边框宽度
+        ctx.stroke(); // 绘制边框
+        ctx.closePath();
+        ctx.clip();
+        ctx.drawImage(
+          imageStr,
+          startX,
+          startY,
+          width,
+          height,
+          0 - magnifierSize / 2,
+          0 - magnifierSize / 2,
+          magnifierSize * magnification,
+          magnifierSize * magnification
+        );
+        ctx.draw()
+        ctx.restore();
+        console.log('初始化放大镜');
+        
+      }
+    
+      initMagnifier();
+
+      function moveMagnifier (event: TouchEvent) {
+        const mouseX = event.touches[0].pageX - canvas.getBoundingClientRect().left;
+        const mouseY = event.touches[0].pageY - canvas.getBoundingClientRect().top;
+
+        // 清空 Canvas
+        ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
+    
+        // 计算放大镜区域的位置和尺寸
+        const startX = mouseX - magnifierSize / 2;
+        const startY = mouseY - magnifierSize / 2;
+        const width = magnifierSize;
+        const height = magnifierSize;
+    
+        // 绘制放大镜效果
+        ctx.save();
+        ctx.beginPath();
+        ctx.arc(mouseX, mouseY, magnifierSize / 2, 0, 2 * Math.PI);
+        ctx.strokeStyle = 'yellow';
+        ctx.lineWidth = lineWidth; 
+        ctx.stroke()
+        ctx.closePath();
+        ctx.clip();
+        ctx.drawImage(
+          imageStr,
+          startX,
+          startY,
+          width,
+          height,
+          mouseX - (magnifierSize * magnification) / 2,
+          mouseY - (magnifierSize * magnification) / 2,
+          magnifierSize * magnification,
+          magnifierSize * magnification
+        );
+        ctx.draw()
+        ctx.restore();
+      }
+
+      // 监听鼠标移动事件,实现放大镜效果
+      canvas.addEventListener("touchmove", moveMagnifier );
+    }
+
+    const clear = () => {
+      ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
+      ctx.draw()
+      // canvas.removeEventListener('touchmove', moveMagnifier)
+    }
+
+    onUnmounted(() => {
+      canvas.removeEventListener('touchmove', () => {})
+    })
+
+    return {
+      draw, clear
+    }
+  };

+ 1 - 1
src/main.ts

@@ -5,7 +5,7 @@ import staticImg from '@/utils/static'
 import VConsole from "vconsole"
 
 
-new VConsole()
+// new VConsole()
 
 
 export function createApp() {

+ 61 - 4
src/pages/GameView/index.vue

@@ -7,6 +7,7 @@
   <!--  :style="`pointer-events: ${state.mode === CardModeEnum.PREVIEW ? 'none' : 'all'}`" -->
     <navbar
       @goback="goback"
+      @changeMagnifier="changeMagnifier"
       :="{
         ...navbarState,
         levelCount: state.cardIds.length
@@ -24,6 +25,7 @@
         :cardDesc="state.card.cardDesc"
         :playLoading="state.playLoading"
         :audioSrc="state.card.audio"
+        :magnifierState="state.magnifierState"
         @playAudio="playAudio"
         @submit="onSubmitCard"
       />
@@ -33,6 +35,13 @@
     <rive-ani ref="riveAniRef" :key="navbarState.progress" />
 
   </view>
+
+  <view class="device-dire-tip"  v-if="device.dire === 'H'" >
+    <view class="tip" >
+      <img :src="staticImg.dire" alt="">
+      <view class="desc" >为了更好的体验,请使用竖屏浏览</view>
+    </view>
+  </view> 
 </template>
 
 
@@ -42,11 +51,12 @@ import { onLoad } from '@dcloudio/uni-app';
 import { reactive, onMounted } from 'vue'
 import { getCardDetailById, submitlearnPortAns } from '@/api/card'
 import { getCollectionDetailById } from '@/api/collection'
-import { useAudioMange, useScheduler, useBase64, useSchedulerOnce, usePlatform } from '@/hooks/index'
+import { useAudioMange, useScheduler, useBase64, useSchedulerOnce, usePlatform, useDeviceDire } from '@/hooks/index'
 import { usePracticeStore, useOpraRecordStore, useGameCountdownStore, useCalcQuantityStore, useWisdomCoinStore } from '@/store'
 import riveAni from '@/components/rive-ani/rive-ani.vue';
 import { ref } from 'vue';
 import { useAppBridge } from '@/hooks/app';
+import { useStaticImg } from '@/hooks/index';
 
 /**
  *  README.md 里有详细的参数说明
@@ -63,7 +73,8 @@ interface State extends QueryParams {
   cardIds: string[],
   playLoading: boolean,
   userCardAnswerList: any[],
-  queryParams: API.P | null
+  queryParams: API.P | null,
+  magnifierState: boolean
 }
 
 const atx = useAudioMange()
@@ -79,6 +90,8 @@ const calcQuantityStore = useCalcQuantityStore()
 const wisdomCoinStore = useWisdomCoinStore()
 const base64 = useBase64()
 
+const staticImg = useStaticImg()
+
 onLoad(query => {
 
   let options
@@ -112,6 +125,10 @@ const navbarState = reactive({
   mode: CardModeEnum.PREVIEW
 })
 
+const device = reactive({
+  dire: 'H'
+})
+
 const state = reactive<State>({
   opraMode: OpraModeEnum.COMPE,
   mode: CardModeEnum.PREVIEW,
@@ -128,13 +145,21 @@ const state = reactive<State>({
   },
   playLoading: false,
   userCardAnswerList: [],
-  queryParams: null
+  queryParams: null,
+  magnifierState: false
 })
 
 const stx = useScheduler(() => {
   navbarState.countdownTotal++
+
 }, 1000)
 
+
+// 控制是否开启动画模式
+const changeMagnifier = (status: boolean) => {
+  state.magnifierState = status
+}
+
 // 控制游戏开始
 const gameStart = () => {
   navbarState.countdownTotal = 0
@@ -261,6 +286,10 @@ const goback = () => {
 }
 
 
+// 监听设备方向,如果是横屏,立马停止所有的计时
+useDeviceDire((dire: 'H' | 'V') => device.dire = dire)
+
+
 onMounted(async () => {
 
   if (state.mode === 'preview') {
@@ -288,10 +317,38 @@ onMounted(async () => {
     background: url('https://app-resources-luojigou.luojigou.vip/Fqj0sowjX078sk3PgbBlSvT_Ti9R');
     display: flex;
     justify-content: center;
-    padding-top: 23px;
+    padding-top: 46rpx;
     display: flex;
     justify-content: center;
     
   }
 }
+
+.device-dire-tip {
+  width: 100vw;
+  height: 100vh;
+  background-color: rgba(0, 0, 0, 0.3);
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 1000;
+  pointer-events: none;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  .tip {
+    img {
+      width: 180rpx;
+      height: 180rpx;
+    }
+    .desc {
+      font-size: 16rpx;
+      font-family: PingFang SC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #FFFFFF;
+      line-height: 22rpx;
+    }
+  }
+  
+}
 </style>

+ 10 - 23
src/utils/static.ts

@@ -22,28 +22,9 @@ import arowLeft from '@/assets/arowLeft.png'
 import blockRect from '@/assets/block-rect.png'
 import trumptPng from '@/assets/trumptPng.png'
 import luojigouDog from '@/assets/luojigou-dog.png'
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+import dire from '@/assets/dire.gif'
+import magnifierClose from '@/assets/magnifier-close.png'
+import magnifierOpen from '@/assets/magnifier-open.png'
 
 export interface StaticImg {
   logo: string
@@ -73,6 +54,9 @@ export interface StaticImg {
   arowLeft: string
   blockRect: string
   luojigouDog: string
+  dire: string
+  magnifierClose: string
+  magnifierOpen: string
 }
 
 let staticImg: Partial<StaticImg> = {
@@ -103,7 +87,10 @@ let staticImg: Partial<StaticImg> = {
   arowRight,
   arowLeft,
   blockRect,
-  luojigouDog
+  luojigouDog,
+  dire,
+  magnifierClose,
+  magnifierOpen
 }
 
 

+ 1 - 0
src/utils/staticV2.ts

@@ -10,6 +10,7 @@ export interface StaticImg {
   purple: string
   red: string
   yellow: string
+  dire: string
 }
 
 const modules = import.meta.glob('/src/assets/*', {eager: true, })