12306小卡片-鸿蒙智慧出行

在没有鸿蒙之前,我大喵相信很多人座高铁是这样的:
1.买了高铁票,打车去高铁站
2.进入高铁站大门然后安检
3.在候车室等车和等检票
4.到了站台等高铁
5.上车
我大喵怕乘错车和晚上车经常会在3,4步骤的时候打开12306,然后打开车票,查看车票信息和发车时间,保证我是准点去检票座车,并且没有乘错车。像我这种经常去出差的人,一到高铁站会一直重复的去打开12306去查看车票信息,我觉得是一件非常非常麻烦的事情。还有现在的12306不会把车票信息以短信的方式发给我们,情景智能都不能用。那如何解决这个问题呢,答案就是鸿蒙小卡片。

下面我们来看一个视频

https://harmonyos.51cto.com/show/7339

https://www.bilibili.com/video/BV1Cb4y1k78E/

12306小卡片效果展示

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

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

12306小卡片-鸿蒙智慧出行12306小卡片开发准备

1.创建工程

详见鸿蒙官网

2.工程中添加小卡片

详见鸿蒙官网

3.目录结构

12306小卡片-鸿蒙智慧出行

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

<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|b

2.获取今日日期

3.获取所有输入框和选择器的数值

4.点击查询按钮时

? 1.提取train_station_code

? 2.获取该站台所有列车数据?

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

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

结束

?

?

?

上一篇:LAMP和LNMP环境搭建的艰辛历程


下一篇:C#堆和栈的区别