
#我的鸿蒙开发手记#鸿蒙日程功能实现 原创 精华
一、介绍与准备
1.整体介绍
本项目是基于 HarmonyOS 5.0.0 及以上版本、DevEco Studio 5.0.0 及以上版本等环境进行鸿蒙日程功能实现的开发展开。
对应用整体构建布局设计进行了介绍,包括日程模块(ScheduleView.ets)的设置以及日程入口 UI 在 products 产品层下的 pages>MainEntry.ets 中的关键代码实现,展示了如何引入日历模块组件、构建导航容器以及实现底部导航栏的标签切换等。
详细阐述了日程功能模块与实现流程,日程模块的 UI 结构由日程主视图(支持月视图、周视图切换,高亮显示当日日程)、日程详情页(展示时间、地点、参与人及提醒设置)和新增 / 编辑页(提供表单输入、地图选点及附件上传功能)等核心组件构成。着重对 ScheduleView.ets 文件的创建与实现代码进行了说明,包括日程数据的加载、搜索功能的实现、新建日程的操作等。
还分别对日历组件的开发进行了介绍,包括日历头部 ScheduleTitle.ets(实现年月切换、农历开关等功能)和日历日期 CalenderView.ets(构建月视图或周视图的日期数据展示、日期选择等)的代码实现。
介绍了日历日程 ScheduleItem.ets 的代码实现,涵盖了日程信息的展示、时间格式化、判断日程是否已结束以及点击跳转到日程详情等功能。
2.基础准备
(1)设备类型:华为手机(直板机)
(2)HarmonyOS版本:HarmonyOS 5.0.0 Release及以上
(3)DevEco Studio版本:DevEco Studio 5.0.0 Release及以上
(4)HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上
二、应用整体构建布局设计
1.模块设计
日程模块:ScheduleView.ets
三、日程入口UI实现
products产品层下的pages>MainEntry.ets关键代码实现:
// 导入模块:底部导航配置、各功能模块视图、类型定义等
import { TAB_LIST } from '../constants/Constants';
import { AgencyTaskView } from '@ohos_agcit/office_attendance_agency';
import { ScheduleView } from '@ohos_agcit/office_attendance_schedule';
import { TabListItem } from '@ohos_agcit/office_attendance_agency';
import { CheckInView } from '@ohos_agcit/office_attendance_checkin';
import { CommonConstants, MainEntryVM } from '@ohos_agcit/office_attendance_common_lib';
import { MineView } from '@ohos_agcit/office_attendance_account';
import { router } from '@kit.ArkUI';
// --------------------------
// 主入口页面构建器(供外部调用)
// --------------------------
@Builder
export function mainEntryBuilder() {
MainEntry();
}
@Entry // 标记为应用入口组件
@ComponentV2 // 声明为ArkUI V2版本组件
struct MainEntry {
// 路由管理单例
vm: MainEntryVM = MainEntryVM.instance;
// 页面状态控制
@Provider() isPageShow: boolean = false; // 页面是否显示
@Provider() currentTabIndex: number = this.vm.curIndex; // 当前选中Tab索引
onPageShow(): void {
this.isPageShow = true; // 页面显示时更新状态
}
onPageHide(): void {
this.isPageShow = false; // 页面隐藏时更新状态
}
build() {
Navigation(this.vm.navStack) { // 导航容器(管理页面堆栈)
// 底部导航栏配置
Tabs({
barPosition: BarPosition.End, // 导航栏位置(底部)
index: this.vm.curIndex // 当前选中索引
}) {
// Tab1: 考勤打卡模块
TabContent() {
CheckInView(); // 加载打卡组件
}
.tabBar(this.tabBarBuilder(TAB_LIST[0], 0)); // 绑定导航栏样式
// Tab2: 待办任务模块
TabContent() {
AgencyTaskView(); // 加载待办组件
}
.tabBar(this.tabBarBuilder(TAB_LIST[1], 1));
// Tab3: 日程管理模块
TabContent() {
ScheduleView(); // 加载日程组件
}
.tabBar(this.tabBarBuilder(TAB_LIST[2], 2));
// Tab4: 个人中心模块
TabContent() {
//MineView({ // 加载个人中心组件
// callback: () => { // 退出登录回调
// router.replaceUrl({ url: 'pages/Login' }) // 跳转登录页
// }
});
}
.tabBar(this.tabBarBuilder(TAB_LIST[3], 3));
}
// 导航栏样式配置
.scrollable(false) // 禁止滑动切换
.barHeight(80) // 导航栏高度
.height(CommonConstants.FULL_HEIGHT) // 全屏高度
.animationDuration(0) // 禁用切换动画
.barMode(BarMode.Fixed) // 固定模式(不随内容滚动)
.onChange((index: number) => { // Tab切换事件
this.vm.curIndex = index; // 更新路由管理状态
this.currentTabIndex = index; // 更新当前索引
});
}
// 导航栏标题配置
.title({
builder: this.titleBuilder(TAB_LIST[this.vm.curIndex]), // 动态标题
height: 92 // 标题栏高度
})
.mode(NavigationMode.Stack) // 堆栈导航模式
}
@Builder
titleBuilder(title: TabListItem) {
Row() {
Text(title.label) // 显示当前模块名称
.fontWeight(FontWeight.Bold) // 加粗
.fontColor('rgba(0,0,0,0.90)') // 字体颜色
.fontSize($r('app.float.navigation_title_font_size')) // 字体大小(资源引用)
.margin({ left: 16, top: 36 }) // 边距
.height(56) // 高度
}
.justifyContent(FlexAlign.Start) // 左对齐
.backgroundColor(title.titleBackgroundColor) // 背景色(动态配置)
.width('100%') // 全宽
.height('100%') // 全高
.alignItems(VerticalAlign.Bottom) // 垂直底部对齐
}
// --------------------------
// 底部导航栏Item构建器
// --------------------------
@Builder
tabBarBuilder(item: TabListItem, index: number) {
Column() { // 垂直布局
// 动态显示选中/未选中图标
Image(this.vm.curIndex === index ? item.iconChecked : item.icon)
.width($r('app.float.navigation_image_size')) // 图标宽度(资源引用)
.height($r('app.float.navigation_image_size')) // 图标高度
.margin({ top: $r('app.string.margin_xs') }); // 上边距
// Tab文字标签
Text(item.label)
.fontColor(this.vm.curIndex === index ?
$r('app.color.icon_color_highlight') : // 选中高亮色
$r('app.color.icon_color_level2')) // 未选中默认色
.fontSize($r('app.float.navigation_navi_size')) // 字体大小
.height(14) // 固定高度
.margin({
top: $r('app.string.margin_xs'),
bottom: $r('app.string.margin_xs')
});
}
.height(80) // 固定高度
.width('100%') // 全宽
.justifyContent(FlexAlign.Start); // 顶部对齐
}
}
四、日程功能模块与实现流程
2.日程模块UI结构设计
日程由以下核心组件构成:
·日程主视图:支持月视图、周视图切换,高亮显示当日日程;
·日程详情页:展示时间、地点、参与人及提醒设置;
·新增/编辑页:提供表单输入、地图选点及附件上传功能。
3.功能实现步骤
(1)ScheduleView.ets文件创建与实现代码:
①在根目录OfficeAttendance下创建scenes场景文件目录
②在scenes目录下创建schedule模块(HSP动态共享)
③ScheduleView.ets代码实现:
// 导入日程数据类型定义和基础工具模块
import { ScheduleInfo } from '../types/Types';
import {
AppStorageData,
CommonConstants,
DataPreferencesUtils,
DateUtils,
DialogMap,
FormatUtil,
MainEntryVM,
RouterModule
} from '@ohos_agcit/office_attendance_common_lib';
import { ScheduleTitle } from './components/ScheduleTitle';
import { DateElement } from '@ohos_agcit/office_attendance_component_lib/src/main/ets/types/DateElement';
import { CalenderView } from '@ohos_agcit/office_attendance_component_lib';
import { RequestProxy } from '../api/RequestProxy';
import { AppStorageV2 } from '@kit.ArkUI';
import { ScheduleItem } from './components/ScheduleItem';
// --------------------------
// 日程管理主页面组件(支持预览)
// --------------------------
@Preview
@ComponentV2
export struct ScheduleView {
// 状态管理
@Local myStorage: AppStorageData = // 应用存储数据连接
AppStorageV2.connect(AppStorageData, CommonConstants.APP_STORAGE_DATA_KEY, () => new AppStorageData())!;
scroller: Scroller = new Scroller() // 列表滚动控制器
@Local selectedDay: DateElement = new DateElement(0, 0, 0); // 当前选中日期
@Local selectedMonth: DateElement = // 当前显示月份
new DateElement(DateUtils.getCurrentYear(), DateUtils.getCurrentMonth(), 0);
@Local showYear: number = DateUtils.getCurrentYear(); // 当前显示年份
@Local showMonth: number = DateUtils.getCurrentMonth(); // 当前显示月份
@Local searchTxt: string | undefined; // 搜索关键词
vm: MainEntryVM = MainEntryVM.instance; // 路由管理单例
@Provider() schedules: ScheduleInfo[] = []; // 日程数据列表
private viewType: number = CommonConstants.MONTH_VIEW; // 视图类型(月/周)
@Local hasScheduleDates: Array<string> = []; // 有日程的日期标记
@Local isShowLunar: boolean = true; // 是否显示农历
// --------------------------
// 加载日程数据
// --------------------------
load() {
let date = FormatUtil.formatDate(this.selectedDay.getDate(), FormatUtil.DATE_YYYY_MM_DD);
// 查询选定日期的日程
RequestProxy.queryByDate(date).then((data) => {
this.schedules = data;
});
// 查询有日程的日期
RequestProxy.queryScheduleDates('', '').then((data) => {
this.hasScheduleDates = data;
});
}
aboutToAppear(): void {
this.load(); // 组件初始化加载数据
}
// --------------------------
// 监听年份变化
// --------------------------
@Monitor('showYear')
onSelectedYearChange(monitor: IMonitor) {
if (monitor.value()) {
this.selectedMonth.year = monitor.value()?.now as number;
}
}
// --------------------------
// 监听月份变化
// --------------------------
@Monitor('showMonth')
onSelectedMonthChange(monitor: IMonitor) {
if (monitor.value()) {
this.selectedMonth.month = monitor.value()?.now as number;
}
}
// 切换月份
changeMonth(selectedMonth: DateElement) {
this.showYear = selectedMonth.year;
this.showMonth = selectedMonth.month;
}
// --------------------------
// 日程搜索
// --------------------------
search(data: string) {
if (data && data.length > 0) {
this.searchTxt = data;
RequestProxy.queryByName(this.searchTxt).then((data) => {
this.schedules = data; // 显示搜索结果
});
} else {
this.searchTxt = undefined;
this.load(); // 清空搜索恢复默认显示
}
}
// --------------------------
// 新建日程
// --------------------------
newSchedule() {
RouterModule.openDialog(DialogMap.NEW_SCHEDULE, {
param: this.selectedDay, // 传递当前选中日期
onPop: () => { this.load(); } // 关闭后刷新
});
}
// 检查农历显示设置
isSettingLunar() {
return DataPreferencesUtils.LUNAR_SETTING_ON ===
DataPreferencesUtils.getSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_ON)
}
// 切换农历显示
changeLunar() {
this.isShowLunar = this.isSettingLunar()
}
// --------------------------
// 主构建方法
// --------------------------
build() {
Stack() {
// 1. 主内容区
Column() {
Row() {
Column().width('4%') // 左侧间距
Column() {
Blank().height('2%')
// 1.1 搜索框
Search({ placeholder: $r('app.string.search_tip') })
.onChange((data: string) => { this.search(data); }) // 输入变化实时搜索
.onSubmit((data: string) => { this.search(data); }) // 提交搜索
Blank().height('2%')
// 1.2 日历标题(年月切换+农历开关)
Row() {
ScheduleTitle({
selectedMonth: this.selectedMonth,
onChange: (selectedMonth: DateElement) => { // 月份切换回调
this.changeMonth(selectedMonth);
},
onLunarChange: () => { // 农历开关回调
this.changeLunar();
}
})
}.visibility(this.searchTxt ? Visibility.None : Visibility.Visible) // 搜索时隐藏
// 1.3 日历视图
Row() {
CalenderView({
hasScheduleDates: this.hasScheduleDates, // 标记有日程的日期
showMonth: new DateElement(this.showYear, this.showMonth, 0),
isShowLunar: this.isShowLunar, // 是否显示农历
onChangeDay: (day: DateElement) => { // 日期选择回调
this.selectedDay = day;
this.load();
},
onChangeMonth: (month: DateElement) => { // 月份切换回调
this.selectedMonth = month;
this.changeMonth(month);
},
onChangeViewType: (viewType: number) => { // 视图切换回调
this.viewType = viewType;
}
})
}.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)
}.layoutWeight(1) // 中间内容区自适应
Column().width('4%') // 右侧间距
}
// 1.4 日期详情(农历显示)
Column() {
Row().shadow({ radius: 10, color: $r('app.color.window_background') }) // 分割线阴影
Column().height('2%')
Row() {
Column().width('4%')
// 农历月份显示
Text(DateUtils.getLunarMonthDesc(this.selectedDay.year, this.selectedDay.month, this.selectedDay.day))
.fontSize(16)
.opacity(0.9)
Blank().width(8) // 间距
// 农历日期显示
Text(DateUtils.getCurrentLunarDayDesc(...))
.fontSize(16)
.opacity(0.9)
}
}.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)
// 1.5 日程列表
Row() {
Column().width('4%')
Column() {
// 正常模式下的日程列表
List({ space: 8, scroller: this.scroller }) {
ForEach(this.schedules, (schedule: ScheduleInfo) => {
ListItem() {
ScheduleItem({
schedule: schedule,
refresh: () => { this.load() } // 刷新回调
})
}
}, (schedule: ScheduleInfo) => JSON.stringify(schedule)) // 唯一键生成
}.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)
// 搜索模式下的日程列表(样式差异)
List({ space: 10, scroller: this.scroller }) {
ForEach(this.schedules, (schedule: ScheduleInfo) => {
ListItem() {
ScheduleItem({ schedule: schedule, refresh: () => { this.load() } })
}
}, (schedule: ScheduleInfo) => JSON.stringify(schedule))
}.visibility(this.searchTxt ? Visibility.Visible : Visibility.None)
}.layoutWeight(1)
Column().width('4%')
}.layoutWeight(1)
}
// 2. 悬浮添加按钮(非搜索状态显示)
Image($r('app.media.ic_plus_filled'))
.onClick(() => { this.newSchedule(); })
.position({
x: this.myStorage.windowWidthVp * 0.8, // 右侧20%位置
y: this.myStorage.windowHeightVp * 0.7 // 底部30%位置
})
.visibility(this.searchTxt ? Visibility.None : Visibility.Visible)
}.backgroundColor(Color.White)
}
}
(2)日历组件开发
描述:日历-头部
ScheduleTitle.ets.ets代码实现:
import { DataPreferencesUtils } from '@ohos_agcit/office_attendance_common_lib';
import { DateElement } from '@ohos_agcit/office_attendance_component_lib/src/main/ets/types/DateElement';
@ComponentV2
export struct ScheduleTitle {
@Param selectedMonth: DateElement = new DateElement(0, 0, 0); // 当前选中的月份
@Param onChange: (selected: DateElement) => void = (selected: DateElement) => {
} // 月份变化回调函数
@Param onLunarChange: () => void = () => {
} // 农历显示设置变化回调函数
// 切换到上一个月
private goBeforeMonth() {
this.selectedMonth.year =
this.selectedMonth.month - 1 >= 0 ? this.selectedMonth.year : this.selectedMonth.year - 1;
this.selectedMonth.month = this.selectedMonth.month - 1 >= 0 ? this.selectedMonth.month - 1 : 11;
this.onChange(this.selectedMonth);
}
// 切换到下一个月
private goAfterMonth() {
this.selectedMonth.year =
this.selectedMonth.month + 1 < 12 ? this.selectedMonth.year : this.selectedMonth.year + 1;
this.selectedMonth.month = this.selectedMonth.month + 1 < 12 ? this.selectedMonth.month + 1 : 0;
this.onChange(this.selectedMonth);
}
// 检查是否设置了显示农历
isSettingLunar() {
return DataPreferencesUtils.LUNAR_SETTING_ON ===
DataPreferencesUtils.getSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_ON)
}
@Builder
settingBuilder() {
Column() {
Row() {
Row() {
Text($r('app.string.schedule_lc_desc')) // 显示农历设置描述文本
}.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Start)
.width('60%')
Row() {
// 如果设置了显示农历,则显示勾选图标
if (this.isSettingLunar()) {
Image($r('app.media.ic_check_mask')).width(24).height(16)
}
}.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.End)
.layoutWeight(1)
}.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
.onClick(() => {
// 点击切换农历显示设置
if (this.isSettingLunar()) {
DataPreferencesUtils.putSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_OFF)
} else {
DataPreferencesUtils.putSync(DataPreferencesUtils.LUNAR_SETTING, DataPreferencesUtils.LUNAR_SETTING_ON)
}
this.onLunarChange();
})
}.height(45)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width(240)
.margin({ top: 10, bottom: 10 })
}
build() {
Row() {
// 左侧:月份导航区域
Row() {
// 上个月按钮
Row() {
Image($r('app.media.ic_public_small_left'))
.id('pre_month')
.height(20)
.width(12)
}
.margin({ left: 5 })
.height('100%')
.aspectRatio(1)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.goBeforeMonth();
})
Column().width('6%') // 间隔
// 显示当前年月
Text(`${this.selectedMonth.year}-${this.selectedMonth.month + 1 >= 10 ? '' : '0'}${this.selectedMonth.month +
1}`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.opacity(0.9)
Column().width('6%') // 间隔
// 下个月按钮
Row() {
Image($r('app.media.ic_public_small_right'))
.id('next_month')
.height(20)
.width(12)
}
.margin({ left: 5 })
.height('100%')
.aspectRatio(1)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.goAfterMonth();
})
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Start)
}.width('50%')
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Start)
// 右侧:设置按钮
Row() {
Image($r('app.media.ic_ellipsis'))
.width(20)
.height(12)
.bindMenu(this.settingBuilder) // 绑定设置菜单
}
.width('50%')
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.End)
}.width('100%')
.height(24)
}
}
描述:日历-日期
CalenderView.ets代码实现:
import { i18n } from '@kit.LocalizationKit';
import { CommonConstants, DateUtils } from '@ohos_agcit/office_attendance_common_lib';
import { DateElement } from '../types/DateElement';
@ObservedV2
class ShowDays {
@Trace showDays: DateElement [][] = []; // 月视图日期数据(二维数组)
@Trace showWeekDays: DateElement [][] = []; // 周视图日期数据
}
@ComponentV2
export struct CalenderView {
// 组件参数
@Param isShowLunar: boolean = true; // 是否显示农历
@Param fontSize: Resource = $r('app.float.calendar_title_font_size') // 字体大小资源
@Param titleFontOpacity: Resource = $r('app.float.calendar_title_font_opacity') // 标题透明度
@Param contentFontOpacity: Resource = $r('app.float.calendar_content_font_opacity') // 内容透明度
@Param weekendFontOpacity: Resource = $r('app.float.calendar_weekend_font_opacity') // 周末透明度
@Param beforeDateFontOpacity: Resource = $r('app.float.calendar_before_date_font_opacity') // 非当月日期透明度
@Param todayFontOpacity: Resource = $r('app.float.calendar_today_font_opacity') // 今日透明度
readonly TIME_DEF_VALUE = -1; // 时间默认值
// 本地化日历实例
private calendar = i18n.getCalendar(CommonConstants.EN_LOCALE);
@Local today: number = this.calendar.get(CommonConstants.FIELD_DAY); // 当前日
@Local currentMonth: number = this.calendar.get(CommonConstants.FIELD_MONTH); // 当前月
@Local currentYear: number = this.calendar.get(CommonConstants.FIELD_YEAR); // 当前年
// 组件状态
@Param selectedDay: DateElement = new DateElement(0, 0, this.TIME_DEF_VALUE); // 选中日期
@Event onChangeDay: (val: DateElement) => void = (val: DateElement) => {}; // 日期变更事件
@Param showMonth: DateElement = new DateElement(this.TIME_DEF_VALUE, this.TIME_DEF_VALUE, this.TIME_DEF_VALUE); // 展示的月份
@Event onChangeMonth: (showMonth: DateElement) => void = (showMonth: DateElement) => {}; // 月份变更事件
@Param showDotDate: Array<string> = [] // 需要显示圆点的日期
@Local viewType: number = CommonConstants.MONTH_VIEW; // 视图类型(月/周)
@Event onChangeViewType: (showType: number) => void = (showType: number) => {}; // 视图切换事件
@Param hasScheduleDates: Array<string> = []; // 有日程的日期列表
showDays: ShowDays = new ShowDays(); // 日期数据实例
// 监听月份和选中日变化
@Monitor('showMonth','selectedDay')
onSelectedMonthChange(monitor: IMonitor) {
this.refresh();
}
// 判断是否是周末
isWeekends(index: number) {
return index === CommonConstants.COMMON_ZERO || index === CommonConstants.COMMON_SIX;
}
// 刷新日历数据
refresh() {
this.init();
// 设置日历到展示月份的第一天
this.calendar.set(this.showMonth.year, this.showMonth.month, 0);
// 计算前后月份信息
let preMonth = this.showMonth.month - 1 >= 0 ? this.showMonth.month - 1 : 11;
let preMonthYear = this.showMonth.month - 1 >= 0 ? this.showMonth.year : this.showMonth.year - 1;
let preMonthDays: number = -this.calendar.compareDays(new Date(preMonthYear, preMonth, 0)) - 1;
let nextMonth = this.showMonth.month + 1 < 12 ? this.showMonth.month + 1 : 0;
let nextMonthYear = this.showMonth.month + 1 < 12 ? this.showMonth.year : this.showMonth.year + 1;
let selectMonthDays: number = this.calendar.compareDays(new Date(nextMonthYear, nextMonth, 0));
// 构建日期数组
let firstDayOfWeek = this.calendar.get(CommonConstants.FIELD_DAY_OF_WEEK);
let weedDays: DateElement [] = [];
let diffDays = firstDayOfWeek < 7 ? firstDayOfWeek : 0;
// 填充上月日期
for (let i = 1; i <= diffDays; i++) {
weedDays.push(new DateElement(preMonthYear, preMonth, preMonthDays - diffDays + i));
}
// 填充本月日期
for (let i = 1; i <= selectMonthDays; i++) {
if (weedDays.length === 7) {
this.showDays.showDays.push(weedDays);
weedDays = [];
}
weedDays.push(new DateElement(this.showMonth.year, this.showMonth.month, i));
}
// 填充下月日期
if (weedDays.length < 7) {
let diffDays = 7 - weedDays.length;
for (let i = 0; i < diffDays; i++) {
weedDays.push(new DateElement(nextMonthYear, nextMonth, i + 1));
}
}
this.showDays.showDays.push(weedDays);
// 同步选中日期状态
if (this.selectedDay.year !== this.showMonth.year || this.selectedDay.month !== this.showMonth.month) {
this.selectedDay.year = this.showMonth.year;
this.selectedDay.month = this.showMonth.month;
this.onChangeDay(this.selectedDay);
}
if (this.selectedDay.day === this.TIME_DEF_VALUE) {
this.selectedDay.day = this.today;
this.onChangeDay(this.selectedDay);
}
this.setWeedViewData()
}
// 设置周视图数据
setWeedViewData() {
this.showDays.showDays.forEach(week => {
week.forEach(day => {
if (this.isSelectedDay(day)) {
this.showDays.showWeekDays = [week]; // 找到选中日所在的周
}
})
})
}
// 初始化月份
init() {
this.showDays.showDays = [];
if (this.showMonth.year === this.TIME_DEF_VALUE) {
this.showMonth.year = this.currentYear;
this.showMonth.month = this.currentMonth;
this.onChangeMonth(this.showMonth);
}
}
// 生命周期:组件即将显示
aboutToAppear() {
this.refresh();
}
// 判断是否是今天
private isToday(day: DateElement): boolean {
return this.today === day.day && this.currentMonth === day.month &&
this.currentYear === day.year;
}
// 判断是否是过去的日期
isBeforeDay(day: DateElement) {
return this.calendar.compareDays(new Date(day.year, day.month, day.day)) < 0;
}
// 补零格式化
appendZero(num: number): string {
return num < 10 ? '0' + num : '' + num;
}
// 判断是否选中日
private isSelectedDay(day: DateElement): boolean {
return this.selectedDay.isEquals(day);
}
// 判断是否当前月份
isCurrentMonth(day: DateElement) {
return this.showMonth.year === day.year && this.showMonth.month === day.month;
}
// 手势切换月份/周
private changeMonthByGesture(e: GestureEvent) {
if (e.offsetX > 10) { // 向左滑动
if (this.viewType === CommonConstants.MONTH_VIEW) {
// 月视图切上个月
this.showMonth.year = this.showMonth.month - 1 < 0 ? this.showMonth.year - 1 : this.showMonth.year;
this.showMonth.month = this.showMonth.month - 1 < 0 ? 11 : this.showMonth.month - 1;
this.onChangeMonth(this.showMonth);
} else {
// 周视图切上周
let selectedDate = new Date(this.selectedDay.year, this.selectedDay.month, this.selectedDay.day);
selectedDate = new Date(selectedDate.getTime() - CommonConstants.SEVEN_DAY);
this.changeSelectDay(new DateElement(selectedDate.getFullYear(),
selectedDate.getMonth(),
selectedDate.getDate()))
}
} else if (e.offsetX < -10) { // 向右滑动
if (this.viewType === CommonConstants.MONTH_VIEW) {
// 月视图切下个月
this.showMonth.year = this.showMonth.month + 1 < 12 ? this.showMonth.year : this.showMonth.year + 1;
this.showMonth.month = this.showMonth.month + 1 < 12 ? this.showMonth.month + 1 : 0;
this.onChangeMonth(this.showMonth);
} else {
// 周视图切下周
let selectedDate = new Date(this.selectedDay.year, this.selectedDay.month, this.selectedDay.day);
selectedDate = new Date(selectedDate.getTime() + CommonConstants.SEVEN_DAY);
this.changeSelectDay(new DateElement(selectedDate.getFullYear(),
selectedDate.getMonth(),
selectedDate.getDate()))
}
}
}
// 改变选中日
changeSelectDay(day: DateElement) {
this.selectedDay.year = day.year;
this.selectedDay.month = day.month;
this.selectedDay.day = day.day;
this.onChangeDay(day);
this.setWeedViewData();
this.changeMonth(day);
}
// 切换月份
changeMonth(month: DateElement) {
this.showMonth.year = month.year;
this.showMonth.month = month.month;
this.onChangeMonth(month);
}
// 判断是否有日程
isHasSchedule(day: DateElement) {
let dayStr: string = day.getDateString();
return this.hasScheduleDates.filter(t => {
return t.indexOf(dayStr) > -1;
}).length > 0;
}
build() {
Column() {
// 周标题区域
Column() {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
ForEach(CommonConstants.WEEK_TITLE, (str: Resource, index: number) => {
Column() {
Text(str) // 显示周几标题
.fontSize(this.fontSize)
.fontWeight(FontWeight.Regular)
.textAlign(TextAlign.Center)
.opacity(this.titleFontOpacity)
}
.justifyContent(FlexAlign.Center)
.width($r('app.float.content_size'))
.height($r('app.float.height_or_line_height'))
})
}
.width(CommonConstants.FULL_PERCENT)
.borderRadius($r('app.float.height_or_line_height'))
.margin({
bottom: $r('app.float.day_button_radius'),
left: CommonConstants.COMMON_MARGIN,
right: CommonConstants.COMMON_MARGIN
})
}
// 日期主体区域
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.viewType === CommonConstants.MONTH_VIEW ? this.showDays.showDays : this.showDays.showWeekDays,
(weekDays: DateElement []) => {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
ForEach(weekDays, (day: DateElement, index: number) => {
Column() {
// 阳历日期
Text(`${day.day}`)
.fontSize(this.fontSize)
.fontWeight(FontWeight.Medium)
.opacity(this.isToday(day) ? this.todayFontOpacity : // 今日透明度
this.isWeekends(index) ? this.weekendFontOpacity : // 周末透明度
this.isCurrentMonth(day) ? this.contentFontOpacity : this.beforeDateFontOpacity) // 非本月透明度
.fontColor(this.isToday(day) && !this.isSelectedDay(day) ? $r('app.color.start_window_background') :
$r('app.color.black'))
// 农历日期
Text(DateUtils.getCurrentLunarDayDesc(day.year, day.month, day.day))
.fontSize(this.fontSize)
.visibility(this.isShowLunar ? Visibility.Visible : Visibility.None)
.opacity(this.isToday(day) ? this.todayFontOpacity : // 透明度设置同上
this.isWeekends(index) ? this.weekendFontOpacity :
this.isCurrentMonth(day) ? this.contentFontOpacity : this.beforeDateFontOpacity)
.fontColor(this.isToday(day) && !this.isSelectedDay(day) ? $r('app.color.start_window_background') :
$r('app.color.black'))
// 日程圆点标记
Column()
.width(4)
.height(4)
.borderRadius(2)
.backgroundColor(this.isSelectedDay(day) ? $r('app.color.sys_default_blue') : // 选中状态颜色
this.isToday(day) ? Color.White : $r('app.color.sys_default_blue')) // 今日颜色
.visibility(this.isHasSchedule(day) ? Visibility.Visible : Visibility.None) // 有日程时显示
}
.onClick(() => {
this.changeSelectDay(day); // 点击选择日期
})
// 背景样式
.backgroundColor(this.isToday(day) && !this.isSelectedDay(day) ? $r('app.color.sys_default_blue') :
Color.White)
.borderRadius($r('app.float.schedule_border_radius'))
.borderColor(this.isSelectedDay(day) ? $r('app.color.sys_default_blue') : Color.White) // 选中边框
.borderWidth(this.isSelectedDay(day) ? 1 : 0)
.justifyContent(FlexAlign.Center)
.width($r('app.float.content_size'))
.height($r('app.float.content_size'))
})
}
.width('100%')
.margin({ top: '6vp' })
})
}
.width('100%')
// 添加水平滑动手势
.gesture(PanGesture({ fingers: 1, direction: PanDirection.Horizontal, distance: 5 })
.onActionEnd((e: GestureEvent) => {
this.changeMonthByGesture(e); // 手势切换月份/周
})
)
Column().height('2%') // 间隔区域
// 视图切换按钮
Column() {
// 展开月视图按钮
Image($r('app.media.ic_chevron_up'))
.width(64)
.height(16)
.visibility(this.viewType === CommonConstants.MONTH_VIEW ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.viewType = CommonConstants.WEEK_VIEW
this.onChangeViewType(this.viewType)
})
// 收起周视图按钮
Image($r('app.media.ic_chevron_down'))
.width(64)
.height(16)
.visibility(this.viewType === CommonConstants.WEEK_VIEW ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.viewType = CommonConstants.MONTH_VIEW
this.onChangeViewType(this.viewType)
})
}.height(16)
.width('100%')
}
}
}
描述:日历-日程
ScheduleItem.ets代码实现:
import { ScheduleInfo } from '../../types/Types'
import {
AppStorageData,
CommonConstants,
DialogMap,
FormatUtil,
RouterModule
} from '@ohos_agcit/office_attendance_common_lib'
import { AppStorageV2 } from '@kit.ArkUI';
@ComponentV2
export struct ScheduleItem {
@Local myStorage: AppStorageData = // 本地应用存储实例
AppStorageV2.connect(AppStorageData, CommonConstants.APP_STORAGE_DATA_KEY, () => new AppStorageData())!;
@Event refresh: () => void = () => { // 刷新事件回调
};
@Param schedule: ScheduleInfo = { title: '', startTime: '', endTime: '' }; // 传入的日程数据
// 跳转到日程详情
toDetail() {
RouterModule.openDialog(DialogMap.SCHEDULE_DETAIL, {
param: this.schedule.id,
onPop: () => {
this.refresh(); // 当详情页关闭时触发刷新
}
});
}
// 格式化时间显示
getTime(time: string | undefined): string {
if (time) {
return FormatUtil.formatDate(new Date(time), FormatUtil.DATE_24H_mm); // 使用24小时制格式
}
return '';
}
// 判断日程是否已结束
isEnd() {
if (this.schedule.isAllDay) { // 全天日程不显示结束状态
return false;
}
return new Date().getTime() > new Date(this.schedule.endTime).getTime();
}
// 获取整体透明度
getOpacity() {
return this.isEnd() ? 0.4 : 1; // 已结束的日程降低透明度
}
// 获取地点信息的透明度
getLocationOpacity() {
return this.isEnd() ? 0.4 : 0.9; // 地点信息使用不同透明度
}
build() {
Row() {
// 左侧时间显示区域
Column() {
if (this.schedule.isAllDay) { // 全天日程样式
Blank()
// 全天标识图标
Row()
.borderColor($r('app.color.purple'))
.borderRadius($r('app.float.trip_schedule_icon_radius'))
.borderWidth($r('app.float.trip_schedule_icon_width'))
.height(CommonConstants.COMMON_FIVE)
.width(CommonConstants.COMMON_FIVE)
.margin({ bottom: CommonConstants.COMMON_FIVE })
// 全天文字
Text($r('app.string.whole_day'))
.fontSize($r('app.float.trip_content_font'))
.opacity(this.getOpacity())
Blank()
} else { // 普通日程样式
// 开始时间
Text(this.getTime(this.schedule.startTime))
.fontSize(10)
.fontWeight(FontWeight.Regular)
.opacity(this.getOpacity())
// 时间轴竖线
Row()
.border({
style: { left: BorderStyle.Solid },
color: { left: $r('sys.color.icon_fourth') },
width: { left: CommonConstants.COMMON_ONE }
})
.margin({ left: $r('app.float.trip_icon_margin') })
.height(CommonConstants.COMMON_SEVEN)
.width(CommonConstants.COMMON_ONE)
// 日程标识圆点
Row()
.borderColor($r('app.color.purple'))
.borderRadius($r('app.float.trip_schedule_icon_radius'))
.borderWidth($r('app.float.trip_schedule_icon_width'))
.height(CommonConstants.COMMON_FIVE)
.width(CommonConstants.COMMON_FIVE)
// 时间轴竖线
Row()
.border({
style: { left: BorderStyle.Solid },
color: { left: $r('sys.color.icon_fourth') },
width: { left: CommonConstants.COMMON_ONE }
})
.margin({ left: $r('app.float.trip_icon_margin') })
.height(CommonConstants.COMMON_SEVEN)
.width(CommonConstants.COMMON_ONE)
// 结束时间
Text(this.getTime(this.schedule.endTime))
.fontSize(10)
.fontWeight(FontWeight.Regular)
.opacity(this.getOpacity())
}
}
.width($r('app.float.trip_icon_width')) // 固定宽度
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.height('100%')
// 右侧内容区域
Column({ space: 8 }) {
// 日程标题
Text(this.schedule.title)
.fontSize(12)
.fontWeight(FontWeight.Regular)
.opacity(this.getOpacity())
.maxLines(2) // 最多显示两行
.textOverflow({ overflow: TextOverflow.Ellipsis }) // 超出显示省略号
.width(CommonConstants.FULL_PERCENT) // 100%宽度
// 日程地点
Text(this.schedule.location)
.fontSize($r('app.float.font_size_common'))
.fontWeight(FontWeight.Regular)
.maxLines(1)
.opacity(this.getLocationOpacity())
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width(CommonConstants.FULL_PERCENT)
}
.layoutWeight(CommonConstants.COMMON_ONE) // 剩余空间权重
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.layoutWeight(1)
.margin({ right: 12 })
}
.backgroundColor($r('app.color.window_background')) // 背景颜色
.height(72) // 固定高度
.width(CommonConstants.FULL_PERCENT) // 100%宽度
.borderRadius(8) // 圆角
.onClick(() => { // 点击事件
this.toDetail();
})
}
}
五、最终成果展示
