Loading...
首页专栏正文

12306鸿蒙小卡片,出差必备神器!

林家大哥 发布于 2021-07-22 14:14:51 浏览 2592 点赞 91 收藏 2

在没有鸿蒙之前,我相信很多人座高铁是这样的:

买了高铁票,打车去高铁站

进入高铁站大门然后安检

在候车室等车和等检票

到了站台等高铁

上车

我怕乘错车和晚上车经常会在 3,4 步骤的时候打开 12306,然后打开车票,查看车票信息和发车时间,保证我是准点去检票座车,并且没有乘错车。

像我这种经常去出差的人,一到高铁站会一直重复的去打开 12306 去查看车票信息,我觉得是一件非常非常麻烦的事情。

还有现在的 12306 不会把车票信息以短信的方式发给我们,情景智能都不能用。那如何解决这个问题呢,答案就是鸿蒙小卡片。

12306 小卡片效果展示

可以看到车票的所有重要信息都展示了出来,这样在出发去高铁站之前,我们只需要长按选择这个 12306 小卡片,添加到桌面上,然后长按小卡片出现编辑页面。

在小卡片编辑页面添加车票相关信息,点击查询后会查询到相关列车信息,然后点就确认就会把数据放到小卡片上去了。

12306 小卡片开发准备

①创建工程

详见鸿蒙官网。

②工程中添加小卡片

详见鸿蒙官网。

③目录结构

④添加 12306 域名

"deviceConfig": { "default": { "network": { "securityConfig": { "domainSettings": { "cleartextPermitted": true, "domains": [ { "name": "search.12306.cn" }, { "name": "kyfw.12306.cn" } ] } } } } },

小卡片页面开发

①index.hml

小卡片主要分成两大模块:车票基本信息和车次列表。

车票基本信息里面有:开始地址、开始时间、车次号、日期、座位号、结束地址和结束时间。

车次列表里面有:到达时间,发车时间,所需时间和正点状态。

{{treainData.startAddress}} {{treainData.startTime}}
{{treainData.trainDataString}}
{{treainData.endAddress}} {{treainData.endTime}}
<!-- 车次列表 -->
<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>

②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": "24", "name": "Train", "description": "train card", "colorMode": "auto", "type": "JS", "supportDimensions": [ "24" ], "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)

} 0 在 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.jar

TrainStore.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 代码很少我就不贴了:

车次号
日期
站台
座位类型
车号
座位号
发车站台 {{trainData.start_station_name}}
结束站台 {{trainData.end_station_name}}
列车类型 {{trainData.train_class_name}}
OCR自动识别信息

具体业务逻辑:

①当页面初始化时请求 API 获取全国站台的缩写代码(编号),因为请求的是 JS 文件,需要使用|隔开存入数组。

https://kyfw.12306.cn/otn/resources/js/framework/station_name.js

var station_names ='@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|b

②获取今日日期。

③获取所有输入框和选择器的数值。

④点击查询按钮时,提取 train_station_code。获取该站台所有列车数据。

"https://kyfw.12306.cn/otn/czxx/query?train_start_date="+that.chooseDate+"&train_station_code="+addressCode

⑤整理数据发送给 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;
    }
}

}

*本文仅代表作者观点,不代表易百纳技术社区立场。系作者授权易百纳技术社区发表,未经许可不得转载。

精彩评论

内容存在敏感词
打赏
打赏作者
林家大哥
您的支持将鼓励我继续创作!
金额:
¥1 ¥5 ¥10 ¥50 ¥100
支付方式:
微信支付
支付宝支付
微信支付
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

易百纳技术社区
确定要删除此文章、专栏、评论吗?
确定
取消
易百纳技术社区