12306小卡片-鸿蒙智慧出行
想了解更多内容,小卡行请访问:
和华为官方合作共建的片鸿鸿蒙技术社区
https://harmonyos.51cto.com
在没有鸿蒙之前,我大喵相信很多人座高铁是蒙智这样的:
1.买了高铁票,打车去高铁站
2.进入高铁站大门然后安检
3.在候车室等车和等检票
4.到了站台等高铁
5.上车
我大喵怕乘错车和晚上车经常会在3,慧出4步骤的时候打开12306,然后打开车票,小卡行查看车票信息和发车时间,片鸿保证我是蒙智准点去检票座车,并且没有乘错车。慧出像我这种经常去出差的小卡行人,一到高铁站会一直重复的片鸿去打开12306去查看车票信息,我觉得是蒙智一件非常非常麻烦的事情。还有现在的慧出12306不会把车票信息以短信的方式发给我们,情景智能都不能用。小卡行那如何解决这个问题呢,片鸿答案就是蒙智鸿蒙小卡片。
下面我们来看一个视频
https://harmonyos.51cto.com/show/7339
12306小卡片效果展示
可以看到车票的网站模板所有重要信息都展示了出来,这样在出发去高铁站之前,我们只需要长按选择这个12306小卡片,添加到桌面上,然后长按小卡片出现编辑页面
在小卡片编辑页面添加车票相关信息,点击查询后会查询到相关列车信息,然后点就确认就会把数据放到小卡片上去了
12306小卡片开发准备
1.创建工程
详见鸿蒙官网
2.工程中添加小卡片
详见鸿蒙官网
3.目录结构

4.添加12306域名
"deviceConfig": { "default": { "network": { "securityConfig": { "domainSettings": { "cleartextPermitted": true, "domains": [ { "name": "search.12306.cn" }, { "name": "kyfw.12306.cn" } ] } } } } },小卡片页面开发
index.hml
小卡片主要分成两大模块:车票基本信息和车次列表
车票基本信息里面有:开始地址、开始时间、车次号、日期、座位号、结束地址和结束时间
车次列表里面有:到达时间,发车时间,所需时间和正点状态
<div class="main_container"> <!-- 车票基本信息 --> <div class="title_container" @click="getNewData"> <div class="title_div"> <text class="title_address_text">{ { treainData.startAddress}}</text> <text class="title_time_text">{ { treainData.startTime}}</text> </div> <div class="title_div" style="margin-top: 5px;" > <text class="title_ticket_text">{ { treainData.trainDataString}}</text> </div> <div class="title_div"> <text class="title_address_text">{ { treainData.endAddress}}</text> <text class="title_time_text">{ { treainData.endTime}}</text> </div> </div> <!-- 车次列表 --> <div> <list class="station_list"> <list-item class="station_list_item" clickeffect="false" for="{ { stationList}}"> <text class="station_start_time">{ { $item.start_time}}</text> <div class="circle"></div> <text class="station_name_text">{ { $item.station_name}}</text> <text class="station_mess_text grey_color">{ { $item.arrive_time}}</text> <text class="station_mess_text grey_color">{ { $item.running_time}}</text> <text class="station_mess_text grey_color">{ { $item.arrive_day_str}}</text> </list-item> </list> </div> </div>index.css
.main_container{ flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: #FFFFFF; } .title_container{ margin-top: 5px; } .title_time_text{ font-size: 10px; width: 100%; height: 10px; text-align: center; color: darkgray; } .title_address_text{ font-size: 16px; width: 100%; height: 20px; text-align: center; color: #5F80FF; } .title_div{ height: 30px; display: flex; flex-direction: column; } .title_ticket_text{ width: 300px; font-size: 10px; text-align: center; color: #5F80FF; } .teain_d_text{ width: 120px; height: 35px; /* background-image: url("/common/jt.png");*/ background-size: 100% 100%; text-align: center; font-size: 10px; line-height: 15px; } .station_list{ width: 100%; height: 120px; margin-left: 10px; margin-top: 5px; } .station_list_item{ } .station_name_text{ width: 80px; font-size: 12px; padding-left: 5px; border-left-style: solid; border-left-color: #5F80FF; border-left-width: 1px; padding-bottom: 8px; } .grey_color{ color: rgb(80,80,80); } .station_mess_text{ width: 60px; text-align: center; font-size: 10px; } .station_start_time{ font-size: 11px; } .circle{ width: 8px; height: 8px; border-radius: 100px; border: 4px solid #5F80FF; margin-top: 3.5px; left: 4.5px; }index.json
小卡片通过json配置数据绑定和点击事件,车票的基本信息保存在treainData里面,车次列表保存在stationList里面
{ "data": { "treainData": { "startAddress": "", "startTime": "", "endAddress": "", "endTime": "", "trainDataString": "" }, "stationList": [], }, "actions": { "getNewData": { "action": "message", "params": { "message": "getNewData" } } } }小卡片编辑功能的开发准备
鸿蒙的小卡片可以创建很多个,最大好像是8个,每个小卡片肯定是展现不一样的信息,所以需要添加编辑页面来让用户编辑这个小卡片,没有编辑页面的小卡片是服务器托管没有灵魂的。
首先需要新建一个TrainConfigAbility,在里面setInstanceName TrainConfig
package com.example.phone.ability.train; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.content.Intent; import ohos.aafwk.content.IntentParams; import ohos.ace.ability.AceAbility; public class TrainConfigAbility extends AceAbility { public static Long cardId; @Override public void onStart(Intent intent) { setInstanceName("TrainConfig"); IntentParams params = intent.getParams(); cardId = (long) params.getParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY); super.onStart(intent); } @Override public void onStop() { super.onStop(); } }然后在小卡片配置JSON里面添加
"formConfigAbility": "ability://com.example.phone.ability.train.TrainConfigAbility"我的小卡片配置文件时这样的
{ "name": "com.example.phone.ability.train.TrainAbility", "icon": "$media:icon", "description": "$string:widget_trainability_description", "formsEnabled": true, "label": "$string:entry_TrainAbility", "type": "page", "forms": [ { "jsComponentName": "train", "isDefault": true, "scheduledUpdateTime": "10:30", "defaultDimension": "2*4", "name": "Train", "description": "train card", "colorMode": "auto", "type": "JS", "supportDimensions": [ "2*4" ], "updateEnabled": true, "updateDuration": 1, "formConfigAbility": "ability://com.example.phone.ability.train.TrainConfigAbility" } ], "launchType": "singleton" },小卡片数据库
关系型数据库加入包
在对应的entry的build.gradle中添加包
dependencies { implementation fileTree(dir: libs, include: [*.jar, *.har]) testCompile junit:junit:4.12 compile files(ORM_ANNOTATIONS_JAVA, ORM_ANNOTATIONS_PROCESSOR_JAVA, JAVAPOET_JAVA) annotationProcessor files(ORM_ANNOTATIONS_JAVA, ORM_ANNOTATIONS_PROCESSOR_JAVA, JAVAPOET_JAVA) }在gradle.properties中添加gradle全局变量
JAVAPOET_JAVA=C:/Users/XX/AppData/Local/Huawei/Sdk/java/2.1.1.21/build-tools/lib/javapoet_java.jar ORM_ANNOTATIONS_PROCESSOR_JAVA=C:/Users/XX/AppData/Local/Huawei/Sdk/java/2.1.1.21/build-tools/lib/orm_annotations_processor_java.jar ORM_ANNOTATIONS_JAVA=C:/Users/XX/AppData/Local/Huawei/Sdk/java/2.1.1.21/build-tools/lib/orm_annotations_java.jarTrainStore.java
package com.example.phone.store; import com.example.phone.store.from.Train; import ohos.data.orm.OrmDatabase; import ohos.data.orm.annotation.Database; @Database(entities = { Train.class}, version = 1) public abstract class TrainStore extends OrmDatabase { }Train.java
package com.example.phone.store.from; import ohos.data.orm.OrmObject; import ohos.data.orm.annotation.Entity; import ohos.data.orm.annotation.PrimaryKey; import ohos.utils.zson.ZSONArray; @Entity(tableName = "trail") public class Train extends OrmObject { @PrimaryKey(autoGenerate = true) private Long id; private String trainList; public Train() { } public Train(Long id, String trainList) { this.id = id; this.trainList = trainList; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public String toString() { return "Train{ " + "id=" + id + ", trainList=" + trainList + }; } public String getTrainList() { return trainList; } public void setTrainList(String trainList) { this.trainList = trainList; } }小卡片Ability
主要作用时对小卡片的创建删除进行管理,点击事件的处理
在创建小卡片时,在数据库中新建一条小卡片数据,当点击更新按钮时,从数据库里面读取对应卡片的trainNo和date,然后请求API获取数据
"https://kyfw.12306.cn/otn/queryTrainInfo/query?leftTicketDTO.train_no="+trainNo+"&leftTicketDTO.train_date="+date+"&rand_code=";TrainAbility.java
package com.example.phone.ability.train; import com.example.phone.store.OilStore; import com.example.phone.store.TrainStore; import com.example.phone.store.from.OilPrice; import com.example.phone.store.from.Train; import ohos.aafwk.ability.*; import ohos.aafwk.content.Intent; import ohos.app.Context; import ohos.data.DatabaseHelper; import ohos.data.orm.OrmContext; import ohos.data.orm.OrmPredicates; import ohos.hiviewdfx.HiLog; import ohos.hiviewdfx.HiLogLabel; import ohos.utils.zson.ZSONArray; import ohos.utils.zson.ZSONObject; import okhttp3.*; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.List; public class TrainAbility extends Ability { public static final int DEFAULT_DIMENSION_2X2 = 2; public static final int INVALID_FORM_ID = -1; private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, TrainAbility.class.getName()); private static OrmContext ormContext = null; private DatabaseHelper helper = new DatabaseHelper(this); OkHttpClient okHttpClient = new OkHttpClient(); public void create(Context context){ System.out.println("创建高铁数据库"); ormContext = helper.getOrmContext("TrainStore", "TrainStore.db", TrainStore.class); } @Override protected void onStart(Intent intent) { super.onStart(intent); stopAbility(intent); } @Override protected ProviderFormInfo onCreateForm(Intent intent) { 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); if(ormContext == null){ create(getContext()); } // 数据库新建数据 Train train = new Train(); train.setId(formId); boolean isSuccessed = ormContext.insert(train); isSuccessed = ormContext.flush(); return null; } @Override protected void onTriggerFormEvent(long formId, String message) { OrmPredicates predicates = ormContext.where(Train.class); predicates.equalTo("id", formId); List<Train> trailList = ormContext.query(predicates); Train trail = trailList.get(0); ZSONObject trainData = ZSONObject.stringToZSON(trail.getTrainList()); System.out.println(trail); // 查询数据 String date = trainData.getString("chooseDate"); String trainNo = trainData.getString("trainNo"); String url = "https://kyfw.12306.cn/otn/queryTrainInfo/query?leftTicketDTO.train_no="+trainNo+"&leftTicketDTO.train_date="+date+"&rand_code="; System.out.println(url); Call call = okHttpClient.newCall(new Request.Builder().url(url).build()); call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { String body = response.body().string(); ZSONObject data = new ZSONObject(); ZSONArray statinList = ZSONObject.stringToZSON(body).getZSONObject("data").getZSONArray("data"); data.put("stationList",statinList); System.out.println(data); FormBindingData formBindingData = new FormBindingData(data); try { if (!updateForm(formId, formBindingData)) { } } catch (FormException e) { e.printStackTrace(); } } }); super.onTriggerFormEvent(formId, message); } }小卡片编辑页面的开发
index.hml
配置页面CSS代码很少我就不贴了
<div class="container"> <div> <text class="title">车次号</text> <input type="text" maxlength="6" style="width: 300px;" on:change="TrainNumberChange"></input> </div> <div> <text class="title">日期</text> <picker type="date" start="{ { today}}" value="{ { today}}" onchange="DataChange" ></picker> </div> <div> <text class="title">站台</text> <input type="text" maxlength="20" style="width: 300px;" on:change="addressChange"></input> </div> <div> <text class="title">座位类型</text> <picker type="text" range="{ { seatPickerList}}" value="{ { seatValue}}" on:change="seatChange"></picker> </div> <div> <text class="title">车号</text> <input type="number" maxlength="2" style="width: 300px;" on:change="VehicleNumberChange"></input> </div> <div> <text class="title">座位号</text> <input type="text" maxlength="4" style="width: 300px;" on:change="SeatNumberChange" ></input> </div> <div> <text class="title">发车站台</text> <text>{ { trainData.start_station_name}}</text> </div> <div> <text class="title">结束站台</text> <text>{ { trainData.end_station_name}}</text> </div> <div> <text class="title">列车类型</text> <text>{ { trainData.train_class_name}}</text> </div> <div> <text class="title">OCR自动识别信息</text> </div> <button class="qr_but" @click="search">查询</button> <button class="qr_but" @click="qr">确定</button> </div>具体业务逻辑
1.当页面初始化时请求API获取全国站台的缩写代码(编号),因为请求的是JS文件,需要使用|隔开存入数组
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js
var station_names =@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|b2.获取今日日期
3.获取所有输入框和选择器的数值
4.点击查询按钮时
提取train_station_code 获取该站台所有列车数据 "https://kyfw.12306.cn/otn/czxx/query?train_start_date="+that.chooseDate+"&train_station_code="+addressCode5.整理数据发送给service ability
index.js
import fetch from @system.fetch import app from @system.app var that; const ABILITY_TYPE_EXTERNAL = 0; const ACTION_SYNC = 0; const CHOOSE = 1001; var stationNameArray = []; export const TrainAbility = { choose: async function(data){ var action = { }; var sendUserData = data; console.info(sendUserData); action.bundleName = com.example.phone; action.abilityName = com.example.phone.ability.train.TrainServiceAbility; action.messageCode = CHOOSE; action.data = sendUserData; action.abilityType = ABILITY_TYPE_EXTERNAL; action.syncOption = ACTION_SYNC; var result = await FeatureAbility.callAbility(action); } } export default { data: { title: "", today: "2000-01-01", chooseDate:"", seatPickerList:["商务座","一等座","二等座","站票"], seatValue: "二等座", pickerDefineIndex: 0, trainNumber: "", vehicleNumber: "", seatNumber: "", address: "", trainNo:"", trainData:{ } }, onInit() { that = this; //设置今日今天的时间 var nowDay = new Date(); let mount = (nowDay.getMonth()+1); let day = nowDay.getDate(); if(mount < 10) mount = "0"+mount; if(day < 10) day = "0"+day; this.today = nowDay.getFullYear()+"-" + mount + "-" + day; this.chooseDate = this.today; // 请求获取站编号 fetch.fetch({ url: "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js", success: function(response) { var result = response.data; stationNameArray = result.split(|); }, fail: function() { console.info("---get station_name fetch fail---"); } }); }, addressChange(value){ this.address = value.text; }, DataChange(e){ let mount = e.month+1; let day = e.day; if(mount < 10) mount = "0"+mount; if(day < 10) day = "0"+day; this.today = e.year + "-" + mount + "-" + day; }, seatChange(obj){ this.seatValue = this.seatPickerList[obj.newSelected]; }, TrainNumberChange(value){ this.trainNumber = value.text; }, VehicleNumberChange(value){ this.vehicleNumber = value.text; }, SeatNumberChange(value){ this.seatNumber = value.text; }, search(){ // 查询车架号https://kyfw.12306.cn/otn/czxx/query?train_start_date=2021-07-17&train_station_code=CAU // 1.提取train_station_code let index = stationNameArray.indexOf(this.address); if(index == 0){ // 输入错误 return; } let addressCode = stationNameArray[index+1]; // 2.获取该站台所有列车数据 C3863 54000C386601 // fetch.fetch({ url: "https://kyfw.12306.cn/otn/czxx/query?train_start_date="+that.chooseDate+"&train_station_code="+addressCode, success: function(response) { var result = JSON.parse(response.data); if(result.data == null){ return; } // 查询trainNumber let data = result.data.data; for(var dindex in data){ if(data[dindex].station_train_code == that.trainNumber){ that.trainData = data[dindex]; that.trainNo = data[dindex].train_no; break; } } console.info("车数据:"+that.trainNo); }, fail: function() { console.info("https://kyfw.12306.cn/otn/czxx/query fail"); } }); }, qr(){ if(that.trainNo == null || that.trainNo.length == 0){ console.info("数据有误"); return; } // 整理数据 let SendData = { treainData:{ "seat": that.seatValue, "today": that.today, "trainNumber": that.trainNumber, "vehicleNumber": that.vehicleNumber, "seatNumber": that.seatNumber, "trainNo": that.trainNo, "chooseDate": that.chooseDate, "startAddress": that.trainData.start_station_name, "endAddress": that.trainData.end_station_name, "startTime": that.trainData.start_start_time, "endTime": that.trainData.end_arrive_time, "trainDataString": that.today+" "+that.trainNumber+"\n"+that.seatValue+" "+that.vehicleNumber+" "+that.seatNumber } } // 调试打印 console.info(this.trainData.toString()); // 发送数据给后端java service ability TrainAbility.choose(SendData); // 关闭当前页面 app.terminate(); } }编辑页面数据处理
TrainServiceAbility.java
package com.example.phone.ability.train; import com.example.phone.ability.OilConfigAbility; import com.example.phone.store.OilStore; import com.example.phone.store.TrainStore; import com.example.phone.store.from.OilPrice; import com.example.phone.store.from.Train; import com.example.phone.utils.TrainDataTF; import ohos.aafwk.ability.Ability; import ohos.aafwk.ability.FormBindingData; import ohos.aafwk.ability.FormException; import ohos.aafwk.content.Intent; import ohos.data.DatabaseHelper; import ohos.data.orm.OrmContext; import ohos.data.orm.OrmPredicates; import ohos.hiviewdfx.HiLog; import ohos.hiviewdfx.HiLogLabel; import ohos.rpc.*; import ohos.utils.zson.ZSONArray; import ohos.utils.zson.ZSONObject; import java.util.List; public class TrainServiceAbility extends Ability { private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo"); private static final int CHOOSE = 1001; private static OrmContext ormContext = null; private DatabaseHelper helper = new DatabaseHelper(this); private TrainServiceAbility.TrainServiceRemote trainServiceRemote; @Override public void onStart(Intent intent) { HiLog.info(LABEL_LOG, "TrainServiceAbility::onStart"); trainServiceRemote = new TrainServiceAbility.TrainServiceRemote(); ormContext = helper.getOrmContext("TrainStore", "TrainStore.db", TrainStore.class); super.onStart(intent); } @Override protected IRemoteObject onConnect(Intent intent) { super.onConnect(intent); return trainServiceRemote.asObject(); } @Override public void onDisconnect(Intent intent) { } class TrainServiceRemote extends RemoteObject implements IRemoteBroker { public TrainServiceRemote() { super("TrainServiceRemote"); } @Override public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) throws RemoteException { switch (code) { case CHOOSE:{ // 更新数据库 ZSONObject zsonStr = ZSONObject.stringToZSON(data.readString()); System.out.println(zsonStr); OrmPredicates predicates = ormContext.where(Train.class); predicates.equalTo("id", TrainConfigAbility.cardId); List<Train> trailList = ormContext.query(predicates); Train trail = trailList.get(0); trail.setTrainList(zsonStr.getZSONObject("treainData").toString()); ormContext.update(trail); ormContext.flush(); // 更新小卡片 FormBindingData formBindingData = new FormBindingData(zsonStr); try { if (!updateForm(TrainConfigAbility.cardId, formBindingData)) { } } catch (FormException e) { e.printStackTrace(); } break; } default: { reply.writeString("service not defined"); return false; } } return true; } @Override public IRemoteObject asObject() { return this; } } }想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com