鸿蒙期末项目(2)

主界面

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

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

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

TypeScript 复制代码
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
    ))
}

导航栏样式的设置:

TypeScript 复制代码
@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滚动布局,当子节点高度超过时自动加滚动条,同时需要设置

TypeScript 复制代码
.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建表

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

java 复制代码
@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类

TypeScript 复制代码
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;

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

TypeScript 复制代码
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方法中编写如下代码:

TypeScript 复制代码
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的情况做处理

TypeScript 复制代码
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")));
    }
}

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

相关推荐
云端漫步19878 小时前
HarmonyOS 互动卡片实战:快递卡片与睡眠卡片完整开发流程
华为·harmonyos
智塑未来9 小时前
鸿蒙6.1隐私安心加倍:加密分享、星盾防诈、应用锁三重保护,守护到位
华为·harmonyos
byte轻骑兵9 小时前
2026手机远程办公:向日葵、TeamViewer、ToDesk鸿蒙适配+传文件+AI审计功能对比
智能手机·harmonyos·todesk·teamviewer·手机远程办公
贾伟康9 小时前
【知律|06】HarmonyOS ArkTS 法律分类实战:让民法、劳动、消费等入口可维护
harmonyos·arkts·arkui·分类设计·多设备
梦想不只是梦与想1 天前
鸿蒙 测试工具:DevEco Testing(一)
测试工具·harmonyos·testing
大锅盖11 天前
Web 工单要调用相机,第一步不是打开取景框,而是建立能力门禁
前端·数码相机·harmonyos
贾伟康1 天前
【华夏二十四节气|07】HarmonyOS 6.0.2(22) ArkTS 节气搜索实战:多字段匹配与四态闭环
移动开发·harmonyos·arkts·arkui·本地搜索
大龄秃头程序员1 天前
Flutter 项目鸿蒙适配实战:从环境搭建到多环境打包全指南
harmonyos
贾伟康1 天前
【万能转换器|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致
移动开发·harmonyos·arkts·权限管理·隐私合规
贾伟康1 天前
【万能转换器|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击
软件测试·移动开发·harmonyos·arkts·回归测试