代码仓地址,大家记得点个star
消息页面开发
设计图


需求分析

消息组件的封装
import { CommonConstant } from '../contants/CommonConstant'
@Component
export struct MessageComponent {
@State icon: ResourceStr = ''
@State title: string = ''
@Prop content: string
@Prop unReadCount: number
build() {
Row({ space: 15 }) {
Badge({
count: this.unReadCount,
position: BadgePosition.RightTop,
style: { badgeSize: 15, badgeColor: '#FA2A2D' }
}) {
Image(this.icon)
.width($r('app.float.common_width_small')).aspectRatio(1)
}
Column({ space: 10 }) {
Text(this.title)
.fontWeight(FontWeight.Medium)
.fontSize($r('app.float.common_font_size_medium'))
Text(this.content)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontColor($r('app.color.common_gray'))
.fontSize($r('app.float.common_font_size_small'))
}.height(80).justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
.width('80%')
}
.width(CommonConstant.WIDTH_FULL)
.height(80)
.backgroundColor($r('app.color.common_white'))
.borderRadius(15)
.padding(10)
}
}
编写消息页面的接口方法
import http from '../request/Request'
import { MessageInfoNew, PageVo, UserMessageCount, UserMessageInfo, UserMessagePageParam } from './MessageApi.type'
/**
* 消息接口
*/
class MessageApi {
/**
* 获取最新消息
*/
getMessageNew = (): Promise<Array<MessageInfoNew>> => {
return http.get('/v1/message/new')
}
/**
* 查询用户获得的消息总数量和未读
*/
getUserMessageCount = (): Promise<UserMessageCount> => {
return http.get('/v1/message/count')
}
/**
* 用户根据消息分类查询分页的消息
*/
getMessageList = (data: UserMessagePageParam): Promise<PageVo<UserMessageInfo>> => {
return http.get('/v1/message/list?type=' + data.type + '&&page=' + data.page + '&&pageSize=' + data.pageSize)
}
/**
* 查看消息详情
*/
getMessageInfo = (data: number): Promise<UserMessageInfo> => {
return http.get('/v1/message/info?id=' + data)
}
}
const messageApi = new MessageApi();
export default messageApi as MessageApi;
/**
* 最新消息
*/
export interface MessageInfoNew extends BaseTime {
/**
* 消息id
*/
id: number
/**
* 消息标题
*/
title: string
/**
* 消息内容
*/
content: string
/**
* 消息类型'1' 系统消息 '2' 通知公告
*/
type: string
}
/**
* 时间
*/
export interface BaseTime {
/**
* 创建时间
*/
createTime?: Date
/**
* 更新时间
*/
updateTime?: Date
}
/**
* 分页参数
*/
export interface PageParam {
/**
* 当前页
*/
page?: number
/**
* 每一页展示的数据条数
*/
pageSize?: number
}
/**
* 消息总数量和未读
*/
export interface UserMessageCount {
/**
* 总消息数量
*/
totalQuantity: number
/**
* 总未读数量
*/
totalUnReadQuantity: number
/**
* 系统消息未读数量
*/
systemUnReadQuantity: number
/**
* 通知公告消息未读数量
*/
noticeUnReadQuantity: number
}
/**
* 用户消息详情
*/
export interface UserMessageInfo extends BaseTime {
/**
* 用户消息id
*/
id: number
/**
* 是否查看 '0'未查看 '1'查看
*/
isLook: string
/**
* 消息id
*/
messageId?: number
/**
* 消息标题
*/
title: string
/**
* 消息内容
*/
content: string
/**
* 消息类型'1' 系统消息 '2' 通知公告
*/
type?: string
/**
* 创建时间
*/
time: string
}
/**
* 分页响应参数
*/
export interface PageVo<T> {
current: number,
size: number,
total: number,
records: Array<T>
}
/**
* 用户根据消息分类查询所有的消息
*/
export interface UserMessagePageParam extends PageParam {
/**
* 消息类型
*/
type: string
}
学习页面开发
设计图

需求分析

学习打卡
1、手机定位
在module.json5文件里面配置位置权限

代码向用户申请权限(弹窗)
/**
* 动态授权
*/
async aboutToAppear() {
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
// 定义要申请的权限
const permissions: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION'];
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
try {
const data = await atManager.requestPermissionsFromUser(context, permissions);
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 获取位置信息
this.getLocation()
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
showToast('用户必须授权才能访问当前定位功能')
return;
}
}
} catch (error) {
showToast('用户必须授权才能访问当前定位功能')
}
}
获取用户位置信息,获取经度纬度然后进行地理编码
/**
* 获取位置
*/
async getLocation() {
let request: geoLocationManager.SingleLocationRequest = {
'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
'locatingTimeoutMs': 10000
}
try {
// 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
const result = await geoLocationManager.getCurrentLocation(request)
Logger.info('current location: ' + JSON.stringify(result));
// 判断地理编码服务是否可用
let isAvailable = geoLocationManager.isGeocoderAvailable();
if (isAvailable) {
// 地理编码服务可用
let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest =
{ "latitude": result.latitude, "longitude": result.longitude, "maxItems": 1 };
geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
if (err) {
Logger.error('getAddressesFromLocation err: ' + JSON.stringify(err));
} else {
Logger.info('getAddressesFromLocation data: ' + JSON.stringify(data));
// 成功获取到位置信息
this.location = data[0].administrativeArea + '' + data[0].subAdministrativeArea
return
}
});
} else {
showToast('地理编码服务不可用')
}
} catch (err) {
Logger.error("errCode:" + JSON.stringify(err));
showToast("获取位置失败")
}
}
2、日历组件
使用我们一开始项目准备阶段引入的鸿蒙三方库ibest-ui
import { IBestCalendar } from "@ibestservices/ibest-ui";
// 日历
IBestCalendar({
clock: true, // 设置 clock 为 true, 可开启打卡模式, 打卡模式下只能切换年月, 不能选择日期
defaultSelectedDate: this.clockDate, // 打卡的日期是一个字符串数组
clockSuccessText: '✔', // 已打卡展示的
unClockText: '✖', // 未打卡展示的
})
3、打卡记录

4、编写学习打卡接口方法
import http from '../request/Request'
import { LearnClockParam, PageParam, UserLearnClockData } from './LearnClockApi.type'
import { PageVo } from './MessageApi.type'
/**
* 消息接口
*/
class LearnClockApi {
/**
* 学习打卡
*/
learnClock = (data: LearnClockParam) => {
return http.post('/v1/learn/clock', data)
}
/**
* 查询用户已打卡的日期
*/
listClockTime = (): Promise<Array<string>> => {
return http.get('/v1/learn/listClockTime')
}
/**
* 查询用户今日是否打卡
*/
isClock = (): Promise<boolean> => {
return http.get('/v1/learn/isClock')
}
/**
* 分页查询用户打卡记录
*/
pageListUserClock = (data: PageParam): Promise<PageVo<UserLearnClockData>> => {
return http.get('/v1/learn/page?page=' + data.page + '&&pageSize=' + data.pageSize)
}
}
const learnClockApi = new LearnClockApi();
export default learnClockApi as LearnClockApi;
/**
* 学习打卡入参
*/
export interface LearnClockParam {
/**
* 打卡地点
*/
location: string
/**
* 打卡内容
*/
content: string
}
/**
* 时间
*/
export interface BaseTime {
/**
* 创建时间
*/
createTime?: Date
/**
* 更新时间
*/
updateTime?: Date
}
/**
* 分页参数
*/
export interface PageParam {
/**
* 当前页
*/
page?: number
/**
* 每一页展示的数据条数
*/
pageSize?: number
}
/**
* 分页响应参数
*/
export interface PageVo<T> {
current: number,
size: number,
total: number,
records: Array<T>
}
/**
* 用户打卡记录数据
*/
export interface UserLearnClockData extends BaseTime {
/**
* 学习打卡id
*/
id: number
/**
* 打卡地点
*/
location: string
/**
* 打卡内容
*/
content: string
/**
* 用户昵称
*/
nickname: string
/**
* 创建时间字符串格式
*/
time: string
}
学习目标

1、目标整体进度
使用的是鸿蒙进度条组件
// 环形进度条
Stack() {
Progress({
value: this.countData.completeQuantity,
total: this.countData.totalQuantity,
type: ProgressType.Ring
})
.width($r('app.float.common_width_medium'))
.height($r('app.float.common_height_medium'))
.color('#1698CE')
.style({ strokeWidth: 8 })
Text(this.countData.completeQuantity + '/' + this.countData.totalQuantity)
.fontSize($r('app.float.common_font_size_tiny'))
}
2、添加目标
使用的是ibest-ui里面的IBestDialog组件
IBestDialog({
visible: $dialogVisible,
title: "添加目标",
showCancelButton: true,
defaultBuilder: (): void => this.formInputContain(),
beforeClose: async (action) => {
if (action === 'cancel') {
return true
}
const valueLength = this.inputTargetValue.trim().length;
this.formInputError = !valueLength;
if (!this.formInputError) {
// 添加新目标
await targetInfoApi.addTargetInfo({ content: this.inputTargetValue })
showToast('添加目标成功')
// 查询数据
this.page = 1
this.aboutToAppear()
return true
}
return !this.formInputError
}
})
@Builder
formInputContain() {
Column() {
TextInput({ 'placeholder': '请输入目标内容,长度不能超过255字符' })
.fontSize(14)
.placeholderFont({ size: 14 })
.onChange((value) => {
this.inputTargetValue = value;
this.formInputError = false
})
if (this.formInputError) {
Text('目标内容不能为空')
.width(CommonConstant.WIDTH_FULL)
.textAlign(TextAlign.Start)
.margin({
top: 5,
left: 5
})
.fontColor(Color.Red)
.fontSize($r('app.float.common_font_size_small'))
.transition({ type: TransitionType.Insert, opacity: 1 })
.transition({ type: TransitionType.Delete, opacity: 0 })
}
}.width('90%').margin({ top: 15, bottom: 15 })
}
3、完成、删除目标
左滑目标即可看到完成和删除按钮
使用的是ListItem.swipeAction方法
核心代码如下
@Builder
itemEnd(item: TargetInfoVo) {
Row() {
// 构建尾端滑出组件
Button({ type: ButtonType.Circle }) {
Image($r('app.media.icon_finish'))
.width(40)
.aspectRatio(1)
}
.onClick(async () => {
// 完成未完成的目标
if (item.status === '1') {
showToast('当前目标已完成,无需重复点击')
return
}
// 完成目标
await targetInfoApi.completeTargetInfo({ id: item.id })
showToast('目标已完成')
router.replaceUrl({ url: RouterConstant.VIEWS_LEARN_TARGET })
}).margin({ right: 10 })
// 构建尾端滑出组件
Button({ type: ButtonType.Circle }) {
Image($r('app.media.icon_delete'))
.width(40)
.aspectRatio(1)
}
.margin({ right: 10 })
.onClick(async () => {
IBestDialogUtil.open({
title: "提示",
message: "是否确认删除当前目标?",
showCancelButton: true,
onConfirm: async () => {
// 删除目标
await targetInfoApi.deleteTargetInfo({ id: item.id })
showToast('删除目标成功')
router.replaceUrl({ url: RouterConstant.VIEWS_LEARN_TARGET })
}
})
})
}.justifyContent(FlexAlign.SpaceBetween)
}
4、编写学习目标接口方法
import http from '../request/Request'
import { PageParam, PageVo, TargetInfoAddParam,
TargetInfoCompleteParam,
TargetInfoCountVo,
TargetInfoDeleteParam,
TargetInfoEditParam,
TargetInfoVo } from './TargetInfoApi.type'
/**
* 目标内容接口
*/
class TargetInfoApi {
/**
* 获取用户整体目标完成进度统计
*/
getTargetInfoCount = (): Promise<TargetInfoCountVo> => {
return http.get('/v1/target/count')
}
/**
* 分页查询用户整体目标
*/
pageListTargetInfo = (data: PageParam): Promise<PageVo<TargetInfoVo>> => {
return http.get('/v1/target/page?page=' + data.page + '&&pageSize=' + data.pageSize)
}
/**
* 新增目标
*/
addTargetInfo = (data: TargetInfoAddParam) => {
return http.post('/v1/target/add', data)
}
/**
* 修改目标
*/
editTargetInfo = (data: TargetInfoEditParam) => {
return http.post('/v1/target/edit', data)
}
/**
* 完成目标
*/
completeTargetInfo = (data: TargetInfoCompleteParam) => {
return http.post('/v1/target/complete', data)
}
/**
* 删除目标
*/
deleteTargetInfo = (data: TargetInfoDeleteParam) => {
return http.post('/v1/target/delete', data)
}
}
const targetInfoApi = new TargetInfoApi();
export default targetInfoApi as TargetInfoApi;
/**
* 用户整体目标完成进度统计
*/
export interface TargetInfoCountVo {
/**
* 总目标数量
*/
totalQuantity: number
/**
* 完成目标的数量
*/
completeQuantity: number
/**
* 更新时间
*/
updateTime: string
}
/**
* 新增目标
*/
export interface TargetInfoAddParam {
/**
* 目标内容
*/
content: string
}
/**
* 修改目标
*/
export interface TargetInfoEditParam {
/**
* 目标内容
*/
content: string
/**
* 目标内容id
*/
id: number
}
/**
* 完成目标
*/
export interface TargetInfoCompleteParam {
/**
* 目标内容id
*/
id: number
}
/**
* 删除目标
*/
export interface TargetInfoDeleteParam {
/**
* 目标内容id
*/
id: number
}
/**
* 用户目标内容详情
*/
export interface TargetInfoVo extends BaseTime {
/**
* 目标内容id
*/
id: number
/**
* 目标内容
*/
content: string
/**
* 目标状态:0未完成 1完成
*/
status: string
/**
* 创建时间字符串格式
*/
time: string
}
/**
* 时间
*/
export interface BaseTime {
/**
* 创建时间
*/
createTime?: Date
/**
* 更新时间
*/
updateTime?: Date
}
/**
* 分页参数
*/
export interface PageParam {
/**
* 当前页
*/
page?: number
/**
* 每一页展示的数据条数
*/
pageSize?: number
}
/**
* 分页响应参数
*/
export interface PageVo<T> {
current: number,
size: number,
total: number,
records: Array<T>
}
学习平台和面试平台
学习平台和面试平台跟首页差不多雷同,所以在这里就不说了