index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. import type { StaticImg } from '@/utils/static'
  2. import { onLoad } from '@dcloudio/uni-app'
  3. import { reactive, toRefs, nextTick} from 'vue'
  4. import { inject, onUnmounted, onMounted, type ComponentInternalInstance, ref} from 'vue'
  5. import { encode, decode} from 'js-base64'
  6. import hand from '@/assets/3-4.png'
  7. /**
  8. * 它从 `inject` 函数返回 `StaticImg` 服务
  9. * 图片的key就是图片名称
  10. * @returns { StaticImg } assets下全部的文件
  11. * @example
  12. * const staticImg = useStaticImg()
  13. * const logo = staticImg.logo
  14. */
  15. export const useStaticImg = (): StaticImg => inject<StaticImg>("staticImg")!
  16. /**
  17. *
  18. * @description 重复执行的定时器, 会在组件的unMounted阶段销毁
  19. *
  20. */
  21. export const useScheduler = (cb: () => void, delay: number) => {
  22. const stx: API.UseSchedulerFastCall = {
  23. timeId: null,
  24. start: () => {},
  25. pause: () => {},
  26. stop: () => {},
  27. onPlaying: () => {}
  28. }
  29. let timeId = ref<number>()
  30. const playing = () => {}
  31. const clearScheduler = () => clearInterval(timeId.value as number)
  32. onUnmounted(clearScheduler)
  33. stx.start = () => {
  34. timeId.value = setInterval(() => {
  35. cb()
  36. playing()
  37. }, delay)
  38. }
  39. stx.pause = () => clearScheduler()
  40. stx.onPlaying = playing
  41. stx.stop = () => clearScheduler()
  42. return stx
  43. }
  44. /**
  45. * @description 执行一定的定时器, 会在执行完后销毁
  46. * @param cb 回调函数
  47. * @param delay 延迟 大部分浏览器setTimeout延迟默认在四毫秒左右
  48. */
  49. export const useSchedulerOnce = (
  50. cb: () => void,
  51. delay: number = 4
  52. ): void => {
  53. const timeId = setTimeout(() => {
  54. cb()
  55. clearTimeout(timeId)
  56. }, delay)
  57. onUnmounted(() => clearTimeout(timeId))
  58. }
  59. /**
  60. * 它返回一个对象,其中包含导航栏的高度、导航栏内内容的高度以及导航栏的顶部位置。
  61. * @returns 具有 height、contentHeight 和 top 属性的对象。
  62. */
  63. export const useNavbarInfo = () => {
  64. const { uniPlatform, safeArea , ...systemInfo} = uni.getSystemInfoSync()
  65. console.log(uniPlatform, window['navigatorBarHeight'], window['statusBarHeight']);
  66. if (uniPlatform == "web") {
  67. const statusBarHeight = window['statusBarHeight'] ? window['statusBarHeight'] * 2 : 33
  68. return {
  69. height: 140 + statusBarHeight + "rpx",
  70. contentHeight: '64rpx',
  71. top: 38 + statusBarHeight + 'rpx',
  72. left: '40rpx',
  73. safeAreaTop: statusBarHeight + 'rpx',
  74. statusBarHeight: statusBarHeight
  75. }
  76. } else {
  77. const bounding = uni.getMenuButtonBoundingClientRect()
  78. console.log("(safeArea!.top + bounding.height):", (safeArea!.top + bounding.height));
  79. return {
  80. height: 104 + 'px',
  81. contentHeight: bounding.height + (bounding.top - systemInfo.statusBarHeight!) * 2 + "px",
  82. top: systemInfo.statusBarHeight! + "px",
  83. left: '20px',
  84. safeAreaTop: 0 + 'rpx',
  85. statusBarHeight: 0
  86. }
  87. }
  88. }
  89. /**
  90. * 它需要一个 id、一个回调函数和一个 vm,然后它使用具有给定 id 的元素的 boundingClientRect 调用回调函数
  91. * @param {string} id - 您要从中获取信息的元素的 ID
  92. * @param cb - 回调函数
  93. * @param {ComponentInternalInstance} vm - 组件实例
  94. */
  95. export const useQueryElInfo = (
  96. id: string,
  97. cb: (params: UniApp.NodeInfo | UniApp.NodeInfo[] ) => void,
  98. vm?: ComponentInternalInstance,
  99. immediate: boolean = true
  100. ) => {
  101. const queryNode = () => {
  102. const query = vm ? uni.createSelectorQuery().in(vm) : uni.createSelectorQuery();
  103. query.select(id).boundingClientRect(data => cb(data)).exec();
  104. }
  105. // onMounted(() => queryNode())
  106. immediate ? onMounted(() => queryNode()) : queryNode()
  107. }
  108. /**
  109. * 它创建一个音频实例,并返回一个具有 destroy、play 和 onPlayend 方法的对象。
  110. * @param {string} src - 音频文件的来源
  111. * @returns 具有三个属性的对象:destroy、play 和 onPlayend。
  112. */
  113. export const useAudioMange = (src?: string) => {
  114. const audioInstance = ref< UniApp.InnerAudioContext>()
  115. audioInstance.value = uni.createInnerAudioContext()
  116. if (src) {
  117. audioInstance.value.src = src
  118. }
  119. const destroy = () => audioInstance.value?.stop()
  120. const play = (src: string) => {
  121. audioInstance.value!.src = src
  122. console.log(src, audioInstance.value);
  123. useSchedulerOnce(() => {
  124. console.log('play');
  125. audioInstance.value?.play()
  126. }, 500)
  127. }
  128. audioInstance.value.autoplay = true
  129. audioInstance.value!.onCanplay((e) => {
  130. console.log('onCanplay:111', e);
  131. })
  132. audioInstance.value?.onError((e) => {
  133. console.log(e);
  134. })
  135. const onplayend = (cb: Function) => audioInstance.value?.onEnded(() => cb && cb())
  136. onUnmounted(() => {
  137. destroy()
  138. })
  139. const atx = {
  140. destroy,
  141. play,
  142. onplayend,
  143. }
  144. return atx
  145. }
  146. /**
  147. * 它以字符串形式返回设备平台。
  148. * @returns uniPlatform 作为 DEVICE.Platform 返回。
  149. */
  150. export const usePlatform = (): DEVICE.Platform => {
  151. const { uniPlatform } = uni.getSystemInfoSync()
  152. return uniPlatform as DEVICE.Platform
  153. }
  154. /**
  155. * 获取多设备动态适配的比率
  156. * @example
  157. * const rate = useAdaptationIpadAndPhone()
  158. * node.width = node.width * rate * 2 + 'rpx
  159. */
  160. export const useAdaptationIpadAndPhone = () => {
  161. let a = window.innerWidth / window.innerHeight > 0.5 ? 667 / ( window.innerHeight ) : 1
  162. const resizeHanlde = () => {
  163. // 获取当前窗口的宽度
  164. var screenWidth = window.innerWidth;
  165. var screenHeight = window.innerHeight;
  166. const rate = screenWidth / screenHeight
  167. if ( rate > 0.5 ) { // 大于0.6 说明是平板
  168. a = 667 / ( screenHeight )
  169. } else {
  170. a = 1
  171. }
  172. }
  173. // 监听窗口尺寸变化事件
  174. window.addEventListener('resize', resizeHanlde);
  175. return a
  176. }
  177. export const useQueryParmas = async () => {
  178. let state = reactive({})
  179. state = await new Promise( (resolve) => {
  180. onLoad(query => {
  181. console.log(3);
  182. resolve(query!)
  183. })
  184. })
  185. return {...toRefs(state)}
  186. }
  187. /**
  188. * 这是一个导出对象的TypeScript函数,有两个方法decode和encode,分别将Base64编码的字符串解码为JSON对象,将JSON对象编码为Base64编码的字符串。
  189. * @returns 正在返回名为“useBase64”的自定义挂钩。这个钩子有两个函数:`decode` 和 `encode`。 `decode` 函数将字符串作为输入并返回解析后的 JSON 对象。
  190. * `encode` 函数将 `API.P` 类型的对象作为输入并返回 base64 编码的字符串。
  191. */
  192. export const useBase64 = () => {
  193. return {
  194. decode: <T>(URI: string): T => JSON.parse(decode(URI)),
  195. encode: (records: API.P): string => encode(JSON.stringify(records))
  196. }
  197. }
  198. /**
  199. * @description 获取设备方向
  200. * @param cb
  201. */
  202. export const useDeviceDire = (cb: (dire: 'H' | 'V') => void) => {
  203. const getDire = () => {
  204. if (window.orientation === 180 || window.orientation === 0) {
  205. cb('V')
  206. }
  207. if (window.orientation === 90 || window.orientation === -90) {
  208. cb('H')
  209. }
  210. }
  211. window.addEventListener('orientationchange', getDire)
  212. onMounted(getDire)
  213. onUnmounted(() => {
  214. window.removeEventListener('orientationchange', () => {})
  215. })
  216. }
  217. /**
  218. * @description 题卡的放大镜功能
  219. */
  220. export const useMagnifier = async (
  221. canvas: HTMLCanvasElement,
  222. canvasId: string,
  223. { ansUrl, quesUrl, instance, rate }
  224. :
  225. { ansUrl: string, quesUrl: string, instance: ComponentInternalInstance, rate: number}
  226. ) => {
  227. function isIOS() {
  228. const userAgent = navigator.userAgent;
  229. return /iPhone|iPad|iPod/i.test(userAgent);
  230. }
  231. const adaptatio = useAdaptationIpadAndPhone()
  232. let ctx: UniApp.CanvasContext = uni.createCanvasContext(canvasId)
  233. const deviceWidthRate = uni.getSystemInfoSync().windowWidth / 375
  234. // 初始化放大镜的参数
  235. const magnifierSize = 150 * deviceWidthRate ; // 放大镜尺寸
  236. const magnification = 1.5 ; // 放大倍数
  237. const lineWidth = 6 * deviceWidthRate// 放大镜边框宽度
  238. let _moveMagnifier = (event: TouchEvent) => {}
  239. const createImgStr = async () => {
  240. ctx.drawImage(quesUrl, 0, 0, 224 * deviceWidthRate, 386 * deviceWidthRate)
  241. ctx.drawImage(ansUrl, 225 * deviceWidthRate , 0, 75 * deviceWidthRate, 386 * deviceWidthRate)
  242. ctx.save()
  243. ctx.draw()
  244. // ctx.restore();
  245. // 创建 Image 对象并加载图片
  246. await new Promise( resolve => {
  247. useSchedulerOnce(() => {
  248. resolve(true)
  249. }, 100)
  250. })
  251. return await new Promise((resolve) => {
  252. uni.canvasToTempFilePath({
  253. canvasId: canvasId,
  254. fileType: 'png',
  255. quality: 1,
  256. destWidth: 300 * deviceWidthRate,
  257. destHeight: 386 * deviceWidthRate,
  258. success: (res) => resolve( res.tempFilePath),
  259. fail: (error) => {
  260. console.error('canvasToTempFilePath failed: ', error);
  261. }
  262. }, instance);
  263. }) as string
  264. }
  265. const draw = async () => {
  266. const imgStr = await createImgStr()
  267. function initMagnifier () {
  268. // 计算放大镜区域的位置和尺寸
  269. const startX = 200;
  270. const startY = 0;
  271. const width = magnifierSize;
  272. const height = magnifierSize;
  273. // 绘制阴影
  274. ctx.shadowColor = 'black';
  275. ctx.shadowBlur = 20;
  276. ctx.shadowOffsetX = 0;
  277. ctx.shadowOffsetY = 0;
  278. // 绘制放大镜效果
  279. ctx.save();
  280. ctx.beginPath();
  281. ctx.arc(
  282. startX,
  283. startY,
  284. magnifierSize / 2,
  285. 0,
  286. 2 * Math.PI
  287. );
  288. ctx.strokeStyle = 'yellow';
  289. ctx.lineWidth = lineWidth;
  290. ctx.stroke();
  291. ctx.closePath();
  292. ctx.clip();
  293. ctx.drawImage(
  294. imgStr,
  295. startX,
  296. startY,
  297. width,
  298. height,
  299. startX - magnifierSize / 2,
  300. startY - magnifierSize / 2,
  301. magnifierSize * magnification,
  302. magnifierSize * magnification
  303. );
  304. ctx.draw()
  305. ctx.restore();
  306. }
  307. function moveMagnifier (event: TouchEvent) {
  308. event.preventDefault();
  309. event.stopPropagation();
  310. let mouseX = Math.floor((event.touches[0].pageX - canvas.getBoundingClientRect().left) / adaptatio)
  311. let mouseY = Math.floor((event.touches[0].pageY - canvas.getBoundingClientRect().top) / adaptatio)
  312. // 拖拽边界处理
  313. if (mouseX <= 0) mouseX = 0
  314. if (mouseX >= canvas.getBoundingClientRect().right - 26 ) mouseX = canvas.getBoundingClientRect().right - 26
  315. if (mouseY <= 0) mouseY = 0
  316. if (mouseY >= (canvas.getBoundingClientRect().bottom - 214 )) mouseY = canvas.getBoundingClientRect().bottom - 214
  317. // 拖拽边界处理
  318. // 清空 Canvas
  319. ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
  320. // 计算放大镜区域的位置和尺寸
  321. const startX = Math.floor((mouseX - magnifierSize / 2 ))
  322. const startY = Math.floor((mouseY - magnifierSize / 2 ))
  323. const width = magnifierSize;
  324. const height = magnifierSize;
  325. // 绘制放大镜效果
  326. ctx.save();
  327. ctx.beginPath();
  328. ctx.arc(mouseX, mouseY, magnifierSize / 2, 0, 2 * Math.PI);
  329. ctx.strokeStyle = 'yellow';
  330. ctx.lineWidth = lineWidth;
  331. ctx.stroke()
  332. ctx.closePath();
  333. ctx.clip();
  334. const sxCount = mouseX - (magnifierSize * magnification) / 2
  335. const syCount = mouseY - (magnifierSize * magnification) / 2
  336. const sx = sxCount <= 0 ? 0 : mouseX - (magnifierSize * magnification) / 2
  337. const sy = syCount <= 0 ? 0 : (mouseY - (magnifierSize * magnification) / 2)
  338. ctx.drawImage(
  339. imgStr,
  340. startX,
  341. startY,
  342. width,
  343. height,
  344. isIOS() ? sx: sxCount ,
  345. isIOS() ? sy : syCount,
  346. magnifierSize * magnification ,
  347. magnifierSize * magnification
  348. );
  349. // ctx.drawImage(
  350. // imgStr,
  351. // startX,
  352. // startY,
  353. // width,
  354. // height,
  355. // mouseX - (magnifierSize * magnification) / 2 ,
  356. // mouseY - (magnifierSize * magnification) / 2 ,
  357. // magnifierSize * magnification ,
  358. // magnifierSize * magnification
  359. // );
  360. ctx.draw()
  361. ctx.restore();
  362. }
  363. _moveMagnifier = moveMagnifier
  364. initMagnifier();
  365. // 监听鼠标移动事件,实现放大镜效果
  366. canvas.addEventListener("touchmove",moveMagnifier);
  367. }
  368. const clear = () => {
  369. ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
  370. ctx.draw()
  371. canvas.removeEventListener('touchmove', _moveMagnifier)
  372. }
  373. onUnmounted(() => {
  374. canvas.removeEventListener('touchmove', _moveMagnifier)
  375. })
  376. return {
  377. draw, clear
  378. }
  379. };
  380. /**
  381. * @description 陀螺仪 根据陀螺仪判断是否是横屏状态
  382. */
  383. export const useGyroH = async (cb: (dire: string) => void) => {
  384. const handleOrientation = (event: DeviceOrientationEvent) => {
  385. var alpha = event.alpha!; // 设备绕 Z 轴的旋转角度(0 到 360 度)
  386. var beta = event.beta!; // 设备绕 X 轴的旋转角度(-180 到 180 度)
  387. var gamma = event.gamma!; // 设备绕 Y 轴的旋转角度(-90 到 90 度)
  388. console.log(`x轴:${Math.floor(beta)} y轴:${ Math.floor(gamma)} z轴:${ Math.floor(alpha) }`);
  389. let dire = ''
  390. if (alpha < 10 ) {
  391. dire = 'H'
  392. } else {
  393. dire = 'V'
  394. }
  395. cb && cb(dire)
  396. }
  397. if (window.DeviceOrientationEvent) {
  398. window.addEventListener('deviceorientation', handleOrientation);
  399. // if ( uni.getSystemInfoSync().system.includes('iOS')) { }
  400. } else {
  401. console.log('设备不支持陀螺仪功能');
  402. }
  403. onUnmounted(() => {
  404. window.removeEventListener('deviceorientation', handleOrientation)
  405. })
  406. }