index.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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: number | null = null
  30. const playing = () => {}
  31. const clearScheduler = () => clearInterval(timeId as number)
  32. onUnmounted(clearScheduler)
  33. stx.start = () => {
  34. timeId = 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. console.log('src');
  118. audioInstance.value.src = src
  119. }
  120. const destroy = () => audioInstance.value?.stop()
  121. const play = (src: string) => {
  122. audioInstance.value!.src = src
  123. console.log(src, audioInstance.value);
  124. useSchedulerOnce(() => {
  125. console.log('play');
  126. audioInstance.value?.play()
  127. }, 500)
  128. }
  129. audioInstance.value.autoplay = true
  130. audioInstance.value!.onCanplay((e) => {
  131. console.log('onCanplay:111', e);
  132. })
  133. audioInstance.value?.onError((e) => {
  134. console.log(e);
  135. })
  136. const onplayend = (cb: Function) => audioInstance.value?.onEnded(() => cb && cb())
  137. onUnmounted(() => {
  138. destroy()
  139. })
  140. const atx = {
  141. destroy,
  142. play,
  143. onplayend,
  144. }
  145. return atx
  146. }
  147. /**
  148. * 它以字符串形式返回设备平台。
  149. * @returns uniPlatform 作为 DEVICE.Platform 返回。
  150. */
  151. export const usePlatform = (): DEVICE.Platform => {
  152. const { uniPlatform } = uni.getSystemInfoSync()
  153. return uniPlatform as DEVICE.Platform
  154. }
  155. /**
  156. * 获取多设备动态适配的比率
  157. * @example
  158. * const rate = useAdaptationIpadAndPhone()
  159. * node.width = node.width * rate * 2 + 'rpx
  160. */
  161. export const useAdaptationIpadAndPhone = () => {
  162. // const { windowWidth, windowHeight } = uni.getWindowInfo()
  163. // console.log(document.documentElement.clientWidth, document.documentElement.clientHeight);
  164. // const { screenWidth, screenHeight, windowWidth, windowHeight } = uni.getSystemInfoSync()
  165. // console.log(window.screen.width, window.screen.height);
  166. console.log('useAdaptationIpadAndPhone');
  167. let a = window.innerWidth / window.innerHeight > 0.5 ? 667 / ( window.innerHeight ) : 1
  168. const resizeHanlde = () => {
  169. // 获取当前窗口的宽度
  170. var screenWidth = window.innerWidth;
  171. var screenHeight = window.innerHeight;
  172. const rate = screenWidth / screenHeight
  173. console.log(screenWidth, screenHeight );
  174. if ( rate > 0.5 ) { // 大于0.6 说明是平板
  175. a = 667 / ( screenHeight )
  176. } else {
  177. a = 1
  178. }
  179. }
  180. // 监听窗口尺寸变化事件
  181. window.addEventListener('resize', resizeHanlde);
  182. console.log('a', a);
  183. return a
  184. }
  185. export const useQueryParmas = async () => {
  186. let state = reactive({})
  187. state = await new Promise( (resolve) => {
  188. onLoad(query => {
  189. console.log(3);
  190. resolve(query!)
  191. })
  192. })
  193. return {...toRefs(state)}
  194. }
  195. /**
  196. * 这是一个导出对象的TypeScript函数,有两个方法decode和encode,分别将Base64编码的字符串解码为JSON对象,将JSON对象编码为Base64编码的字符串。
  197. * @returns 正在返回名为“useBase64”的自定义挂钩。这个钩子有两个函数:`decode` 和 `encode`。 `decode` 函数将字符串作为输入并返回解析后的 JSON 对象。
  198. * `encode` 函数将 `API.P` 类型的对象作为输入并返回 base64 编码的字符串。
  199. */
  200. export const useBase64 = () => {
  201. return {
  202. decode: <T>(URI: string): T => JSON.parse(decode(URI)),
  203. encode: (records: API.P): string => encode(JSON.stringify(records))
  204. }
  205. }
  206. /**
  207. * @description 获取设备方向
  208. * @param cb
  209. */
  210. export const useDeviceDire = (cb: (dire: 'H' | 'V') => void) => {
  211. const getDire = () => {
  212. if (window.orientation === 180 || window.orientation === 0) {
  213. cb('V')
  214. }
  215. if (window.orientation === 90 || window.orientation === -90) {
  216. cb('H')
  217. }
  218. }
  219. window.addEventListener('orientationchange', getDire)
  220. onMounted(getDire)
  221. onUnmounted(() => {
  222. window.removeEventListener('orientationchange', () => {})
  223. })
  224. }
  225. /**
  226. * @description 题卡的放大镜功能
  227. */
  228. export const useMagnifier = async (
  229. canvas: HTMLCanvasElement,
  230. canvasId: string,
  231. { ansUrl, quesUrl, instance, rate }
  232. :
  233. { ansUrl: string, quesUrl: string, instance: ComponentInternalInstance, rate: number}
  234. ) => {
  235. return
  236. const adaptatio = useAdaptationIpadAndPhone()
  237. let ctx: UniApp.CanvasContext = uni.createCanvasContext(canvasId)
  238. const deviceWidthRate = uni.getSystemInfoSync().windowWidth / 375
  239. // 初始化放大镜的参数
  240. const magnifierSize = 150 * deviceWidthRate ; // 放大镜尺寸
  241. const magnification = 1.5 ; // 放大倍数
  242. const lineWidth = 4 * deviceWidthRate// 放大镜边框宽度
  243. ctx.drawImage(quesUrl, 0, 0, 224 * deviceWidthRate, 386 * deviceWidthRate)
  244. ctx.save()
  245. ctx.drawImage(ansUrl, 225 * deviceWidthRate , 0, 75 * deviceWidthRate, 386 * deviceWidthRate)
  246. ctx.save()
  247. ctx.draw()
  248. ctx.restore();
  249. // console.log(' canvas.getBoundingClientRect().width:', canvas.getBoundingClientRect().width);
  250. // ctx.fillRect(0, 0, canvas.getBoundingClientRect().width / adaptatio , canvas.getBoundingClientRect().height / adaptatio )
  251. // ctx.setFillStyle('rgb(0, 0, 0)')
  252. // ctx.draw()
  253. // return
  254. // 创建 Image 对象并加载图片
  255. await new Promise( resolve => {
  256. useSchedulerOnce(() => {
  257. resolve(true)
  258. }, 100)
  259. })
  260. const imageStr = await new Promise((resolve) => {
  261. uni.canvasToTempFilePath({
  262. canvasId: canvasId,
  263. fileType: 'png',
  264. quality: 1,
  265. destWidth: 300 * deviceWidthRate,
  266. destHeight: 386 * deviceWidthRate,
  267. success: (res) => resolve( res.tempFilePath),
  268. fail: (error) => {
  269. console.error('canvasToTempFilePath failed: ', error);
  270. }
  271. }, instance);
  272. }) as string
  273. function initMagnifier () {
  274. // 计算放大镜区域的位置和尺寸
  275. const startX = 0;
  276. const startY = 0;
  277. const width = magnifierSize;
  278. const height = magnifierSize;
  279. // 绘制放大镜效果
  280. ctx.save();
  281. ctx.beginPath();
  282. ctx.arc(
  283. magnifierSize / 2,
  284. magnifierSize / 2,
  285. magnifierSize / 2,
  286. 0,
  287. 2 * Math.PI
  288. );
  289. ctx.strokeStyle = 'yellow'; // 设置边框颜色为黄色
  290. ctx.lineWidth = lineWidth; // 设置边框宽度
  291. ctx.stroke(); // 绘制边框
  292. ctx.closePath();
  293. ctx.clip();
  294. ctx.drawImage(
  295. imageStr,
  296. startX,
  297. startY,
  298. width,
  299. height,
  300. 0 - magnifierSize / 2,
  301. 0 - magnifierSize / 2,
  302. magnifierSize * magnification,
  303. magnifierSize * magnification
  304. );
  305. ctx.draw()
  306. ctx.restore();
  307. }
  308. function moveMagnifier (event: TouchEvent) {
  309. event.preventDefault();
  310. event.stopPropagation();
  311. // * adaptatio * deviceWidthRate
  312. const mouseX = (event.touches[0].pageX - canvas.getBoundingClientRect().left) / adaptatio
  313. const mouseY = (event.touches[0].pageY - canvas.getBoundingClientRect().top) / adaptatio
  314. // 清空 Canvas
  315. ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
  316. // 计算放大镜区域的位置和尺寸
  317. const startX = (mouseX - magnifierSize / 2)
  318. const startY = (mouseY - magnifierSize / 2)
  319. const width = magnifierSize;
  320. const height = magnifierSize;
  321. // 绘制放大镜效果
  322. ctx.save();
  323. ctx.beginPath();
  324. ctx.arc(mouseX, mouseY, magnifierSize / 2, 0, 2 * Math.PI);
  325. ctx.strokeStyle = 'yellow';
  326. ctx.lineWidth = lineWidth;
  327. ctx.stroke()
  328. ctx.closePath();
  329. ctx.clip();
  330. ctx.drawImage(
  331. imageStr,
  332. startX,
  333. startY,
  334. width,
  335. height,
  336. mouseX - (magnifierSize * magnification) / 2,
  337. mouseY - (magnifierSize * magnification) / 2,
  338. magnifierSize * magnification,
  339. magnifierSize * magnification
  340. );
  341. ctx.draw()
  342. ctx.restore();
  343. }
  344. const draw = async () => {
  345. initMagnifier();
  346. // 监听鼠标移动事件,实现放大镜效果
  347. canvas.addEventListener("touchmove", moveMagnifier);
  348. }
  349. const clear = () => {
  350. ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
  351. ctx.draw()
  352. canvas.removeEventListener('touchmove', moveMagnifier)
  353. }
  354. onUnmounted(() => {
  355. canvas.removeEventListener('touchmove', moveMagnifier)
  356. })
  357. return {
  358. draw, clear
  359. }
  360. };
  361. /**
  362. * 陀螺仪
  363. */
  364. export const useGyro = async () => {
  365. const handleOrientation = (event: DeviceOrientationEvent) => {
  366. var alpha = event.alpha; // 设备绕 Z 轴的旋转角度(0 到 360 度)
  367. var beta = event.beta!; // 设备绕 X 轴的旋转角度(-180 到 180 度)
  368. var gamma = event.gamma; // 设备绕 Y 轴的旋转角度(-90 到 90 度)
  369. console.log('beta', beta);
  370. if ( beta > 45 && beta <= 270 ) {
  371. console.log('顺时针横屏');
  372. } else if ( beta < -45 && beta >= -270 ) {
  373. console.log('逆時針横屏');
  374. }
  375. }
  376. if (window.DeviceOrientationEvent) {
  377. console.log();
  378. // 支持陀螺仪功能
  379. window.addEventListener('deviceorientation', handleOrientation);
  380. } else {
  381. console.log('设备不支持陀螺仪功能');
  382. }
  383. onUnmounted(() => {
  384. window.removeEventListener('deviceorientation', handleOrientation)
  385. })
  386. }