| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- /* eslint-disable */
- import { v4 } from 'uuid'
- import { SystemInfo, ShareMessage, HttpRequestConfig } from './types'
- import { urlScheme } from './constants'
- declare global {
- interface Window {
- plus: any;
- }
- }
- interface AndroidErrorCallback {
- code: number;
- message: string;
- }
- export default new (class {
- private readonly plusready = new Promise<void>((resolve) => {
- if (this.hasPlus()) {
- resolve()
- } else {
- document.addEventListener('plusready', () => resolve())
- }
- })
- /**
- * 网络请求对象
- */
- private xhr = new XMLHttpRequest()
- /**
- * 当前下载任务
- */
- private downloadTask = new Map()
- /**
- * 系统信息
- */
- private systemInfo: SystemInfo = {
- os: 'Web', // 客户端操作系统
- version: '1.0.0', // 客户端版本号
- versionCode: '100000', // 客户端版本代码
- statusBarHeight: 0, // 状态栏高度
- }
- constructor() {
- this.onPlusReady((plus) => {
- this.xhr = new plus.net.XMLHttpRequest()
- this.systemInfo.os = plus.os.name
- this.systemInfo.statusBarHeight = plus.navigator.getStatusbarHeight()
- plus.runtime.getProperty(plus.runtime.appid, (info: any) => {
- this.systemInfo.version = info.version
- this.systemInfo.versionCode = info.versionCode
- })
- })
- // 监听返回按钮事件
- this.onPlusReady((plus) => {
- let firstBack = true
- plus.key.addEventListener('backbutton', () => {
- const webviews = plus.webview.all() // 所有Webview窗口
- if (webviews.length > 1) {
- plus.webview.close(webviews[webviews.length - 1])
- } else {
- const webview = plus.webview.currentWebview()
- webview.canBack((e: any) => {
- // 判断能否继续返回
- if (e.canBack) {
- webview.back()
- } else {
- // 1秒内连续两次按返回键退出应用
- if (firstBack) {
- firstBack = false
- plus.nativeUI.toast('再按一次退出应用')
- setTimeout(() => {
- firstBack = true
- }, 1000)
- } else {
- plus.runtime.quit()
- }
- }
- })
- }
- })
- })
- }
- hasPlus() {
- return !!window.plus
- }
- onPlusReady(callback: (plus: Window['plus']) => void) {
- this.plusready.then(() => {
- callback(window.plus)
- })
- }
- /**
- * 退出应用程序
- */
- quit() {
- this.onPlusReady((plus) => {
- plus.runtime.quit()
- })
- }
- /**
- * 获取系统信息
- * @param prop
- * @returns
- */
- getSystemInfo<K extends keyof SystemInfo>(prop: K) {
- return this.systemInfo[prop]
- }
- /**
- * 获取状态栏高度
- * @param callback
- */
- getStatusBarHeight(callback: (statusbarHeight: number) => void) {
- this.onPlusReady((plus) => {
- const height = plus.navigator.getStatusbarHeight()
- callback(height)
- })
- }
- /**
- * 设置状态栏文字颜色
- * @param color dark - 暗色,light - 亮色
- */
- setStatusBarStyle(color: 'dark' | 'light') {
- this.onPlusReady((plus) => {
- plus.navigator.setStatusBarStyle(color)
- })
- }
- /**
- * 隐藏状态栏
- */
- hideStatusBar() {
- this.onPlusReady((plus) => {
- plus.navigator.setFullscreen(true)
- })
- }
- /**
- * 显示状态栏
- */
- showStatusBar() {
- this.onPlusReady((plus) => {
- plus.navigator.setFullscreen(false)
- })
- }
- /**
- * 设置应用全屏
- */
- setFullSreen() {
- this.onPlusReady((plus) => {
- this.hideStatusBar()
- plus.navigator.hideSystemNavigation()
- })
- }
- /**
- * 应用退出全屏
- */
- exitFullSreen() {
- this.onPlusReady((plus) => {
- this.showStatusBar()
- plus.navigator.showSystemNavigation()
- })
- }
- /**
- * http 跨域请求(待完善)
- * @param config
- * @returns
- */
- httpRequest(config: HttpRequestConfig) {
- return new Promise<any>((resolve, reject) => {
- this.xhr.responseType = config.responseType ?? 'json'
- if (config.header) {
- for (const key in config.header) {
- this.xhr.setRequestHeader(key, config.header[key])
- }
- }
- this.xhr.onreadystatechange = () => {
- if (this.xhr.readyState === 4) {
- if (this.xhr.status == 200) {
- resolve({
- code: 200,
- data: this.xhr.response
- })
- } else {
- reject({
- code: this.xhr.status,
- message: this.xhr.statusText
- })
- }
- }
- }
- this.xhr.open(config.method ?? 'GET', config.url)
- this.xhr.send()
- })
- }
- /**
- * 删除文件
- * @param url
- */
- deleteFile(url: string) {
- this.onPlusReady((plus) => {
- plus.io.resolveLocalFileSystemURL(url, (entry: any) => {
- entry.remove()
- })
- })
- }
- /**
- * 监听下载进度
- * @param callback
- * @returns
- */
- onDownload(callback: (filename: string, progress: number) => void) {
- const uuid = v4()
- this.downloadTask.set(uuid, callback)
- /** 注意离开页面时销毁监听事件,防止事件重复触发 */
- return {
- uuid,
- cancel: () => this.downloadTask.delete(uuid)
- }
- }
- /**
- * 文件下载
- * https://www.html5plus.org/doc/zh_cn/downloader.html#plus.downloader.createDownload
- * @param url
- */
- createDownload(url: string) {
- this.onPlusReady((plus) => {
- // plus.downloader.enumerate((downloads: any) => {
- // if (downloads.length) {
- // plus.nativeUI.toast('正在下载')
- // } else {
- // }
- // })
- const task = plus.downloader.createDownload(url, {
- filename: '_downloads/', // 非系统 Download 目录
- retry: 1,
- }, (d: any, status: number) => {
- if (status !== 200) {
- plus.nativeUI.toast('下载失败,请稍后再试')
- }
- })
- // 监听下载状态
- task.addEventListener('statechanged', (task: any) => {
- console.log(task.state, task.downloadedSize / task.totalSize * 100)
- switch (task.state) {
- case 3:
- const progress = task.downloadedSize / task.totalSize * 100
- for (const fn of this.downloadTask.values()) {
- fn(task.filename, progress) // 推送下载进度
- }
- break
- case 4:
- console.log('下载完成', task.filename)
- this.downloadTask.clear()
- break
- }
- })
- // 开始下载
- task.start()
- })
- }
- /**
- * App更新安装
- * @param file
- */
- installApp(file: string) {
- this.onPlusReady((plus) => {
- plus.nativeUI.showWaiting('正在安装...')
- plus.runtime.install(file, {
- // true表示强制安装,不进行版本号的校验;false则需要版本号校验,如果将要安装应用的版本号不高于现有应用的版本号则终止安装,并返回安装失败。 仅安装wgt和wgtu时生效,默认值 false
- force: false
- }, () => {
- console.log('安装成功!')
- this.deleteFile(file)
- plus.nativeUI.closeWaiting()
- plus.runtime.restart()
- }, (e: any) => {
- plus.nativeUI.closeWaiting()
- plus.nativeUI.alert('安装失败:' + e.message)
- })
- })
- }
- /**
- * 保存图片到相册
- * @param base64Data
- */
- saveImage(base64Data: string, fileName?: string) {
- this.onPlusReady((plus) => {
- const bitmap = new plus.nativeObj.Bitmap()
- const filename = fileName ?? new Date().getTime()
- bitmap.loadBase64Data(base64Data)
- bitmap.save(`_doc/${filename}.jpg`, { overwrite: true, quality: 100, }, (e: Event) => {
- //保存到系统相册
- plus.gallery.save(
- e.target,
- () => {
- //销毁Bitmap图片
- bitmap.clear()
- plus.nativeUI.toast('已保存到相册中')
- },
- () => {
- //销毁Bitmap图片
- bitmap.clear()
- plus.nativeUI.toast('保存失败')
- }
- )
- }, (err: { message: string }) => {
- plus.nativeUI.toast(err.message)
- })
- })
- }
- /**
- * https://www.html5plus.org/doc/zh_cn/runtime.html#plus.runtime.openURL
- * @param url
- */
- openURL(url: string) {
- if (this.hasPlus()) {
- this.onPlusReady((plus) => {
- plus.runtime.openURL(url)
- })
- } else {
- window.open(url)
- }
- }
- /**
- * https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.create
- * @param options
- */
- openWebview(options: { url: string; id?: string; titleText?: string; titleColor?: string; backgroundColor?: string; onClose?: () => void; }) {
- if (this.hasPlus()) {
- const styles = {
- titleNView: {
- backgroundColor: options.backgroundColor,
- titleText: options.titleText,
- titleColor: options.titleColor,
- autoBackButton: true,
- }
- }
- this.onPlusReady((plus) => {
- const wv = plus.webview.create(options.url, options.id ?? v4(), styles)
- wv.show()
- wv.addEventListener('close', () => options.onClose && options.onClose(), false)
- })
- } else {
- this.openURL(options.url)
- }
- }
- /**
- * 将本地URL路径转换成平台绝对路径
- * https://www.html5plus.org/doc/zh_cn/io.html#plus.io.convertLocalFileSystemURL
- * @param url
- */
- convertLocalFileSystemURL(url: string) {
- return new Promise<string>((resolve) => {
- if (this.hasPlus()) {
- this.onPlusReady((plus) => {
- const localURL = plus.io.convertLocalFileSystemURL('_www/' + url)
- resolve(localURL)
- })
- } else {
- const absoluteURL = new URL(url, window.location.href).href
- resolve(absoluteURL)
- }
- })
- }
- /**
- * 读取本地文件内容
- * https://www.html5plus.org/doc/zh_cn/io.html#plus.io.resolveLocalFileSystemURL
- * @param filePath
- * @returns
- */
- getLocalFileContent(filePath: string) {
- return new Promise<any>((resolve, reject) => {
- this.onPlusReady((plus) => {
- plus.io.resolveLocalFileSystemURL('_www/' + filePath, (entry: any) => {
- entry.file((file: any) => {
- const fileReader = new plus.io.FileReader()
- fileReader.readAsText(file, 'utf-8')
- fileReader.onloadend = (evt: any) => {
- resolve(evt.target.result)
- }
- })
- }, (e: any) => {
- reject(e.message)
- })
- })
- })
- }
- /**
- * 系统分享
- * https://www.html5plus.org/doc/zh_cn/share.html#plus.share.sendWithSystem
- * @param message
- * @returns
- */
- systemShare(message: Partial<ShareMessage>) {
- return new Promise<void>((resolve, reject) => {
- this.onPlusReady((plus) => {
- const logo = plus.io.convertLocalFileSystemURL('./logo.gif')
- const shareMessage: Partial<ShareMessage> = {
- type: 'web',
- thumbs: ['file://' + logo],
- ...message,
- }
- plus.share.sendWithSystem(shareMessage, () => {
- resolve()
- }, (e: { code: number; message: string; }) => {
- reject(e.message)
- })
- })
- })
- }
- /**
- * 内容分享
- * https://www.html5plus.org/doc/zh_cn/share.html#plus.share.getServices
- */
- share() {
- this.onPlusReady((plus) => {
- // 成功回调
- const success = (services: any) => {
- console.log('分享列表', services)
- services.forEach((e: any) => {
- if (e.id === 'weixin') {
- e.send({
- type: 'web',
- title: '标题',
- content: '内容',
- href: 'https://',
- }, () => {
- console.log('分享成功')
- }, (e: Error) => {
- console.log('分享失败', e.message)
- })
- }
- })
- }
- // 失败回调
- const error = (e: Error) => {
- console.log('获取分享列表失败', e.message)
- }
- plus.share.getServices(success, error)
- })
- }
- /**
- * 打开第三方APP
- * https://www.html5plus.org/doc/zh_cn/runtime.html#plus.runtime.launchApplication
- * @param app
- */
- launchApplication<K extends keyof typeof urlScheme>(app: K) {
- this.onPlusReady((plus) => {
- const os = this.getSystemInfo('os')
- const params = Object.create(null)
- if (os === 'Android') {
- params.pname = urlScheme[app].pname
- }
- if (os === 'iOS') {
- params.action = urlScheme[app].scheme
- }
- plus.runtime.launchApplication(params, (e: Error) => {
- console.log('失败', e.message)
- })
- })
- }
- /**
- * 请求摄像头权限
- */
- requestPermissionCamera(options: Partial<{ onSuccess: () => void; onError: (message: string) => void; }> = {}) {
- const { onSuccess, onError } = options
- if (this.hasPlus()) {
- this.onPlusReady((plus) => {
- plus.android.requestPermissions(['android.permission.CAMERA'], (e: { granted: string[]; deniedPresent: string[]; deniedAlways: string[]; }) => {
- if (e.deniedAlways.length > 0) {
- onError && onError('访问摄像头被拒绝')
- }
- if (e.deniedPresent.length > 0) {
- onError && onError('请打开摄像头权限')
- }
- if (e.granted.length > 0) {
- onSuccess && onSuccess()
- }
- }, (e: AndroidErrorCallback) => {
- onError && onError(e.message)
- })
- })
- } else {
- onSuccess && onSuccess()
- }
- }
- /**
- * 请求麦克风权限
- */
- requestPermissionRecordAudio(options: Partial<{ onSuccess: () => void; onError: (message: string) => void; }> = {}) {
- const { onSuccess, onError } = options
- if (this.hasPlus()) {
- this.onPlusReady((plus) => {
- plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], (e: { granted: string[]; deniedPresent: string[]; deniedAlways: string[]; }) => {
- if (e.deniedAlways.length > 0) {
- onError && onError('访问麦克风被拒绝')
- }
- if (e.deniedPresent.length > 0) {
- onError && onError('请打开麦克风权限')
- }
- if (e.granted.length > 0) {
- onSuccess && onSuccess()
- }
- }, (e: AndroidErrorCallback) => {
- onError && onError(e.message)
- })
- })
- } else {
- onSuccess && onSuccess()
- }
- }
- })
|