主界面

主界面和商店详情界面参考如下设计图(灵感严重匮乏)

img

简单起见,将整个app分为4个布局,分别是主界面、搜索界面、购物车界面,以及个人界面。

所以在app中也需要使用tab组件进行分割,且需要通过tabBar方法设置底部导航栏

Tabs({
    barPosition: BarPosition.End,
    controller: this.tabsController,
}) {
    TabContent() {
        HomeView()
    }
    .tabBar(this.TabBuilder(
        'Home',
        CommentConstant.HOME_TAB_INDEX,
        CommentConstant.HomeIconActive,
        CommentConstant.HomeIconNormal
    ))
​
    TabContent() {
        SearchView()
    }
    .tabBar(this.TabBuilder(
        'Search',
        CommentConstant.SEARCH_TAB_INDEX,
        CommentConstant.SearchIconActive,
        CommentConstant.SearchIconNormal
    ))
​
    TabContent() {
        CartView()
    }
    .tabBar(this.TabBuilder(
        'Cart',
        CommentConstant.CART_TAB_INDEX,
        CommentConstant.CartIconActive,
        CommentConstant.CartIconNormal
    ))
​
    TabContent() {
        ProfileView()
    }
    .tabBar(this.TabBuilder(
        'Profile',
        CommentConstant.PROFILE_TAB_INDEX,
        CommentConstant.ProfileIconActive,
        CommentConstant.ProfileIconNormal
    ))
}

导航栏样式的设置:

@State currentIndex: number = CommentConstant.HOME_TAB_INDEX;
private tabsController: TabsController = new TabsController();
​
@Builder
TabBuilder(title: string, index: number, selectIcon: ResourceStr, normalIcon: ResourceStr) {
    Column() {
        Image(this.currentIndex === index ? selectIcon : normalIcon)
            .width('25vp')
            .height('25vp')
​
        Text(title)
            .margin({ top: '4vp' })
            .fontSize('10fp')
            .fontColor(this.currentIndex === index ? '#e76b7a' : '#6b6b6b')
    }
    .justifyContent(FlexAlign.Center)
        .height('56vp')
        .width('100%')
        .onClick(() => {
        this.currentIndex = index;
        this.tabsController.changeIndex(index)
    })
}

即可实现如下效果

随后开始编写四个页面组件

根据页面图,整个页面分为五个部分,分别为头部位置信息和通知选项、广告栏、热门标签栏、今日推荐栏、著名品牌栏,由于五个组件仅会在主页面中使用,所以可以使用@Builder创建组件函数在主页面组件中函数式声明,简单快捷且不需要额外import,灰常好用~

由于主页面需要放下的内容过多导致一页不能放下,考虑使用Scroll滚动布局,当子节点高度超过时自动加滚动条,同时需要设置

.align(Alignment.TopStart)

保证子节点从顶部开始排列

其中在实现下面这个卡片的样式时,可以看到卡片是有四个方向的圆角的,但是如果直接放图片,图片会将上方的两个圆角覆盖住,这个时候可以使用.clip(true) 属性,意为沿边缘裁剪,即可达到下面这种效果。

实现效果如下:

Web服务器搭建和数据持久化

到目前位置,本项目的所有数据都是保存在内存中的,仅作外观测试使用,而没有实际作用。

那么如何实现数据持久化呢?

鸿蒙提供了数据库接口,其可以方便地通过一系列配置和方法读取或修改数据库中的数据。然而,使用这种方法却存在一个问题,数据库文件保存在用户本地,既无数据来源,也无法实时更新。所以更好的解决方法应当是建立服务器,客户端通过调用 api 的方式从服务器的数据库中获取数据。

所以现在项目迫切需要搭建一个javaweb服务器。

根据点餐app需求分析,可以大致抽象出如下几个数据表(使用mysql数据库)。

用户表 user

属性列描述
email邮箱
phone电话
id编号
username用户名
password密码

商店表 store

属性列描述
id编号
storeName商店名
address地址
phone电话
intro介绍
score评分
open目前是否营业
cover店铺封面
logo店铺logo
special特殊性 0-无 1-著名 2-TopRated

菜品表 dish

属性列描述
id编号
sid所属商铺编号
dishName菜名
intro介绍
price价格
discount折扣
catalog所属分类

标签表 tags

属性列描述
id编号
tagName标签名

菜品-标签表 dt

属性列描述
Did菜品编号
Tid标签标号

订单表 orders

属性列描述
id订单号
time付款时间
uid用户
price总额
state订单状态 0-待支付 1-送餐中 2-已取消 3-已完成 4-已过期
sid消费商店

订单-菜表 od

属性列描述
oid订单号
Did菜号
number选菜数量

使用sql建表

use pigdelivery;
​
create table user (
    email varchar(2048) not null,
    id int auto_increment primary key ,
    username varchar(30) not null,
    password double not null
);
​
create table store(
    id int auto_increment primary key ,
    storeName varchar(50) not null,
    intro varchar(100) not null,
    score double not null,
    open bit not null,
    cover varchar(2048) not null,
    logo varchar(2048) default 'defaultLogo.jpg',
    special int default 0 not null
);
​
create table dish(
    id int auto_increment primary key ,
    sid int not null,
    dishName varchar(50) not null,
    intro varchar(100) not null,
    price double not null,
    discount double not null,
    catalog varchar(30) not null,
    foreign key (sid) references store(id)
);
​
create table tags(
    id int auto_increment primary key ,
    tagName varchar(30) not null
);
​
create table dt(
    Did int not null,
    Tid int not null,
    foreign key (Did) references dish(id),
    foreign key (Tid) references tags(id)
);
​
create table orders(
    id int auto_increment primary key ,
    time varchar(30),
    uid int not null,
    price double not null,
    state int not null,
    sid int not null,
    foreign key (uid) references user(id),
    foreign key (sid) references store(id)
);
​
create table od(
    Oid int not null,
    Did int not null,
    number int not null,
    foreign key (Oid) references orders(id),
    foreign key (Did) references dish(id)
);

建表完成后,填入一些商店数据用于测试。


数据库建立完成后,接下来开始写Javaweb,javaweb就很好写了,按照流程一步一步来就好,这里仅仅简单地概述一下步骤:

使用Tomcat10.1.24构建服务器,通过JavaWeb编写Servlet,并使用mybatis框架连接数据库。

首先新建一个Jakarta EE项目

通过maven导入依赖的jar包,在资源目录下新建 mybatis-config.xml 用作mybatis配置文件,并按照mybatis配置文件格式填写该xml

随后写相应的Mapper接口和实体类(省略)

以登入接口为例,写一个doPost

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    EvalTran.evalJSON(req, resp);
​
    if(req.getParameter("email") != null && req.getParameter("password") != null) {
        try {
            double password = Double.parseDouble(req.getParameter("password"));
            String email = req.getParameter("email");
            User user = UserLoader.selectUser(email, password);
            if(user != null) {
                resp.getWriter().write(gson.toJson(Response.success("success")));
            } else {
                resp.getWriter().write(gson.toJson(Response.success("user not exist")));
            }
        } catch (NumberFormatException e) {
            resp.getWriter().write(gson.toJson(Response.badRequest("参数不合法")));
        }
    } else {
        resp.getWriter().write(gson.toJson(Response.badRequest("表单数据不完整")));
    }
}

javaWeb程序写好之后,回到DevEco Studio中尝试调用该url

在DevEco Studio中打开Terminal终端执行指令 npm install axios 安装axios

在model目录下编写LoginModel类

import axios from '@ohos/axios';
​
class LoginModel {
    readonly baseUrl = 'http://localhost:8080/PigDeliveryServer_war_exploded'
​
    // 当返回 0 表示登入失败, 否则返回一个数字代表登入成功后的用户id
    async verifyAccount(email: string, password: string): Promise<number> {
        return await axios.post(
            this.baseUrl + '/login',
            {
                email: email,
                password: password,
            }
        )
            .then((resp) => {
                console.log(resp.data.message + '');
            // 下面使用双等号的原因是json传输是数字类型可能会被转成字符串类型
                if(resp.data.code == 200 && resp.data.message == 'success') {
                    return parseInt(resp.data.code);
                } else {
                    return 0;
                }
            })
            .catch((error) => {
                console.log("error: " + error);
                return 0;
            })
    }
}
​
export default new LoginModel() as LoginModel;

回到之前编写的登入界面,当时为了测试登入界面,将登入按钮设计成点击就可以登入,现在,我们尝试将点击事件改成真实有效的验证方式:

Button('LogIn')
    .regButton()
    .onClick(() => {
    loginModel.verifyAccount(this.emailInput, this.passwordInput)
        .then((data) => {
        if(data === 0) {
            AlertDialog.show({
                message: '用户不存在或密码错误',
            })
        } else {
            CommentConstant.userId = data;
            router.replaceUrl({
                url: 'pages/MainPage',
            })
        }
    })
})

当点击登入按钮中,期望中进入主界面的效果并没有实现,而是出现了密码错误的提示,将服务器返回值输出后,控制台输出:

success表示服务器收到了请求,但是参数没有传递过去。

在服务器端doPost方法中编写如下代码:

BufferedReader reader = new BufferedReader(req.getReader());
StringBuilder stringBuilder = new StringBuilder();
while (reader.ready()) {
    stringBuilder.append(reader.readLine());
}
​
String json = stringBuilder.toString();
System.out.println(json);

再次请求,结果如下

ArkTs 中的 axios 直接将data中的数据以 json 字符串的形式发给了服务器,所以在服务器中还要对json的情况做处理

BufferedReader reader = new BufferedReader(req.getReader());
StringBuilder stringBuilder = new StringBuilder();
while (reader.ready()) {
    stringBuilder.append(reader.readLine());
}

String json = stringBuilder.toString();
UserTemp userTemp = gson.fromJson(json, UserTemp.class);

if(userTemp == null || userTemp.getEmail() == null || userTemp.getPassword() == null) {
    resp.getWriter().write(gson.toJson(Response.badRequest("表单数据不完整")));
} else {
    User user = UserLoader.verifyUser(userTemp.getEmail(), userTemp.getPassword());
    if(user != null) {
        resp.getWriter().write(gson.toJson(Response.success("success", String.valueOf(user.getId()))));
    } else {
        resp.getWriter().write(gson.toJson(Response.success("userNotExist")));
    }
}

改变之后,鸿蒙端测试登入成功。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部