diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 4d3dce9..d526b12 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -5,12 +5,16 @@ import { isDev } from '@/utils/utils'; import splash from '@/assets/splash.png'; import styles from './index.css'; +import { init, destory } from '@/utils/h5plus'; const BasicLayout: React.FC = props => { const [isReady, cancel] = useTimeout(isDev ? 100 : 1500); useEffect(() => { + init(); + return () => { + destory(); if (!isReady()) cancel(); }; }, []); diff --git a/src/pages/check/style.less b/src/pages/check/style.less index f26c788..c8e152c 100644 --- a/src/pages/check/style.less +++ b/src/pages/check/style.less @@ -65,7 +65,6 @@ .reset { height: 100%; - padding-top: @header-height; display: flex; flex-direction: column; } diff --git a/src/pages/draw/index.tsx b/src/pages/draw/index.tsx index 208aad6..c25cd4b 100644 --- a/src/pages/draw/index.tsx +++ b/src/pages/draw/index.tsx @@ -12,6 +12,7 @@ import { PageState } from '@/types/common'; import Header from '@/components/Header'; import styles from './style.less'; +import h5plus from '@/utils/h5plus'; const Option = Select.Option; @@ -44,8 +45,17 @@ const DrawPage: React.FC = ({ ); } else if (pageState === PageState.Success) { const coinLabel = coin && coin.label.toUpperCase(); - const onScan = () => { - message.warn(formatMessage({ id: 'draw.qrPrompt' })); + const onScan = async () => { + if (h5plus.isApp()) { + const result = await h5plus.scan(); + if (result) { + dispatch({ type: 'draw/onUpdateState', address: result }); + } else { + message.warn(formatMessage({ id: 'draw.qrPrompt' })); + } + } else { + message.warn(formatMessage({ id: 'draw.qrPrompt' })); + } } const confirmContent = ( = ({ {formatMessage({ id: 'home.mine.valid' })} - {info ? info.stoneAll : 0} KG + {info ? info.activeFund : 0} KG @@ -66,7 +66,7 @@ const MinePage: React.FC = ({ const renderItem = (it: Mine) => (
-
{formatMessage({ id: 'home.mine.validTime' })}{it.validTime}
+
{formatMessage({ id: 'home.mine.validTime' })}{it.createdAt}
+{it.amount} KG
@@ -74,7 +74,7 @@ const MinePage: React.FC = ({ {formatMessage({ id: 'home.mine.status' })} {formatMessage({ id: `home.mine.status${it.status}` })}
-
{formatMessage({ id: 'home.mine.invalidTime' })}{it.invalidTime}
+
{formatMessage({ id: 'home.mine.invalidTime' })}{it.expiresAt}
); diff --git a/src/types/common.ts b/src/types/common.ts index 3fe8e6a..e59f4ab 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -112,7 +112,7 @@ export interface MineBasic { /** * 当前有效矿晶 */ - stoneAll: string; + activeFund: string; } export enum MineStatus { @@ -132,12 +132,12 @@ export interface Mine { /** * 获赠日期 */ - validTime: string; + createdAt: string; /** * 到期日期 */ - invalidTime: string; + expiresAt: string; /** * 矿晶状态 diff --git a/src/types/global.d.ts b/src/types/global.d.ts index bf08b83..2719eed 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,3 +1,406 @@ +interface BaseView { } + +interface BarcodeSuccessCallback { + /** + * @param type 识别到的条码类型 与Barcode对象定义的条码类型常量一致。 + * @param code 识别到的条码数据 扫码识别出的数据内容,字符串类型,采用UTF8编码格式。 + * @param file 扫码成功的截图文件路径 扫码识别到的截图,png格式文件,如果设置为不保存截图,则返回undefined。 + */ + (type: number, code: string, file?: string): any; +} + +interface BarcodeErrorCallback { + (error: DOMException): any; +} + +interface BarcodeStyles { + /** + * 条码识别控件背景颜色 + * + * @description 颜色值支持(参考CSS颜色规范):颜色名称(参考CSS Color Names)/十六进制值/rgb值,默认值为红色。 + */ + background?: string; + + /** + * 扫码框颜色 + * + * @description 颜色值支持(参考CSS颜色规范):颜色名称(参考CSS Color Names)/十六进制值/rgb值,默认值为红色。 + */ + frameColor?: string; + + /** + * 扫码条颜色 + * + * @description 颜色值支持(参考CSS颜色规范):颜色名称(参考CSS Color Names)/十六进制值/rgb值,默认值为红色。 + */ + scanbarColor?: string; + + /** + * Barcode扫码控件左上角的垂直偏移量 + * + * @description 可取值: 像素值,如"100px"; 百分比,如"10%",相对于父Webview窗口的高度; 自动计算,如"auto",根据height值自动计算,相对于父Webview窗口垂直居中。 + */ + top?: string; + + /** + * Barcode扫码控件左上角的水平偏移量 + * + * @description 可取值: 像素值,如"100px"; 百分比,如"10%",相对于父Webview窗口的高度; 自动计算,如"auto",根据height值自动计算,相对于父Webview窗口垂直居中。 + */ + left?: string; + + /** + * Barcode扫码控件的宽度 + * + * @description 可取值: 像素值,如"100px"; 百分比,如"10%",相对于父Webview窗口的宽度。 默认值为"100%"。 + */ + width?: string; + + /** + * Barcode扫码控件的高度 + * + * @description 可取值: 像素值,如"100px"; 百分比,如"10%",相对于父Webview窗口的宽度。 默认值为"100%"。 + */ + height?: string; + + /** + * Barcode扫码控件在Webview窗口的布局模式 + * + * @description 可取值: "static" - 静态布局模式,如果页面存在滚动条则随窗口内容滚动; "absolute" - 绝对布局模式,如果页面存在滚动条不随窗口内容滚动; 默认值为"static"。 + */ + position?: 'static' | 'absolute'; +} + +interface BarcodeOptions { + /** + * 是否保存扫码成功时的截图 + * + * @description 如果设置为true则在扫码成功时将图片保存,并通过onmarked回调函数的file参数返回保存文件的路径。 默认值为false,不保存截图。 + */ + conserve?: boolean; + + /** + * 保存扫码成功时图片保存路径 + * + * @description 可通过此参数设置保存截图的路径和名称,如果设置图片文件名称则必须指定文件的后缀名(必须是.png),否则认为是指定目录,文件名称则自动生成。 + */ + filename?: string; + + /** + * 扫码成功时是否需要震动提醒 + * + * @description 如果设置为true则在扫码成功时震动设备,false则不震动。 默认值为true。 + */ + vibrate?: boolean; + + /** + * 扫码成功时播放的提示音 + * + * @description 可取值: "none" - 不播放提示音; "default" - 播放默认提示音(5+引擎内置)。 默认值为"default"。 + */ + sound?: 'none' | 'default'; +} + +interface Barcode extends BaseView { + /** + * 取消扫码识别 + * + * @description 结束对摄像头获取图片数据进行条码识别操作,同时关闭摄像头的视频捕获。 结束后可调用start方法重新开始识别。 + */ + cancel: () => any; + + /** + * 关闭条码识别控件 + * + * @description 释放控件占用系统资源,调用close方法后控件对象将不可使用。 + */ + close: () => any; + + /** + * 操作闪光灯 + * + * @description 设置扫码识别控件在扫码时是否开启摄像头的闪光灯,默认值为不开启闪光灯。 + * + * @param open true表示打开闪光灯,false表示关闭闪光灯。 + */ + setFlash: (open: boolean) => any; + + /** + * 设置扫码识别控件的样式 + * + * @description 用于动态更新扫码识别控件的显示样式参数。 + */ + setStyle: (styles: BarcodeStyles) => any; + + /** + * 开始扫码识别 + * + * @description 调用设备的摄像头在控件中预览,并获取捕获数据进行扫码识别,当识别出条码(二维码)数据时触发onmarked事件返回扫码结果。 + * + * @param options 扫码识别的参数 + */ + start: (options?: BarcodeOptions) => any; + + /** + * 扫码识别成功事件 + */ + onmarked: BarcodeSuccessCallback; + + /** + * 扫码识别错误事件 + */ + onerror: BarcodeErrorCallback; +} + +interface BarcodeWrapper { + /** + * QR二维码 + * + * @description 1994年由日本Denso-Wave公司发明,QR来自英文Quick Response的缩写,即快速反应的意思,源自发明者希望QR码可让其内容快速被解码。 目前使用最广泛的二维码。 + */ + QR: 0; + + /** + * EAN条形码标准版 + * + * @description 国际物品编码协会在全球推广应用的商品条码,是由13位数字组成。 目前使用最广泛的一维条形码。 + */ + EAN13: 1; + + /** + * ENA条形码简版 + * + * @description 国际物品编码协会在全球推广应用的商品条码,是由8位数字组成。 + */ + EAN8: 2; + + /** + * 扫码识别图片中的条码 + * + * @description 输入图片文件进行扫码识别,成功扫描到条码(一维码或二维码)后通过successCallback回调返回,失败则通过errorCallback回调返回。 + * + * @param path 要扫码的图片路径 必须是本地文件路径,如URLType类型(如以"_www"、"_doc"、"_documents"、"_downloads"开头的相对URL路径)或者系统绝对路径。 + * @param successCB 扫码识别成功回调函数 + * @param errorCB 扫码识别失败回调函数 + * @param filters 条码类型过滤器 + */ + scan: (path: string, successCB: BarcodeSuccessCallback, errorCB?: BarcodeErrorCallback, filters?: number[]) => any; + + /** + * 创建扫码识别控件对象 + * + * @description 此方法创建扫码识别控件并不会显示在页面中,需要调用plus.webview.Webview窗口对象的append方法将其添加到Webview窗口中才能显示。 注意:需要设置styles参数的top/left/width/height属性指定扫码识别控件的位置及大小,否则可能无法正确显示。 + * + * @param id 扫码识别控件的标识 可用于通过plus.barcode.getBarcodeById()方法查找已经创建的扫码识别控件对象。 + * @param filters 条码类型过滤器 条码类型常量数组,默认情况支持QR、EAN13、EAN8类型。 通过此参数可设置扫码识别支持的条码类型(注意:设置支持的条码类型越多,扫描识别速度可能将会降低)。 + * @param styles 扫码识别控件样式 + */ + create: (id: string, filters?: number[], styles?: BarcodeStyles) => Barcode; + + /** + * 查找扫码识别控件对象 + * + * @description 根据指定的id(标识)查找扫码控件对象,可跨页面进行查找。 + * + * @param id 扫码识别控件的标识 + */ + getBarcodeById: (id: string) => Barcode | null; +} + +interface WebviewObject extends BaseView { + /** + * Webview窗口的标识 + * + * @description 在打开或创建Webview窗口时设置,如果没有设置窗口标识,此属性值为undefined。 + */ + id: string; + + /** + * 在Webview窗口中添加子窗口 + * + * @description 将另一个Webview窗口作为子窗口添加到当前Webview窗口中,添加后其所有权归父Webview窗口,当父窗口关闭时子窗口自动关闭。 + */ + append: (view: BaseView) => any; + + /** + * 移除子Webview窗口 + * + * @description 从当前Webview窗口移除指定的子Webview窗口,若指定的webview对象不是当前窗口的子窗口则无任何作用。 移除后子Webview窗口不会关闭,需要调用其close方法才能真正关闭并销毁。 + */ + remove: (view: BaseView) => any; +} + +interface WebviewWrapper { + /** + * 获取所有Webview窗口 + * + * @description 获取应用中已创建的所有Webview窗口,包括所有未显示的Webview窗口。 返回WebviewObject对象在数组中按创建的先后顺序排列,即数组中第一个WebviewObject对象用是加载应用的入口页面。 + */ + all: () => WebviewObject[]; + + /** + * 获取当前窗口的WebviewObject对象 + */ + currentWebview: () => WebviewObject; + + /** + * 获取屏幕所有可视的Webview窗口 + * + * @description 仅在屏幕区域显示的Webview窗口,如果Webview窗口显示了但被其它Webview窗口盖住则认为不可视。 + */ + getDisplayWebview: () => WebviewObject[]; + + /** + * 查找指定标识的WebviewObject窗口 + * + * @description 在已创建的窗口列表中查找指定标识的Webview窗口并返回。 若没有查找到指定标识的窗口则返回null,若存在多个相同标识的Webview窗口,则返回第一个创建的Webview窗口。 如果要获取应用入口页面所属的Webview窗口,其标识为应用的%APPID%,可通过plus.runtime.appid获取。 + */ + getWebviewById: (id: string) => WebviewObject | null; + + /** + * 获取应用首页WebviewObject窗口对象 + */ + getLaunchWebview: () => WebviewObject; +} + +interface RuntimeWrapper { + /** + * 当前应用的APPID + */ + appid: string; + + /** + * 客户端的版本名称 + * + * @description 字符串类型,在编译环境中设置的apk/ipa版本名称。 Android平台对应AndroidManifest.xml中的android:versionName属性值; iOS平台对应Info.plist中的CFBundleShortVersionString字段值。 + */ + version: string; + + /** + * 客户端的版本号 + * + * @description 字符串类型,仅包含数字。 Android平台对应AndroidManifest.xml中的android:versionCode属性值; iOS平台对应Info.plist中CFBundleVersion字段值。 + */ + versionCode: string; + + /** + * 退出应用 + * + * @description 5+APP:退出应用,返回系统桌面; 流应用:在“流应用”中调用则退出当前流应用,返回进入应用前的界面(如应用列表)。 + */ + quit: () => any; + + /** + * 重启当前的应用 + */ + restart: () => any; + + /** + * 设置程序快捷方式图标上显示的角标数字 + */ + setBadgeNumber: (num: number) => any; + + /** + * 调用第三方程序打开指定的URL + */ + openURL: (url: string, err?: (e: DOMException) => any, identity?: string) => any; + + /** + * 使用内置Webview窗口打开URL + */ + openWeb: (url: string) => any; +} + +interface ShareMessage { + /** + * 内容见 https://www.html5plus.org/doc/zh_cn/share.html#plus.share.ShareMessage + */ + type?: 'web' | 'text' | 'image' | 'music' | 'video'; + + /** + * 分享消息的文字内容 + */ + content?: string; + + /** + * 分享消息的图片 + * + * @description 分享消息中包含的图片路径,仅支持本地路径。 若分享平台仅支持提交一张图片,传入多张图片则仅提交第一张图片。 如果未指定type类型,优先级顺序为:pictures>content(即设置了pictures则认为分享图片类型)。 + */ + pictures?: string[]; + + /** + * 分享消息的缩略图 + * + * @description 分享消息中包含的缩略图路径,支持本地路径及网络路径。 若分享平台仅支持提交一张图片,传入多张图片则仅提交第一张图片。 如果分享平台的信息不支持缩略图,若没有设置消息的图片(pictures)则使用缩略图,否则忽略其属性值。 注意:图片有大小限制,推荐图片小于20Kb。 + */ + thumbs?: string[]; + + /** + * 分享的多媒体资源 + * + * @description 分享的多媒体资源地址,当type值为"music"、"video"时有效。 注意: 微信分享平台支持音乐、视频类型,仅支持网络地址(以http://或https://开头); QQ分享平台支持音乐类型,仅支持网络路径(以http://或https://开头); 新浪微博分享平台支持视频类型,仅支持本地文件路径。 + */ + media?: string; + + /** + * 分享独立的链接 + * + * @description 分享资源地址,仅支持网络地址(以http://或https://开头)。 如果未指定type类型,优先级顺序为:href>pictures>content(即设置了href则认为分享网页类型)。 + */ + href?: string; + + /** + * 分享消息的标题 + * + * @description 仅微信分享网页、音频、视频、小程序类型时支持。 + */ + title?: string; +} + +interface ShareWrapper { + /** + * 使用系统组件发送分享 + */ + sendWithSystem: (message: ShareMessage, successCB: () => any, errorCB: (err: DOMException) => any) => any; +} + +type AppKeyType = + | 'backbutton'; + +interface AppKeyEvent { + keyCode: number; + keyType: AppKeyType; +} + +interface KeyWrapper { + /** + * 添加按键事件监听器 + */ + addEventListener: (event: AppKeyType, listener: (event: AppKeyEvent) => any) => any; + + /** + * 移除按键事件监听器 + */ + removeEventListener: (event: AppKeyType, listener: (event: AppKeyEvent) => any) => any; +} + +interface H5Plus { + barcode: BarcodeWrapper; + + webview: WebviewWrapper; + + runtime: RuntimeWrapper; + + share: ShareWrapper; + + net: { XMLHttpRequest: XMLHttpRequest }; + + key: KeyWrapper; +} + interface Window { g_app: any; + + plus: H5Plus; } diff --git a/src/utils/h5plus.ts b/src/utils/h5plus.ts new file mode 100644 index 0000000..accc895 --- /dev/null +++ b/src/utils/h5plus.ts @@ -0,0 +1,75 @@ +import { dispatch, getState } from './utils'; +import { routerRedux } from 'dva'; + +const plus = window.plus; + +const isApp = () => typeof plus !== 'undefined'; + +const barcodeId = 'barcode' +const scan = () => { + if (!isApp()) return Promise.resolve(''); + return new Promise(resolve => { + const { barcode, webview } = plus; + const bc = barcode.create(barcodeId, [barcode.QR], { + position: 'absolute', + left: '0px', + top: '100px', + width: '100%', + height: '500px', + }); + bc.onmarked = (_, code) => { + resolve(code); + bc.close(); + }; + bc.onerror = () => { + resolve(''); + bc.close(); + } + webview.currentWebview().append(bc); + bc.start(); + }); +} + +const firstPaths = [ + '/check', + '/home/estate', + '/home/fund', + '/home/market', + '/home/profile', +]; +const backButtonHandler = () => { + const barcode = plus.barcode.getBarcodeById(barcodeId); + if (barcode) { + barcode.close(); + return; + } + const { pathname } = getState().router.location; + if (firstPaths.includes(pathname)) { + plus.runtime.quit(); + } else { + dispatch(routerRedux.goBack()); + } +}; +const init = () => { + if (!isApp()) return; + plus.key.addEventListener('backbutton', backButtonHandler); +} + +const destory = () => { + if (!isApp()) return; + plus.key.removeEventListener('backbutton', backButtonHandler); +} + +export { + init, + isApp, + scan, + destory, +}; + +export default { + init, + isApp, + scan, + destory, +}; diff --git a/src/utils/http.ts b/src/utils/http.ts index 9620001..95a953d 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -20,7 +20,7 @@ const errorMap = { }; const baseURL = 'https://server.fiv.tacever.org/v1'; -// const baseURL = '//yapi.faronear.org/mock/20/v1'; +// const baseURL = 'http://yapi.faronear.org/mock/20/v1'; const instance = axios.create({ baseURL, }); @@ -57,7 +57,8 @@ async function filter( } else { instance.defaults.headers = _.omit(instance.defaults.headers, 'token'); } - const { status, data } = await (instance[method] as (...args: any[]) => Promise>)(...args); + const { status, data } = + await (instance[method] as (...args: any[]) => Promise>)(...args); replaceId(data); ret = { ok: status >= 200 && status < 300 && data && data.code === 0, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 802e6ee..848d98f 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -10,6 +10,8 @@ interface Action { const dispatch = (action: Action) => window.g_app._store.dispatch(action); -export { isDev, dispatch }; +const getState = () => window.g_app._store.getState(); -export default { isDev, dispatch }; +export { isDev, dispatch, getState }; + +export default { isDev, dispatch, getState };