如何在鸿蒙系统弄一个彩票查询卡片

描述

接触鸿蒙开发已经有 3 个来月了,最近开始在看鸿蒙卡片开发。因为之前的开发大都是基于 Java UI,但按官方的说法,JS 卡片相比 Java 卡片有更大的优势,故决定写个 JS 卡片的 demo 来练练手。

碰巧,前几天和媳妇儿在散步时捡到 1 元钱,没能交给警察叔叔,媳妇儿就提议“我们把它昧了吧,买张彩票。”

由于不是老 CM,没有关注开奖的习惯,想着要是能把开奖结果放在手机桌面显示就好了,这样就不会错过我一夜暴富的机会了。有了需求就开撸,然后就有了这篇文章!

项目简介

①本项目基于 API 6 开发,demo 运行在 API 6 以下的手机上会出现部分功能不能使用的现象。

②卡片功能上实现了***、福彩 3D 的最近一期开奖结果查询,点击卡片“刷新”按钮,调用接口更新最新数据;点击“查看更多”按钮,跳转至应用主界面。

③应用主界面上实现了***、福彩 3D 近 50 期开奖结果查看。

④以上数据均使用了聚合数据的 https://www.juhe.cn/docs/api/id/300 免费接口。

需申请 key,一个 key 一天可免费调用 100 次,如遇 key使 用次数过多导致接口请求失败情况时,开发者可自行申请 key 并替换 Constants.java 文件下的 JH_KEY 常量值),数据可能会有延迟。

⑤卡片开发部分使用了卡片的 JS UI 框架,但由于系统 PA 与 FA 相互调用的限制问题,卡片的业务逻辑部分仍然采用 Java 代码编写。

PS:最开始的想法是尽量可能的少依赖 Java 代码,故尝试了 JS FA 与 Java PA 相互调用的方式,但当应用进程被干掉时,Java 端无法再调用到 JS 端方法。

这样就导致 JS 只能写 UI 部分,业务逻辑还得 Java 层实现。有知道解决办法的也麻烦告知一声。

⑥卡片业务层使用 Java 开发,采用了简单的 MVP 架构,网络请求和数据处理部分使用了 rxjava3+retrofit 框架。

⑦应用主界面使用了 JS-UI 框架实现。

实现效果

请忽略我粗陋的 UI 设计和 GIF 的渣渣像素。

项目代码结构分析

base:

IBasePresenter:MVP 架构中 presenter 基类接口

IBaseView:MVP 架构中 view 基类接口

network:

bean,LotteryBean:彩票详情接口返回对应 model

CachedLotteryDetailUtil:彩票详情接口请求工具类,主要作用是防止重复调用详情接口

LogInterceptor:OKhttp 日志拦截工具类

LotteryAPI:接口请求类,通过 retrofit 注解,将接口返回数据转化为实体类

Services:配置 Retrofit 并提供

presenter:

IMainContract:MVP 中 view 与 presenter 的桥梁

MainPresenter:提供彩票详情接口的请求,并处理接口返回数据为卡片需要的 ZSONObject 对象

utils:

LogUtil:日志打印工具类

widget:

controller:FormController:创建卡片时自动生成,卡片管理器的抽象基类;FormControllerManager:创建卡片时自动生成,管理各个 FormController 的工具类

dltwidget.DltWidgetImpl:大乐透卡片管理类,提供了创建卡片、更新卡片、删除卡片、卡片点击事件等行为的回调方法

fcsdwidget.FcsdWidgetImpl:福彩 3D 卡片管理类,提供了创建卡片、更新卡片、删除卡片、卡片点击事件等行为的回调方法

ssqwidget.SsqWidgetImpl:双色球卡片管理类,提供了创建卡片、更新卡片、删除卡片、卡片点击事件等行为的回调方法

Constants:常量工具类。

MainAbility:HAP 的入口 ability,由 DevEco Studio 自动生成。同时也是各个卡片对应的 Ability,用来项各个 FormController 分发事件。

default:

common,component/lottery:应用首页列表 item 组件;images :资源图片

pages,home:应用首页

dlt_widget:

common:资源图片存放目录

pages/index,index.css :大乐透卡片 css 样式;index.hml:大乐透卡片布局文件;index.json:包含页面默认值

fcsd_widget:目录结构同 dlt_widget。

ssq_widget:目录结构同 dlt_widget。

详细实现过程

①创建双色球卡片

在目录 entry 上点击右键,在弹出的菜单中选择 New,然后在弹出的子菜单中点击 Service Widget,如下图所示:

在模板选择界面,选择基本的模板 Grid Pattern,点击按钮 Next,进入到卡片配置界面:

首先配置卡片的名称和描述;然后配置卡片关联的 Page Ability;然后配置卡片的编程语言类型是 JS;接下来配置卡片的 JS 组件名称;最后配置卡片支持的规格,勾选支持 2x4、4x4 规格。

重复上述步骤,创建出大乐透和双色球卡片。运行项目,长按图标打开卡片管理界面,我们能看到刚创建的 3 类卡片,且每类卡片对应 3 种不同样式,如下图所示:

②绘制双色球卡片 UI

我们编写双色球界面:

《!--index.hml--》《div class=“container”》

《div》

《text class=“text”》双色球《/text》

《text class=“text-small” style=“margin-left : 15px;” if=“{{ cardType2x4 || cardType4x4 }}”》每周二、四、日开奖《/text》

《text class=“text” style=“margin-left : 75px;” if=“{{ cardType2x4 || cardType4x4 }}” onclick=“showMore”》 查看更多

《/text》

《/div》

《div style=“margin-top : 10px; align-content : center;”》

《text class=“text-small” style=“margin-right : 10px;”》第{{ lotteryData.lottery_no }}期《/text》

《text class=“text-small” if=“{{ cardType2x4 || cardType4x4 }}”》 开奖日期:《/text》

《text class=“text-small”》 {{ lotteryData.lottery_date }}《/text》

《/div》

《div style=“flex-wrap : wrap; margin-top : 10px;”》

《text class=“ball” for=“{{ lotteryRed }}” tid=“id”》{{ $item }}《/text》

《text class=“ball” style=“background-color : blue;” for=“{{ lotteryBlue }}” tid=“id”》{{ $item }}《/text》

《/div》

《div class=“amount-box” if=“{{ cardType2x4 || cardType4x4 }}”》

《div style=“flex-direction : column;”》

《text class=“text-small”》本期全国销量《/text》

《text class=“text-amount”》{{ lotteryData.lottery_sale_amount }}《/text》

《/div》

《text class=“diver”》《/text》

《div style=“flex-direction : column;”》

《text class=“text-small”》累计奖池《/text》

《text class=“text-amount”》{{ lotteryData.lottery_pool_amount }}《/text》

《/div》

《/div》

《div style=“flex-direction : column; padding-top : 10px; margin-bottom: 20px;” if=“{{ cardType4x4 }}”》

《div style=“background-color : green;”》

《text class=“text-prize”》奖项《/text》

《text class=“text-prize”》中奖条件《/text》

《text class=“text-prize”》中奖注数《/text》

《text class=“text-prize”》单注金额(元)《/text》

《/div》

《div for=“{{ lottery_prize }}”》

《text class=“text-prize”》{{ $item.prize_name }}《/text》

《text class=“text-prize”》{{ $item.prize_require }}《/text》

《text class=“text-prize”》{{ $item.prize_num }}《/text》

《text class=“text-prize”》{{ $item.prize_amount }}《/text》

《/div》

《/div》

《div》

《text class=“text-small” if=“{{ cardType2x4 || cardType4x4 }}”》更新时间:《/text》

《text class=“text-small”》{{ updateTime }}《/text》

《image class=“refresh” src=“/common/image_1.png” onclick=“updateData”》《/image》

《/div》《/div》

《!--index.json--》

{

“data”: {

“cardType2x2”: true,

“cardType2x4”: false,

“cardType4x4”: false,

“lotteryData”: {

“lottery_no”: “21081”,

“lottery_date”: “2021-07-20”,

“lottery_sale_amount”:“344,437,194”,

“lottery_pool_amount”:“997,378,346”

},

“lotteryRed”:[“01”,“03”,“05”,“18”,“22”,“23”],

“lotteryBlue”:[“01”],

“updateTime”: “2021/07/07 1459”,

“lottery_prize”:[

{

“prize_name”:“一等奖”,

“prize_num”:“4”,

“prize_amount”:“10,000,000”,

“prize_require”:“6+1”

},

{

“prize_name”:“二等奖”,

“prize_num”:“135”,

“prize_amount”:“207,725”,

“prize_require”:“6+0”

},

{

“prize_name”:“三等奖”,

“prize_num”:“879”,

“prize_amount”:“3,000”,

“prize_require”:“5+1”

},

{

“prize_name”:“四等奖”,

“prize_num”:“45659”,

“prize_amount”:“200”,

“prize_require”:“5+0,4+1”

},

{

“prize_name”:“五等奖”,

“prize_num”:“1001881”,

“prize_amount”:“10”,

“prize_require”:“4+0,3+1”

},

{

“prize_name”:“六等奖”,

“prize_num”:“6962930”,

“prize_amount”:“5”,

“prize_require”:“2+1,1+1,0+1”

}

},

“actions”: {

}

}

《!--index.css--》

.container {

width: 100%;

height: 100%;

padding: 10px;

flex-direction: column;

}

.text {

font-size: 15px;

}

.text-small {

font-size: 11px;

}

.text-amount {

font-size: 14px;

font-weight: bold;

color: darkred;

}

.text-prize {

font-size: 11px;

flex-weight: 25;

min-height: 20px;

text-align: center;

}

.ball {

width: 20px;

height: 20px;

text-align: center;

font-size: 12px;

margin: 4px;

color: #FFFFFF;

border-radius: 20px;

background-color: red;

}

.amount-box {

flex-direction: row;

align-items: center;

height: 30px;

}

.diver {

width: 1px;

height: 20px;

background-color: red;

margin-left: 15px;

margin-right: 15px;

}

.refresh {

width: 22px;

height: 22px;

}

编写完成后,重新运行,不出意外你应该能看到如下效果:

这里提下卡片 JS-UI 框架的坑:

框架提供了原子布局来控制元素在不同尺寸布局上的隐藏和展示,但怎么说呢 ,一句话概括就是:你以为的并不是你以为的。

可以看到我这里放弃了 display-index 的使用,而采用通过 JAVA 端卡片的类型,来适配不同的 UI。

不同于应用开发中的 JS UI 框架,这里的条件渲染不支持表达式。

css 不支持标签选择器。

部分 css 样式不能被继承,例如给父 div 元素设置了 font-size,你会发现 div 中的 text 组件并没继承上述样式。

列表渲染的 for 循环的数组必须是 index.json data 对象的最外层。

不支持双层 for 循环。

api6 不兼容 api5 的设备。

你会发现卡片 JSON 文件中的 data 对象数据结构同接口返回的数据结构有些差异,就是因为上述原因导致的。

③获取网络数据

简单的 MVP 架构,采用 Retrofit+RxJava 作为异步网络请求框架。

build.gradle 中添加 RxJava 和 Retrofit 的依赖:

// RxJava

api ‘io.reactivex.rxjava33.0.3’

implementation ‘io.openharmony.tpc.thirdlib1.0.0’// retrofit

implementation ‘com.squareup.retrofit22.7.0’

implementation “com.squareup.retrofit22.1.0”

implementation ‘com.squareup.retrofit22.9.0’

config.json 文件中添加网络权限:

// config.json“deviceConfig”: {

“default”: {

“network”: {

“cleartextTraffic”: true,

“securityConfig”: {

“domainSettings”: {

“cleartextPermitted”: true,

“domains”: [

{

“subdomains”: true,

“name”: “apis.juhe.cn”

},

{

“subdomains”: true,

“name”: “v.juhe.cn”

}

}

}

}

}

},

“module”: {

。..

“reqPermissions”: [

{

“name”: “ohos.permission.GET_NETWORK_INFO”

},

{

“name”: “ohos.permission.SET_NETWORK_INFO”

},

{

“name”: “ohos.permission.INTERNET”

}

],

。..

}

创建接口请求:

// LotteryAPI.javapublic interface LotteryAPI {

@POST(“/lottery/query”)

@FormUrlEncoded

Observable《LotteryBean》 queryDetail(@Field(“lottery_id”) String lottery_id, @Field(“lottery_no”) String lottery_no, @Field(“key”) String key);

}

P 层和 V 层的接口比较简单,不在罗列。编写 P 层业务逻辑:

//MainPresenter.java@Overridepublic void loadLotteryData(long formId, String lotteryId) {

CachedLotteryDetailUtil.getLotteryDetail(lotteryId)

.flatMap((Function《LotteryBean, ObservableSource《ZSONObject》》) res -》 {

if (0 != res.getError_code()) {

Throwable throwable = new Throwable(res.getReason());

return Observable.error(throwable);

}

ZSONObject zsonObject = buildDataByResult(res);

return Observable.just(zsonObject);

})

.subscribeOn(Schedulers.io())

.observeOn(OpenHarmonySchedulers.mainThread())

.subscribe(new Observer《ZSONObject》() {

@Override

public void onSubscribe(@NonNull Disposable d) {

}

@Override

public void onNext(@NonNull ZSONObject result) {

if (mView != null) {

mView.onLoadDataSuccess(formId, result);

}

}

@Override

public void onError(@NonNull Throwable e) {

if (mView != null) {

mView.onLoadDataFailed(e);

}

}

@Override

public void onComplete() {

}

});

}

/**

* 根据接口返回的数据,组装成JS卡片需要的数据

*

* @param result

* @return

*/private ZSONObject buildDataByResult(LotteryBean result) {

LotteryBean.DetailBean detailBean = result.getResult();

ZSONObject data = new ZSONObject();

ZSONObject lotteryData = new ZSONObject();

lotteryData.put(“lottery_no”, detailBean.getLottery_no());

lotteryData.put(“lottery_date”, detailBean.getLottery_date());

lotteryData.put(“lottery_sale_amount”, detailBean.getLottery_sale_amount());

lotteryData.put(“lottery_pool_amount”, detailBean.getLottery_pool_amount());

data.put(“lotteryData”, lotteryData);

String[] ballArray = detailBean.getLottery_res().split(“,”);

if (“ssq”.equalsIgnoreCase(detailBean.getLottery_id())){

data.put(“lotteryRed”, Arrays.copyOfRange(ballArray, 0, 6));

data.put(“lotteryBlue”, Arrays.copyOfRange(ballArray, 6, 7));

} else if (“dlt”.equalsIgnoreCase(detailBean.getLottery_id())){

data.put(“lotteryRed”, Arrays.copyOfRange(ballArray, 0, 5));

data.put(“lotteryBlue”, Arrays.copyOfRange(ballArray, 5, 7));

} else if (“fcsd”.equalsIgnoreCase(detailBean.getLottery_id())){

data.put(“lotteryRed”, ballArray);

}

data.put(“updateTime”, new SimpleDateFormat(“yyyy-MM-dd HHss”).format(new Date()));

ZSONArray zsonArray = new ZSONArray();

for (LotteryBean.PrizeBean prizeBean : detailBean.getLottery_prize()) {

ZSONObject prize = new ZSONObject();

prize.put(“prize_name”, prizeBean.getPrize_name());

prize.put(“prize_num”, prizeBean.getPrize_num());

prize.put(“prize_amount”, prizeBean.getPrize_amount());

prize.put(“prize_require”, prizeBean.getPrize_require());

zsonArray.add(prize);

}

data.put(“lottery_prize”, zsonArray);

return data;

}

④V 层调用 P 层,更新卡片

这里提下 MainAbility 的 onCreateForm 方法,它在进入卡片管理界面时被调用,内部通过 FormControllerManager 获取了卡片对应的 FormController,并调用了其 bindFormData() 方法。

由于我们卡片展示的数据来源于网络请求,在对应的 FormController 实现类中不太好获得 formId,所以我们稍微改造一下 FormController 的 bindFormData 方法,把 formId 给传进去。

public abstract ProviderFormInfo bindFormData(long formId);

//MainAbility.javapublic class MainAbility extends AceAbility implements IMainContract.View {

private IMainContract.Presenter mPresenter;

@Override

public void onStart(Intent intent) {

。..

}

@Override

public void onStop() {

super.onStop();

}

@Override

protected ProviderFormInfo onCreateForm(Intent intent) {

HiLog.info(TAG, “onCreateForm”);

long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);

String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);

int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);

HiLog.info(TAG, “onCreateForm: formId=” + formId + “,formName=” + formName);

FormControllerManager formControllerManager = FormControllerManager.getInstance(this);

FormController formController = formControllerManager.getController(formId);

formController = (formController == null) ? formControllerManager.createFormController(formId,

formName, dimension) : formController;

if (formController == null) {

HiLog.error(TAG, “Get null controller. formId: ” + formId + “, formName: ” + formName);

return null;

}

return formController.bindFormData(formId);

}

@Override

protected void onUpdateForm(long formId) {

。..

}

@Override

protected void onDeleteForm(long formId) {

。..

}

@Override

protected void onTriggerFormEvent(long formId, String message) {

。..

}

@Override

public void onNewIntent(Intent intent) {

。..

}

private boolean intentFromWidget(Intent intent) {

。..

}

private String getRoutePageSlice(Intent intent) {

。..

}

private IMainContract.Presenter getPresenter() {

if (mPresenter == null) {

mPresenter = new MainPresenter(this);

}

return mPresenter;

}

/**

* 查询彩票开奖详情

*

* @param formId

* @param lotteryId

*/

public void loadData(long formId, String lotteryId) {

getPresenter().loadLotteryData(formId, lotteryId);

}

@Override

public void onLoadDataSuccess(long formId, ZSONObject data) {

try {

// 更新卡片

updateForm(formId, new FormBindingData(data));

} catch (FormException e) {

e.printStackTrace();

}

}

@Override

public void onLoadDataFailed(Throwable exception) {

。..

}

}

//SsqWidgetImpl.javapublic class SsqWidgetImpl extends FormController{

private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, SsqWidgetImpl.class.getName());

public SsqWidgetImpl(Context context, String formName, Integer dimension) {

super(context, formName, dimension);

}

@Override

public ProviderFormInfo bindFormData(long formId) {

HiLog.info(TAG, “===== ssq bind form data”);

ZSONObject zsonObject = new ZSONObject();

ProviderFormInfo providerFormInfo = new ProviderFormInfo();

boolean is2x2 = dimension == DEFAULT_DIMENSION_2X2;

boolean is2x4 = dimension == DIMENSION_2X4;

boolean is4x4 = dimension == DIMENSION_4X4;

zsonObject.put(“cardType2x2”, is2x2);

zsonObject.put(“cardType2x4”, is2x4);

zsonObject.put(“cardType4x4”, is4x4);

providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));

// 调用接口更新卡片数据

((MainAbility)context).loadData(formId,Constants.LOTTERY_ID_SSQ);

return providerFormInfo;

}

@Override

public void updateFormData(long formId, Object.。. vars) {

}

@Override

public void onTriggerFormEvent(long formId, String message) {

}

@Override

public Class《? extends AbilitySlice》 getRoutePageSlice(Intent intent) {

HiLog.info(TAG, “get the default page to route when you click card.”);

return null;

}

}

⑤简单的网络优化

由于 onCreateForm(Intent intent)方法会被调用多次,而每个类型的卡片请求的数据一样,聚合 api 一天 100 次免费请求的次数一会儿就用完了,故对此进行一个简单的优化。

优先看内存缓存中是否有,有且为过期(缓存默认 10 分钟有效期)则直接返回,否则阻塞等待接口请求完成,对于并发请求,若请求队列已有相同的请求,则阻塞,否则创建新的请求。

若不关心接口调用的可略过本节:

// CachedLotteryDetailUtil.java/**

* CachedLotteryDetailUtil

* 缓存彩票详情工具类,防止在卡片创建时,重复调用接口

*

* @author:xwg

* @since 2021-07-30

*/public class CachedLotteryDetailUtil {

//缓存的有效时间 默认10分钟

private static final long CACHE_TIME = 10 * 60 * 1000;

// 缓存的彩票结果

private static volatile ConcurrentHashMap《String, LotteryBean》 cacheResMap = new ConcurrentHashMap《》();

//缓存的请求时间

private static volatile ConcurrentHashMap《String, Long》 reqTimeMap = new ConcurrentHashMap《》();

//正在请求 对应的lotteryId

private static volatile List《String》 requestingLotteryId;

/**

* 获取彩票详情

*

* @param lotteryId

* @return

*/

public static Observable《LotteryBean》 getLotteryDetail(String lotteryId) {

if (cacheResMap.get(lotteryId) != null) {

if (!isExpired(lotteryId)) {

return Observable.create(emitter -》 {

if (!emitter.isDisposed()) {

emitter.onNext(cacheResMap.get(lotteryId));

}

});

} else {

cacheResMap.remove(lotteryId);

}

}

if (isRequesting(lotteryId)) {

return waitToRequestEnd(lotteryId);

} else {

return requestDetail(lotteryId);

}

}

/**

* 是否正在请求

*

* @param lotteryId

* @return

*/

private static boolean isRequesting(String lotteryId) {

if (requestingLotteryId == null || lotteryId.isEmpty()) {

return false;

}

return requestingLotteryId.contains(lotteryId);

}

/**

* 阻塞等待请求结果

*

* @param lotteryId

* @return

*/

private static Observable《LotteryBean》 waitToRequestEnd(final String lotteryId) {

return Observable.create(emitter -》 {

while (isRequesting(lotteryId)) {

try {

Thread.sleep(10);

} catch (InterruptedException e) {

}

}

if (!emitter.isDisposed()) {

try {

if (cacheResMap != null && cacheResMap.get(lotteryId) != null && !isExpired(lotteryId)) {

LotteryBean lotteryBean = cacheResMap.get(lotteryId);

emitter.onNext(lotteryBean);

if (!emitter.isDisposed()) {

emitter.onComplete();

}

} else {

Throwable throwable = new Throwable(“请求异常,请稍后再试”);

emitter.onError(throwable);

}

} catch (Exception e) {

emitter.onError(e);

}

}

});

}

/**

* 联网请求彩票详情

*

* @param lotteryId

* @return

*/

private static Observable《LotteryBean》 requestDetail(final String lotteryId) {

if (requestingLotteryId == null) {

requestingLotteryId = new LinkedList《》();

}

if (!lotteryId.isEmpty() && !requestingLotteryId.contains(lotteryId)) {

requestingLotteryId.add(lotteryId);

}

return Services.createAPI(LotteryAPI.class).queryDetail(lotteryId, “”, Constants.JH_KEY)

.doOnError(throwable -》 {

requestingLotteryId.remove(lotteryId);

reqTimeMap.remove(lotteryId);

})

.doAfterNext(lotteryBean -》 {

requestingLotteryId.remove(lotteryId);

cacheResMap.put(lotteryId, lotteryBean);

reqTimeMap.put(lotteryId, System.currentTimeMillis());

});

}

/**

* 缓存是否已经过期

*

* @return

*/

private static boolean isExpired(String lotteryId) {

if (reqTimeMap == null || reqTimeMap.get(lotteryId) == null) {

return true;

}

return System.currentTimeMillis() - reqTimeMap.get(lotteryId) 》 CACHE_TIME;

}

}

⑥给卡片增加刷新事件和查看更多事件

给卡片的 index.json 文件添加 actions,定义 showMore 为 router 事件,触发这个事件会跳转到指定的 abilityName 对应的 Ability。

updateData 为 message 事件,触发该事件,会回调 Ability 中的 onTriggerFormEvent(long formId, String message)方法。

// index.json

{

“data”: {

。..

},

“actions”: {

“showMore”: {

“action”: “router”,

“abilityName”: “com.xwg.lotteryquery.MainAbility”,

“params”: {

“message”: “fcsd”

}

},

“updateData”: {

“action”: “message”,

“params”: {

“key”: “fcsd”

}

}

}

}

卡片的布局文件中添加点击事件:

《!--跳转应用首页事件--》《text class=“text” style=“margin-left : 120px;” if=“{{ cardType2x4 || cardType4x4 }}” onclick=“showMore”》 查看更多

《/text》

《!--点击刷新按钮事件--》《image class=“refresh” src=“/common/refresh.png” onclick=“updateData”》《/image》

// SsqWidgetImpl.java@Overridepublic void onTriggerFormEvent(long formId, String message) {

HiLog.info(TAG, “======== ssq handle card click event.”);

((MainAbility)context).loadData(formId,Constants.LOTTERY_ID_SSQ);

}

添加定时刷新:打开 config.json,对于标签“scheduledUpdateTime”设定的时刻,当到达之后,MainAbility 中卡片的回调方法 onUpdateForm() 就会被自动调用。

updateDuration 默认为 1,下面配置表示:双色球卡片允许定时刷新,从 10:30 开始,每隔半小时刷新一次。

// config.json“forms”: [

{

“jsComponentName”: “ssq_widget”,

“isDefault”: true,

“scheduledUpdateTime”: “10:30”,

“defaultDimension”: “2*2”,

“name”: “SsqWidget”,

“description”: “双色球”,

“colorMode”: “auto”,

“type”: “JS”,

“supportDimensions”: [

“2*2”,

“2*4”,

“4*4”

],

“updateEnabled”: true,

“updateDuration”: 1

}

。..

FormController 直接调用 MainAbility 的获取数据方法:

// SsqWidgetImpl.java@Overridepublic void updateFormData(long formId, Object.。. vars) {

HiLog.info(TAG, “======== ssq update form data timing, default 30 minutes”);

((MainAbility)context).loadData(formId,Constants.LOTTERY_ID_SSQ);

}

⑦大乐透和福彩 3D 卡片的实现

这两个卡片的实现过程和双色球卡片基本一致,主要是 UI 上有些区别。大乐透卡片 4X4 样式中,由于中奖信息列表较长,引入了 list 和 list-item 组件,让其可在卡片内上下滚动,具体实现此处不再赘述,有兴趣可阅读源码。

⑧历史开奖列表的实现

这个界面也相对简单,使用了 swiper 组件,左右滑动或点击头部标签栏,完成***、福彩 3D 标签页的切换,每个标签页展示对应的近 50 期开奖结果,使用 list 和 list-item 组件渲染。

由于各列表 item 展示 UI 相近,故将其抽成了组件放置于 */common/component/lottery* 目录下。

lottery 子组件部分,lottery.js:通过 props 接收外部传入的 lotteryData 数据。

// lottery.jsexport default {

props: {

lotteryData: {}

}

}

lottery.hml 组件布局代码:

《!--lottery.hml--》《div class=“item”》

《div class=“item-header”》

《text style=“font-weight : bold;”》第{{ lotteryData.lottery_no }}期《/text》

《text class=“remarks”》开奖日期:{{ lotteryData.lottery_date }}《/text》

《/div》

《div style=“margin-left : 10px;”》

《text for=“{{ lotteryData.ballList }}”

class=“ball”

style=“background-color : {{ $idx 》= lotteryData.redBallCount ? ‘#6666FF’ : ‘#FF0033’ }};”

》{{ $item }}《/text》

《/div》

《div class=“amount-box”》

《div style=“flex-direction : column;”》

《text class=“text-small”》本期全国销量《/text》

《text class=“text-amount”》{{ lotteryData.lottery_sale_amount }}《/text》

《/div》

《text class=“diver”》《/text》

《div style=“flex-direction : column;”》

《text class=“text-small”》累计奖池《/text》

《text class=“text-amount”》{{ lotteryData.lottery_pool_amount || ‘0’ }}《/text》

《/div》

《/div》《/div》

css 比较简单,这里不再给出。

父组件实现:

《!--home.hml--》《element name=‘lottery’ src=‘。./。./common/component/lottery/lottery.hml’》《/element》《div class=“container”》

《div class=“title-container”》

《text class=“title {{ currentIdx == 0 ? tabSelected : tabUnSelected }}” onclick=“onTabClick(0)”》双色球《/text》

《text class=“title {{ currentIdx == 1 ? tabSelected : tabUnSelected }}” onclick=“onTabClick(1)”》大乐透《/text》

《text class=“title {{ currentIdx == 2 ? tabSelected : tabUnSelected }}” onclick=“onTabClick(2)”》福彩3D《/text》

《/div》

《swiper class=“swiper” id=“swiper” index=“0” indicator=“false” loop=“true”

digital=“false” on:change=“onChange”》

《div class=“swiperContent”》

《list class=“”》

《list-item for=“{{ ssqResList }}” class=“lottery-item”》

《lottery lottery-data = “{{$item}}”》《/lottery》

《/list-item》

《/list》

《/div》

《div class=“swiperContent”》

《list class=“”》

《list-item for=“{{ dltResList }}” class=“lottery-item”》

《lottery lottery-data = “{{$item}}”》《/lottery》

《/list-item》

《/list》

《/div》

《div class=“swiperContent”》

《list class=“”》

《list-item for=“{{ fcsdResList }}” class=“lottery-item”》

《lottery lottery-data = “{{$item}}”》《/lottery》

《/list-item》

《/list》

《/div》

《/swiper》《/div》

《!--home.js--》

import http from ‘@ohos.net.http’;

const JH_URL = ‘http://apis.juhe.cn’const JH_KEY = ‘4931b786dd99c28e7e9990fb75c39fad’export default {

data: {

currentIdx: 0,

tabSelected: ‘tabSelected’,

tabUnSelected: ‘tabUnSelected’,

ssqResList: null, // 双色球近期开奖结果列表

dltResList: null, // 大乐透近期开奖结果列表

fcsdResList: null, // 3D球近期开奖结果列表

},

onInit() {

this.querySsqHis();

this.queryDltHis();

this.queryFcsdHis();

},

// 查询双色球历史开奖

querySsqHis() {

console.info(‘xwg== querySsqHis ----------’);

let _t = this

let httpRequest = http.createHttp();

let url = JH_URL + ‘/history’ + ‘?lottery_id=ssq&page_size=50&page=&key=’ + JH_KEY

httpRequest.request(url, (err, data) =》 {

if (err == null) {

let lotteryResList = JSON.parse(data.result).result.lotteryResList

_t.ssqResList = lotteryResList.map(item =》 {

item.ballList = item.lottery_res.split(“,”)

item.redBallCount = 6

return item

})

console.info(‘xwg== ssqResList:’ + JSON.stringify(_t.ssqResList));

} else {

console.info(‘xwg== error:’ + JSON.stringify(err));

}

});

},

// 查询大乐透历史开奖

queryDltHis() {

let _t = this

let httpRequest = http.createHttp();

let url = JH_URL + ‘/history’ + ‘?lottery_id=ssq&page_size=50&page=&key=’ + JH_KEY

httpRequest.request(url, (err, data) =》 {

if (err == null) {

let dltRes = JSON.parse(data.result).result.lotteryResList

_t.dltResList = dltRes.map(item =》 {

item.ballList = item.lottery_res.split(“,”)

item.redBallCount = 5

return item

})

} else {

console.info(‘xwg== error:’ + JSON.stringify(err));

}

});

},

// 查询福彩3D历史开奖

queryFcsdHis() {

let _t = this

let httpRequest = http.createHttp();

let url = JH_URL + ‘/history’ + ‘?lottery_id=ssq&page_size=50&page=&key=’ + JH_KEY

httpRequest.request(url, (err, data) =》 {

if (err == null) {

let fcsdRes = JSON.parse(data.result).result.lotteryResList

_t.fcsdResList = fcsdRes.map(item =》 {

item.ballList = item.lottery_res.split(“,”)

item.redBallCount = 3

return item

})

} else {

console.info(‘xwg== error:’ + JSON.stringify(err));

}

});

},

// swiper滑动监听

onChange(value) {

this.currentIdx = value.index

},

// tab click事件

onTabClick(idx) {

this.currentIdx = idx

this.$element(‘swiper’).swipeTo({

index: idx

});

},

computed: {}

}

尚未解决的问题:这里引入了 http 组件进行网络请求,但在请求聚合接口时,失败率很高,但尝试请求别的网站的 api 时没有此现象,目前尚不知原因。

总结

由于这也是我第一次使用 JS UI 框架进行卡片开发的项目,水平有限,难免会对官方部分 API 理解不到位甚至理解有误的地方,希望大家也多多指正,共同进步。

这一路上虽然磕磕巴巴,也有很多吐槽,但我们从卡片 JS-UI API 5 到 API 6 功能上逐渐靠拢应用 JS-UI 上也能看出来鸿蒙的努力,给它点时间,相信它功能上会变得更强大、完善;对于开发也会变得更快捷、简单。

最后附上项目地址:lottery-query,请自行下载资源,欢迎交流学习。

https://gitee.com/chinasoft6_ohos/lottery-query

责任编辑:haq

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分