index.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /* eslint-disable */
  2. declare global {
  3. interface Window {
  4. plus: any;
  5. }
  6. }
  7. interface SystemInfo {
  8. os: 'Web' | 'Android' | 'iOS'; // 客户端操作系统
  9. version: string; // 客户端版本号
  10. versionCode: string; // 客户端版本代码
  11. statusBarHeight: number; // 状态栏高度
  12. }
  13. const urlScheme = {
  14. appStore: {
  15. name: 'App Store',
  16. pname: '',
  17. scheme: 'itms-apps://'
  18. },
  19. alipay: {
  20. name: '支付宝',
  21. pname: 'com.eg.android.AlipayGphone',
  22. scheme: 'alipay://'
  23. },
  24. taobao: {
  25. name: '淘宝',
  26. pname: 'com.taobao.taobao',
  27. scheme: 'taobao://'
  28. },
  29. qq: {
  30. name: 'QQ',
  31. pname: 'com.tencent.mobileqq',
  32. scheme: 'mqq://'
  33. },
  34. weixin: {
  35. name: '微信',
  36. pname: 'com.tencent.mm',
  37. scheme: 'weixin://'
  38. },
  39. jd: {
  40. name: '京东',
  41. pname: 'com.jingdong.app.mall',
  42. scheme: 'openApp.jdMobile://'
  43. },
  44. weibo: {
  45. name: '新浪微博',
  46. pname: 'com.sina.weibo',
  47. scheme: 'sinaweibo://'
  48. },
  49. youku: {
  50. name: '优酷',
  51. pname: 'com.youku.phone',
  52. scheme: 'youku://'
  53. }
  54. }
  55. export default new (class {
  56. private readonly plusready = new Promise<void>((resolve) => {
  57. if (this.hasPlus()) {
  58. resolve()
  59. } else {
  60. document.addEventListener('plusready', () => resolve())
  61. }
  62. })
  63. /**
  64. * 系统信息
  65. */
  66. private systemInfo: SystemInfo = {
  67. os: 'Web', // 客户端操作系统
  68. version: '1.0', // 客户端版本号
  69. versionCode: '100000', // 客户端版本代码
  70. statusBarHeight: 0, // 状态栏高度
  71. }
  72. constructor() {
  73. this.plusready.then(() => {
  74. const plus = window.plus
  75. this.systemInfo.os = plus.os.name
  76. this.systemInfo.statusBarHeight = plus.navigator.getStatusbarHeight()
  77. plus.runtime.getProperty(plus.runtime.appid, (info: any) => {
  78. this.systemInfo.version = info.version
  79. this.systemInfo.versionCode = info.versionCode
  80. })
  81. })
  82. // 监听返回按钮事件
  83. this.onPlusReady((plus) => {
  84. let firstBack = true
  85. const webview = plus.webview.currentWebview()
  86. plus.key.addEventListener('backbutton', () => {
  87. webview.canBack((e: any) => {
  88. // 判断能否继续返回
  89. if (e.canBack) {
  90. webview.back()
  91. } else {
  92. // 1秒内连续两次按返回键退出应用
  93. if (firstBack) {
  94. firstBack = false
  95. plus.nativeUI.toast('再按一次退出应用')
  96. setTimeout(() => {
  97. firstBack = true
  98. }, 1000)
  99. } else {
  100. plus.runtime.quit()
  101. }
  102. }
  103. })
  104. })
  105. })
  106. }
  107. hasPlus() {
  108. return !!window.plus
  109. }
  110. onPlusReady(callback: (plus: Window['plus']) => void) {
  111. this.plusready.then(() => {
  112. callback(window.plus)
  113. })
  114. }
  115. /**
  116. * 退出应用程序
  117. */
  118. quit() {
  119. this.onPlusReady((plus) => {
  120. plus.runtime.quit()
  121. })
  122. }
  123. /**
  124. * 获取系统信息
  125. * @param prop
  126. * @returns
  127. */
  128. getSystemInfo<K extends keyof SystemInfo>(prop: K) {
  129. return this.systemInfo[prop]
  130. }
  131. /**
  132. * 获取状态栏高度
  133. * @param callback
  134. */
  135. getStatusBarHeight(callback: (statusbarHeight: number) => void) {
  136. this.onPlusReady((plus) => {
  137. const height = plus.navigator.getStatusbarHeight()
  138. callback(height)
  139. })
  140. }
  141. /**
  142. * 设置状态栏文字颜色
  143. * @param color dark - 暗色,light - 亮色
  144. */
  145. setStatusBarStyle(color: 'dark' | 'light') {
  146. this.onPlusReady((plus) => {
  147. plus.navigator.setStatusBarStyle(color)
  148. })
  149. }
  150. /**
  151. * 隐藏状态栏
  152. */
  153. hideStatusBar() {
  154. this.onPlusReady((plus) => {
  155. plus.navigator.setFullscreen(true)
  156. })
  157. }
  158. /**
  159. * 显示状态栏
  160. */
  161. showStatusBar() {
  162. this.onPlusReady((plus) => {
  163. plus.navigator.setFullscreen(false)
  164. })
  165. }
  166. /**
  167. * 设置应用全屏
  168. */
  169. setFullSreen() {
  170. this.onPlusReady((plus) => {
  171. this.hideStatusBar()
  172. plus.navigator.hideSystemNavigation()
  173. })
  174. }
  175. /**
  176. * 应用退出全屏
  177. */
  178. exitFullSreen() {
  179. this.onPlusReady((plus) => {
  180. this.showStatusBar()
  181. plus.navigator.showSystemNavigation()
  182. })
  183. }
  184. /**
  185. * 更新应用
  186. * @param url
  187. */
  188. updateApp(url: string) {
  189. this.onPlusReady((plus) => {
  190. const dtask = plus.downloader.createDownload(
  191. url,
  192. {
  193. filename: ''
  194. },
  195. function (d: { filename: string }, status: number) {
  196. if (status == 200) {
  197. // 当前下载的状态
  198. installApp(d.filename) // 调用安装的方法
  199. } else {
  200. //plus.nativeUI.alert('下载失败')
  201. }
  202. }
  203. )
  204. dtask.start() // 开启下载的任务
  205. // app自动更新进度
  206. dtask.addEventListener('statechanged', function (task: { state: number }) {
  207. // 给下载任务设置一个监听 并根据状态 做操作
  208. switch (task.state) {
  209. case 1:
  210. console.log('正在下载')
  211. break
  212. case 2:
  213. console.log('已连接到服务器')
  214. break
  215. case 3:
  216. // console.log(task)
  217. // console.log(task.downloadedSize)//当前的大
  218. // console.log(task.totalSize)//安装包的大小
  219. }
  220. })
  221. // 自动更新
  222. function installApp(path: string) {
  223. plus.nativeUI.showWaiting('正在更新...')
  224. plus.runtime.install(
  225. path,
  226. {
  227. // true表示强制安装,不进行版本号的校验;false则需要版本号校验,如果将要安装应用的版本号不高于现有应用的版本号则终止安装,并返回安装失败。 仅安装wgt和wgtu时生效,默认值 false
  228. force: false
  229. },
  230. function () {
  231. plus.nativeUI.closeWaiting()
  232. console.log('更新成功!')
  233. plus.runtime.restart()
  234. },
  235. function (e: { message: string }) {
  236. plus.nativeUI.closeWaiting()
  237. plus.nativeUI.alert('更新失败:' + e.message)
  238. }
  239. )
  240. }
  241. })
  242. }
  243. /**
  244. * 保存图片到相册
  245. * @param base64Data
  246. */
  247. saveImage(base64Data: string, fileName?: string) {
  248. this.onPlusReady((plus) => {
  249. const bitmap = new plus.nativeObj.Bitmap()
  250. const filename = fileName ?? new Date().getTime()
  251. bitmap.loadBase64Data(base64Data)
  252. bitmap.save(`_doc/${filename}.jpg`, { overwrite: true, quality: 100, }, (e: Event) => {
  253. //保存到系统相册
  254. plus.gallery.save(
  255. e.target,
  256. () => {
  257. //销毁Bitmap图片
  258. bitmap.clear()
  259. plus.nativeUI.toast('已保存到相册中')
  260. },
  261. () => {
  262. //销毁Bitmap图片
  263. bitmap.clear()
  264. plus.nativeUI.toast('保存失败')
  265. }
  266. )
  267. }, (err: { message: string }) => {
  268. plus.nativeUI.toast(err.message)
  269. })
  270. })
  271. }
  272. /**
  273. * 打开本地文件(安卓生产包可能无效)
  274. * https://www.html5plus.org/doc/zh_cn/runtime.html#plus.runtime.openFile
  275. * @param filePath
  276. */
  277. openFile(filePath: string) {
  278. if (this.hasPlus()) {
  279. this.onPlusReady((plus) => {
  280. plus.runtime.openFile('_www/' + filePath)
  281. })
  282. } else {
  283. window.open(filePath)
  284. }
  285. }
  286. /**
  287. * https://www.html5plus.org/doc/zh_cn/runtime.html#plus.runtime.openURL
  288. * @param url
  289. */
  290. openURL(url: string) {
  291. if (this.hasPlus()) {
  292. this.onPlusReady((plus) => {
  293. plus.runtime.openURL(url)
  294. })
  295. } else {
  296. window.open(url)
  297. }
  298. }
  299. /**
  300. * 读取本地文件内容
  301. * @param filePath
  302. * @returns
  303. */
  304. getLocalFileContent(filePath: string) {
  305. return new Promise<any>((resolve, reject) => {
  306. this.onPlusReady((plus) => {
  307. plus.io.resolveLocalFileSystemURL('_www/' + filePath, (entry: any) => {
  308. entry.file((file: any) => {
  309. const fileReader = new plus.io.FileReader()
  310. fileReader.readAsText(file, 'utf-8')
  311. fileReader.onloadend = (evt: any) => {
  312. resolve(evt.target.result)
  313. }
  314. })
  315. }, (e: any) => {
  316. reject(e.message)
  317. })
  318. })
  319. })
  320. }
  321. /**
  322. * 内容分享
  323. */
  324. share() {
  325. this.onPlusReady((plus) => {
  326. // 成功回调
  327. const success = (services: any) => {
  328. console.log('分享列表', services)
  329. services.forEach((e: any) => {
  330. if (e.id === 'weixin') {
  331. e.send({
  332. type: 'web',
  333. title: '标题',
  334. content: '内容',
  335. href: 'https://',
  336. }, () => {
  337. console.log('分享成功')
  338. }, (e: Error) => {
  339. console.log('分享失败', e.message)
  340. })
  341. }
  342. })
  343. }
  344. // 失败回调
  345. const error = (e: Error) => {
  346. console.log('获取分享列表失败', e.message)
  347. }
  348. plus.share.getServices(success, error)
  349. })
  350. }
  351. /**
  352. * 打开第三方APP
  353. * @param app
  354. */
  355. launchApplication<K extends keyof typeof urlScheme>(app: K) {
  356. this.onPlusReady((plus) => {
  357. const os = this.getSystemInfo('os')
  358. const params = Object.create(null)
  359. if (os === 'Android') {
  360. params.pname = urlScheme[app].pname
  361. }
  362. if (os === 'iOS') {
  363. params.action = urlScheme[app].scheme
  364. }
  365. plus.runtime.launchApplication(params, (e: Error) => {
  366. console.log('失败', e.message)
  367. })
  368. })
  369. }
  370. })