import type { StaticImg } from '@/utils/static' import { onLoad } from '@dcloudio/uni-app' import { reactive, toRefs, nextTick} from 'vue' import { inject, onUnmounted, onMounted, type ComponentInternalInstance, ref} from 'vue' import { encode, decode} from 'js-base64' import hand from '@/assets/3-4.png' /** * 它从 `inject` 函数返回 `StaticImg` 服务 * 图片的key就是图片名称 * @returns { StaticImg } assets下全部的文件 * @example * const staticImg = useStaticImg() * const logo = staticImg.logo */ export const useStaticImg = (): StaticImg => inject("staticImg")! /** * * @description 重复执行的定时器, 会在组件的unMounted阶段销毁 * */ export const useScheduler = (cb: () => void, delay: number) => { const stx: API.UseSchedulerFastCall = { timeId: null, start: () => {}, pause: () => {}, stop: () => {}, onPlaying: () => {} } let timeId = ref() const playing = () => {} const clearScheduler = () => clearInterval(timeId.value as number) onUnmounted(clearScheduler) stx.start = () => { timeId.value = setInterval(() => { cb() playing() }, delay) } stx.pause = () => clearScheduler() stx.onPlaying = playing stx.stop = () => clearScheduler() return stx } /** * @description 执行一定的定时器, 会在执行完后销毁 * @param cb 回调函数 * @param delay 延迟 大部分浏览器setTimeout延迟默认在四毫秒左右 */ export const useSchedulerOnce = ( cb: () => void, delay: number = 4 ): void => { const timeId = setTimeout(() => { cb() clearTimeout(timeId) }, delay) onUnmounted(() => clearTimeout(timeId)) } /** * 它返回一个对象,其中包含导航栏的高度、导航栏内内容的高度以及导航栏的顶部位置。 * @returns 具有 height、contentHeight 和 top 属性的对象。 */ export const useNavbarInfo = () => { const { uniPlatform, safeArea , ...systemInfo} = uni.getSystemInfoSync() console.log(uniPlatform, window['navigatorBarHeight'], window['statusBarHeight']); if (uniPlatform == "web") { const statusBarHeight = window['statusBarHeight'] ? window['statusBarHeight'] * 2 : 33 return { height: 140 + statusBarHeight + "rpx", contentHeight: '64rpx', top: 38 + statusBarHeight + 'rpx', left: '40rpx', safeAreaTop: statusBarHeight + 'rpx', statusBarHeight: statusBarHeight } } else { const bounding = uni.getMenuButtonBoundingClientRect() console.log("(safeArea!.top + bounding.height):", (safeArea!.top + bounding.height)); return { height: 104 + 'px', contentHeight: bounding.height + (bounding.top - systemInfo.statusBarHeight!) * 2 + "px", top: systemInfo.statusBarHeight! + "px", left: '20px', safeAreaTop: 0 + 'rpx', statusBarHeight: 0 } } } /** * 它需要一个 id、一个回调函数和一个 vm,然后它使用具有给定 id 的元素的 boundingClientRect 调用回调函数 * @param {string} id - 您要从中获取信息的元素的 ID * @param cb - 回调函数 * @param {ComponentInternalInstance} vm - 组件实例 */ export const useQueryElInfo = ( id: string, cb: (params: UniApp.NodeInfo | UniApp.NodeInfo[] ) => void, vm?: ComponentInternalInstance, immediate: boolean = true ) => { const queryNode = () => { const query = vm ? uni.createSelectorQuery().in(vm) : uni.createSelectorQuery(); query.select(id).boundingClientRect(data => cb(data)).exec(); } // onMounted(() => queryNode()) immediate ? onMounted(() => queryNode()) : queryNode() } /** * 它创建一个音频实例,并返回一个具有 destroy、play 和 onPlayend 方法的对象。 * @param {string} src - 音频文件的来源 * @returns 具有三个属性的对象:destroy、play 和 onPlayend。 */ export const useAudioMange = (src?: string) => { const audioInstance = ref< UniApp.InnerAudioContext>() audioInstance.value = uni.createInnerAudioContext() if (src) { audioInstance.value.src = src } const destroy = () => audioInstance.value?.stop() const play = (src: string) => { audioInstance.value!.src = src console.log(src, audioInstance.value); useSchedulerOnce(() => { console.log('play'); audioInstance.value?.play() }, 500) } audioInstance.value.autoplay = true audioInstance.value!.onCanplay((e) => { console.log('onCanplay:111', e); }) audioInstance.value?.onError((e) => { console.log(e); }) const onplayend = (cb: Function) => audioInstance.value?.onEnded(() => cb && cb()) onUnmounted(() => { destroy() }) const atx = { destroy, play, onplayend, } return atx } /** * 它以字符串形式返回设备平台。 * @returns uniPlatform 作为 DEVICE.Platform 返回。 */ export const usePlatform = (): DEVICE.Platform => { const { uniPlatform } = uni.getSystemInfoSync() return uniPlatform as DEVICE.Platform } /** * 获取多设备动态适配的比率 * @example * const rate = useAdaptationIpadAndPhone() * node.width = node.width * rate * 2 + 'rpx */ export const useAdaptationIpadAndPhone = () => { let a = window.innerWidth / window.innerHeight > 0.5 ? 667 / ( window.innerHeight ) : 1 const resizeHanlde = () => { // 获取当前窗口的宽度 var screenWidth = window.innerWidth; var screenHeight = window.innerHeight; const rate = screenWidth / screenHeight if ( rate > 0.5 ) { // 大于0.6 说明是平板 a = 667 / ( screenHeight ) } else { a = 1 } } // 监听窗口尺寸变化事件 window.addEventListener('resize', resizeHanlde); return a } export const useQueryParmas = async () => { let state = reactive({}) state = await new Promise( (resolve) => { onLoad(query => { console.log(3); resolve(query!) }) }) return {...toRefs(state)} } /** * 这是一个导出对象的TypeScript函数,有两个方法decode和encode,分别将Base64编码的字符串解码为JSON对象,将JSON对象编码为Base64编码的字符串。 * @returns 正在返回名为“useBase64”的自定义挂钩。这个钩子有两个函数:`decode` 和 `encode`。 `decode` 函数将字符串作为输入并返回解析后的 JSON 对象。 * `encode` 函数将 `API.P` 类型的对象作为输入并返回 base64 编码的字符串。 */ export const useBase64 = () => { return { decode: (URI: string): T => JSON.parse(decode(URI)), 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, rate } : { ansUrl: string, quesUrl: string, instance: ComponentInternalInstance, rate: number} ) => { function isIOS() { const userAgent = navigator.userAgent; return /iPhone|iPad|iPod/i.test(userAgent); } const adaptatio = useAdaptationIpadAndPhone() let ctx: UniApp.CanvasContext = uni.createCanvasContext(canvasId) const deviceWidthRate = uni.getSystemInfoSync().windowWidth / 375 // 初始化放大镜的参数 const magnifierSize = 150 * deviceWidthRate ; // 放大镜尺寸 const magnification = 1.5 ; // 放大倍数 const lineWidth = 6 * deviceWidthRate// 放大镜边框宽度 let _moveMagnifier = (event: TouchEvent) => {} const createImgStr = async () => { ctx.drawImage(quesUrl, 0, 0, 224 * deviceWidthRate, 386 * deviceWidthRate) ctx.drawImage(ansUrl, 225 * deviceWidthRate , 0, 75 * deviceWidthRate, 386 * deviceWidthRate) ctx.save() ctx.draw() // ctx.restore(); // 创建 Image 对象并加载图片 await new Promise( resolve => { useSchedulerOnce(() => { resolve(true) }, 100) }) return await new Promise((resolve) => { uni.canvasToTempFilePath({ canvasId: canvasId, fileType: 'png', quality: 1, destWidth: 300 * deviceWidthRate, destHeight: 386 * deviceWidthRate, success: (res) => resolve( res.tempFilePath), fail: (error) => { console.error('canvasToTempFilePath failed: ', error); } }, instance); }) as string } const draw = async () => { const imgStr = await createImgStr() function initMagnifier () { // 计算放大镜区域的位置和尺寸 const startX = 200; const startY = 0; const width = magnifierSize; const height = magnifierSize; // 绘制阴影 ctx.shadowColor = 'black'; ctx.shadowBlur = 20; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // 绘制放大镜效果 ctx.save(); ctx.beginPath(); ctx.arc( startX, startY, magnifierSize / 2, 0, 2 * Math.PI ); ctx.strokeStyle = 'yellow'; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.closePath(); ctx.clip(); ctx.drawImage( imgStr, startX, startY, width, height, startX - magnifierSize / 2, startY - magnifierSize / 2, magnifierSize * magnification, magnifierSize * magnification ); ctx.draw() ctx.restore(); } function moveMagnifier (event: TouchEvent) { event.preventDefault(); event.stopPropagation(); let mouseX = Math.floor((event.touches[0].pageX - canvas.getBoundingClientRect().left) / adaptatio) let mouseY = Math.floor((event.touches[0].pageY - canvas.getBoundingClientRect().top) / adaptatio) // 拖拽边界处理 if (mouseX <= 0) mouseX = 0 if (mouseX >= canvas.getBoundingClientRect().right - 26 ) mouseX = canvas.getBoundingClientRect().right - 26 if (mouseY <= 0) mouseY = 0 if (mouseY >= (canvas.getBoundingClientRect().bottom - 214 )) mouseY = canvas.getBoundingClientRect().bottom - 214 // 拖拽边界处理 // 清空 Canvas ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); // 计算放大镜区域的位置和尺寸 const startX = Math.floor((mouseX - magnifierSize / 2 )) const startY = Math.floor((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(); const sxCount = mouseX - (magnifierSize * magnification) / 2 const syCount = mouseY - (magnifierSize * magnification) / 2 const sx = sxCount <= 0 ? 0 : mouseX - (magnifierSize * magnification) / 2 const sy = syCount <= 0 ? 0 : (mouseY - (magnifierSize * magnification) / 2) ctx.drawImage( imgStr, startX, startY, width, height, isIOS() ? sx: sxCount , isIOS() ? sy : syCount, magnifierSize * magnification , magnifierSize * magnification ); // ctx.drawImage( // imgStr, // startX, // startY, // width, // height, // mouseX - (magnifierSize * magnification) / 2 , // mouseY - (magnifierSize * magnification) / 2 , // magnifierSize * magnification , // magnifierSize * magnification // ); ctx.draw() ctx.restore(); } _moveMagnifier = moveMagnifier initMagnifier(); // 监听鼠标移动事件,实现放大镜效果 canvas.addEventListener("touchmove",moveMagnifier); } const clear = () => { ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); ctx.draw() canvas.removeEventListener('touchmove', _moveMagnifier) } onUnmounted(() => { canvas.removeEventListener('touchmove', _moveMagnifier) }) return { draw, clear } }; /** * @description 陀螺仪 根据陀螺仪判断是否是横屏状态 */ export const useGyroH = async (cb: (dire: string) => void) => { const handleOrientation = (event: DeviceOrientationEvent) => { var alpha = event.alpha!; // 设备绕 Z 轴的旋转角度(0 到 360 度) var beta = event.beta!; // 设备绕 X 轴的旋转角度(-180 到 180 度) var gamma = event.gamma!; // 设备绕 Y 轴的旋转角度(-90 到 90 度) console.log(`x轴:${Math.floor(beta)} y轴:${ Math.floor(gamma)} z轴:${ Math.floor(alpha) }`); let dire = '' if (alpha < 10 ) { dire = 'H' } else { dire = 'V' } cb && cb(dire) } if (window.DeviceOrientationEvent) { window.addEventListener('deviceorientation', handleOrientation); // if ( uni.getSystemInfoSync().system.includes('iOS')) { } } else { console.log('设备不支持陀螺仪功能'); } onUnmounted(() => { window.removeEventListener('deviceorientation', handleOrientation) }) }