鸿蒙OSUniApp导航栏组件开发:打造清新简约的用户界面#三方框架 #Uniapp
UniApp 开发实战:打造符合鸿蒙设计风格的日历活动安排组件
在移动应用开发中,日历和活动安排是非常常见的需求。本文将详细介绍如何使用 UniApp 框架开发一个优雅的日历活动安排组件,并融入鸿蒙系统的设计理念,实现一个既美观又实用的功能模块。
设计理念
在开发这个组件之前,我们需要深入理解鸿蒙系统的设计哲学。鸿蒙系统强调"自然、流畅、统一"的设计原则,这些特点将贯穿我们的整个组件实现过程:
- 自然:日历的切换和选择要符合用户的直觉
- 流畅:动画过渡要平滑,操作响应要及时
- 统一:视觉元素要保持一致性,符合系统设计规范
- 高效:快速获取和展示日程信息
组件实现
1. 日历选择器组件
首先实现一个基础的日历选择器组件:
<!-- components/harmony-calendar/harmony-calendar.vue -->
<template><view class="harmony-calendar"><view class="calendar-header"><view class="month-switcher"><text class="iconfont icon-left" @click="changeMonth(-1)"></text><text class="current-month">{{ currentYear }}年{{ currentMonth + 1 }}月</text><text class="iconfont icon-right" @click="changeMonth(1)"></text></view><view class="weekday-header"><text v-for="day in weekDays" :key="day">{{ day }}</text></view></view><view class="calendar-body" :class="{ 'with-animation': enableAnimation }"><view class="days-grid"><view v-for="(day, index) in daysArray" :key="index"class="day-cell":class="[{ 'current-month': day.currentMonth },{ 'selected': isSelected(day.date) },{ 'has-events': hasEvents(day.date) }]"@click="selectDate(day)"><text class="day-number">{{ day.dayNumber }}</text><view v-if="hasEvents(day.date)" class="event-indicator"></view></view></view></view></view>
</template><script>
export default {name: 'HarmonyCalendar',props: {events: {type: Array,default: () => []},selectedDate: {type: Date,default: () => new Date()}},data() {return {currentDate: new Date(),currentMonth: new Date().getMonth(),currentYear: new Date().getFullYear(),weekDays: ['日', '一', '二', '三', '四', '五', '六'],enableAnimation: true,daysArray: []}},watch: {currentMonth: {handler() {this.generateCalendar()},immediate: true}},methods: {generateCalendar() {const firstDay = new Date(this.currentYear, this.currentMonth, 1)const lastDay = new Date(this.currentYear, this.currentMonth + 1, 0)const daysInMonth = lastDay.getDate()const startingDay = firstDay.getDay()let days = []// 上个月的日期const prevMonth = new Date(this.currentYear, this.currentMonth - 1, 1)const daysInPrevMonth = new Date(this.currentYear, this.currentMonth, 0).getDate()for (let i = startingDay - 1; i >= 0; i--) {days.push({date: new Date(prevMonth.getFullYear(), prevMonth.getMonth(), daysInPrevMonth - i),dayNumber: daysInPrevMonth - i,currentMonth: false})}// 当前月的日期for (let i = 1; i <= daysInMonth; i++) {days.push({date: new Date(this.currentYear, this.currentMonth, i),dayNumber: i,currentMonth: true})}// 下个月的日期const remainingDays = 42 - days.length // 保持6行固定高度for (let i = 1; i <= remainingDays; i++) {days.push({date: new Date(this.currentYear, this.currentMonth + 1, i),dayNumber: i,currentMonth: false})}this.daysArray = days},changeMonth(delta) {const newDate = new Date(this.currentYear, this.currentMonth + delta, 1)this.currentMonth = newDate.getMonth()this.currentYear = newDate.getFullYear()this.enableAnimation = truesetTimeout(() => {this.enableAnimation = false}, 300)},isSelected(date) {return date.toDateString() === this.selectedDate.toDateString()},hasEvents(date) {return this.events.some(event => new Date(event.date).toDateString() === date.toDateString())},selectDate(day) {this.$emit('date-selected', day.date)}}
}
</script><style lang="scss">
.harmony-calendar {background-color: #ffffff;border-radius: 24rpx;padding: 32rpx;box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);.calendar-header {margin-bottom: 32rpx;.month-switcher {display: flex;justify-content: space-between;align-items: center;margin-bottom: 24rpx;.current-month {font-size: 32rpx;font-weight: 500;color: #333333;}.iconfont {font-size: 40rpx;color: #666666;padding: 16rpx;&:active {opacity: 0.7;}}}.weekday-header {display: grid;grid-template-columns: repeat(7, 1fr);text-align: center;text {font-size: 28rpx;color: #999999;padding: 16rpx 0;}}}.calendar-body {.days-grid {display: grid;grid-template-columns: repeat(7, 1fr);gap: 8rpx;.day-cell {aspect-ratio: 1;display: flex;flex-direction: column;align-items: center;justify-content: center;position: relative;border-radius: 16rpx;transition: all 0.2s ease-in-out;&.current-month {background-color: #f8f8f8;.day-number {color: #333333;}}&.selected {background-color: #2979ff;.day-number {color: #ffffff;}.event-indicator {background-color: #ffffff;}}.day-number {font-size: 28rpx;color: #999999;}.event-indicator {width: 8rpx;height: 8rpx;border-radius: 4rpx;background-color: #2979ff;position: absolute;bottom: 12rpx;}&:active {transform: scale(0.95);}}}}&.with-animation {.days-grid {animation: fadeInScale 0.3s ease-out;}}
}@keyframes fadeInScale {from {opacity: 0.5;transform: scale(0.95);}to {opacity: 1;transform: scale(1);}
}
</style>
2. 活动列表组件
接下来实现活动列表组件,用于展示选中日期的活动:
<!-- components/schedule-list/schedule-list.vue -->
<template><view class="schedule-list"><view class="list-header"><text class="title">活动安排</text><text class="add-btn" @click="$emit('add')">添加</text></view><view class="list-content"><template v-if="filteredEvents.length"><view v-for="event in filteredEvents" :key="event.id"class="schedule-item":class="{ 'completed': event.completed }"@click="toggleEvent(event)"><view class="event-time">{{ formatTime(event.time) }}</view><view class="event-info"><text class="event-title">{{ event.title }}</text><text class="event-location" v-if="event.location">{{ event.location }}</text></view><text class="iconfont icon-check"></text></view></template><view v-else class="empty-tip"><image src="/static/empty.png" mode="aspectFit"></image><text>暂无活动安排</text></view></view></view>
</template><script>
export default {name: 'ScheduleList',props: {selectedDate: {type: Date,required: true},events: {type: Array,default: () => []}},computed: {filteredEvents() {return this.events.filter(event => new Date(event.date).toDateString() === this.selectedDate.toDateString()).sort((a, b) => new Date(`${a.date} ${a.time}`) - new Date(`${b.date} ${b.time}`))}},methods: {formatTime(time) {return time.slice(0, 5) // 只显示小时和分钟},toggleEvent(event) {this.$emit('toggle', event)}}
}
</script><style lang="scss">
.schedule-list {margin-top: 32rpx;.list-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 24rpx;.title {font-size: 32rpx;font-weight: 500;color: #333333;}.add-btn {font-size: 28rpx;color: #2979ff;padding: 16rpx;&:active {opacity: 0.7;}}}.list-content {.schedule-item {display: flex;align-items: center;padding: 24rpx;background-color: #ffffff;border-radius: 16rpx;margin-bottom: 16rpx;box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);transition: all 0.2s ease-in-out;&.completed {opacity: 0.6;.event-info {text-decoration: line-through;}.icon-check {color: #52c41a;}}.event-time {font-size: 28rpx;color: #666666;margin-right: 24rpx;min-width: 100rpx;}.event-info {flex: 1;.event-title {font-size: 28rpx;color: #333333;margin-bottom: 8rpx;}.event-location {font-size: 24rpx;color: #999999;}}.icon-check {font-size: 40rpx;color: #dddddd;}&:active {transform: scale(0.98);}}.empty-tip {text-align: center;padding: 64rpx 0;image {width: 200rpx;height: 200rpx;margin-bottom: 16rpx;}text {font-size: 28rpx;color: #999999;}}}
}
</style>
3. 使用示例
下面展示如何在页面中使用这两个组件:
<!-- pages/schedule/schedule.vue -->
<template><view class="schedule-page"><harmony-calendar:events="events":selected-date="selectedDate"@date-selected="handleDateSelect"></harmony-calendar><schedule-list:events="events":selected-date="selectedDate"@toggle="toggleEventStatus"@add="showAddEventPopup"></schedule-list><!-- 添加活动弹窗 --><uni-popup ref="addEventPopup" type="bottom"><view class="add-event-form"><input v-model="newEvent.title" placeholder="活动标题" class="input-field" /><input v-model="newEvent.location" placeholder="活动地点" class="input-field" /><picker mode="time" :value="newEvent.time"@change="handleTimeChange"class="input-field"><view>{{ newEvent.time || '选择时间' }}</view></picker><button @click="saveEvent" class="save-btn":disabled="!newEvent.title || !newEvent.time">保存</button></view></uni-popup></view>
</template><script>
import HarmonyCalendar from '@/components/harmony-calendar/harmony-calendar'
import ScheduleList from '@/components/schedule-list/schedule-list'export default {components: {HarmonyCalendar,ScheduleList},data() {return {selectedDate: new Date(),events: [],newEvent: {title: '',location: '',time: '',completed: false}}},methods: {handleDateSelect(date) {this.selectedDate = date},showAddEventPopup() {this.$refs.addEventPopup.open()},handleTimeChange(e) {this.newEvent.time = e.detail.value},saveEvent() {const event = {...this.newEvent,id: Date.now(),date: this.selectedDate}this.events.push(event)this.$refs.addEventPopup.close()// 重置表单this.newEvent = {title: '',location: '',time: '',completed: false}// 提示保存成功uni.showToast({title: '添加成功',icon: 'success'})},toggleEventStatus(event) {const index = this.events.findIndex(e => e.id === event.id)if (index > -1) {this.events[index].completed = !this.events[index].completed}}}
}
</script><style lang="scss">
.schedule-page {padding: 32rpx;min-height: 100vh;background-color: #f5f5f5;
}.add-event-form {background-color: #ffffff;padding: 32rpx;border-radius: 24rpx 24rpx 0 0;.input-field {width: 100%;height: 88rpx;border-bottom: 2rpx solid #f0f0f0;margin-bottom: 24rpx;padding: 0 24rpx;font-size: 28rpx;}.save-btn {margin-top: 32rpx;background-color: #2979ff;color: #ffffff;border-radius: 44rpx;height: 88rpx;line-height: 88rpx;font-size: 32rpx;&:active {opacity: 0.9;}&[disabled] {background-color: #cccccc;}}
}
</style>
性能优化
- 虚拟列表优化
当活动数量较多时,可以使用虚拟列表优化性能:
// 在 schedule-list 组件中添加
import VirtualList from '@/components/virtual-list'export default {components: {VirtualList}// ...其他配置
}
- 状态管理
对于大型应用,建议使用 Vuex 管理日程数据:
// store/modules/schedule.js
export default {state: {events: []},mutations: {ADD_EVENT(state, event) {state.events.push(event)},TOGGLE_EVENT(state, eventId) {const event = state.events.find(e => e.id === eventId)if (event) {event.completed = !event.completed}}},actions: {async fetchEvents({ commit }) {// 从服务器获取数据const events = await api.getEvents()commit('SET_EVENTS', events)}}
}
- 缓存优化
使用本地存储缓存日程数据:
// utils/storage.js
export const saveEvents = (events) => {try {uni.setStorageSync('schedule_events', JSON.stringify(events))} catch (e) {console.error('保存失败:', e)}
}export const getEvents = () => {try {const events = uni.getStorageSync('schedule_events')return events ? JSON.parse(events) : []} catch (e) {console.error('读取失败:', e)return []}
}
适配建议
- 暗黑模式支持
// 在组件样式中添加
.harmony-calendar {&.dark {background-color: #1c1c1e;.calendar-header {.current-month {color: #ffffff;}}.day-cell {&.current-month {background-color: #2c2c2e;.day-number {color: #ffffff;}}}}
}
- 响应式布局
使用 flex 布局和 rpx 单位确保在不同设备上的适配:
.schedule-page {display: flex;flex-direction: column;min-height: 100vh;@media screen and (min-width: 768px) {flex-direction: row;.harmony-calendar {flex: 0 0 400rpx;}.schedule-list {flex: 1;margin-left: 32rpx;}}
}
总结
通过本文的讲解,我们实现了一个功能完整的日历活动安排组件。该组件不仅具有优雅的界面设计,还融入了鸿蒙系统的设计理念,同时考虑了性能优化和多端适配等实际开发中的重要因素。
在实际开发中,我们可以根据具体需求对组件进行扩展,例如:
- 添加重复活动功能
- 实现活动提醒
- 支持多人协作
- 添加数据同步功能
希望这篇文章能够帮助你更好地理解如何在 UniApp 中开发高质量的组件,同时也能为你的实际项目开发提供有价值的参考。
相关文章:
鸿蒙OSUniApp导航栏组件开发:打造清新简约的用户界面#三方框架 #Uniapp
UniApp 开发实战:打造符合鸿蒙设计风格的日历活动安排组件 在移动应用开发中,日历和活动安排是非常常见的需求。本文将详细介绍如何使用 UniApp 框架开发一个优雅的日历活动安排组件,并融入鸿蒙系统的设计理念,实现一个既美观又实…...

力扣HOT100之动态规划:300. 最长递增子序列
这道题之前刷代码随想录的时候也刷过,现在又给忘完了。自己尝试着写了一下,发现怎么写都写不对,直接去看视频了。。我自己写的时候的定义是:考虑下标0 ~ i范围内索赔能取到的最长严格递增子序列的长度,后面发现在写递推…...
EEPROM库详解
EEPROM EEPROM 地址空间: 每个字节有唯一地址(从 0 开始),例如 ATmega328P 的地址范围是 0~1023(共 1KB)。不同型号的 Arduino 板 EEPROM 大小不同(如 Mega2560 为 4KB,地址 0~409…...
JDK21深度解密 Day 10:微服务架构适配JDK21
【JDK21深度解密 Day 10】微服务架构适配JDK21 引言:百万并发时代的微服务进化 作为"JDK21深度解密"系列的第10天,今天我们聚焦微服务架构在JDK21时代的技术跃迁。Java语言历史上最大的一次并发模型革新——虚拟线程(Virtual Threads),正在重塑微服务架构的底…...
Java并发编程实战 Day 2:线程安全与synchronized关键字
【Java并发编程实战 Day 2】线程安全与synchronized关键字 开篇 欢迎来到《Java并发编程实战》系列的第二天!在第一天中,我们学习了Java并发编程的基础知识以及线程模型的核心概念。今天我们将继续深入探讨并发编程中的关键问题——线程安全࿰…...

在win10/11下Node.js安装配置教程
下载安装 官网链接https://nodejs.org/zh-cn 下载好以后双击打开,点击下一步 勾选,然后下一步 选择路径、下一步 下一步 配置环境 找到我们安装的文件夹,创建两个文件夹 node_global node_cache 在CMD中配置路径 npm config set p…...

飞致云开源社区月度动态报告(2025年5月)
自2023年6月起,中国领先的开源软件公司飞致云以月度为单位发布《飞致云开源社区月度动态报告》,旨在向广大社区用户同步飞致云旗下系列开源软件的发展情况,以及当月主要的产品新版本发布、社区运营成果等相关信息。 飞致云开源运营数据概览&…...

压缩包方式在Linux和Windows下安装mongodb
目录 安装流程安装实例1. Linux安装2. Windows安装 总结 安装流程 zip方式安装 优点:自定义性较高,可以自己控制数据、日志等文件的位置 1、下载安装包 2、解压安装包 3、创建各类文件路径 4、配置conf文件 5、使用自定义配置文件启动 安装实例 1. Li…...

智慧场馆:科技赋能的艺术盛宴
智慧场馆作为城市公共服务设施数字化转型的典型代表,通过深度融合新一代信息技术,构建起全方位、智能化的运营管理体系。其功能架构不仅提升了场馆本身的运营效能,更重塑了公共服务体验模式,展现出显著的社会价值和商业潜力。 一…...
flutter常用动画
Flutter 动画基础概念 术语解释Animation表示动画的值,通常是一个 double (0.0 ~ 1.0) 或其他数值。AnimationController管理动画的时间进度和状态。需要 Ticker (vsync) 来驱动。Tween定义动画的取值范围,如从 0.0 到 1.0,从红色到蓝色。Cu…...
Windows10下使用QEMU安装Ubuntu20.04虚拟机,并启用硬件加速
Windows10下使用QEMU安装Ubuntu20.04虚拟机,并启用硬件加速 作者将狼才鲸创建日期2025-05-30 CSDN阅读地址:Windows10下使用QEMU安装Ubuntu20.04虚拟机,并启用硬件加速 本文档源码地址:Windows10下使用QEMU安装Ubuntu20.04虚拟机…...

《ChatGPT o3抗命:AI失控警钟还是成长阵痛?》
ChatGPT o3 “抗命” 事件起底 在人工智能的飞速发展进程中,OpenAI 于 2025 年推出的 ChatGPT o3 推理模型,犹如一颗重磅炸弹投入了技术的海洋,激起千层浪。它被视为 “推理模型” 系列的巅峰之作,承载着赋予 ChatGPT 更强大问题解…...
题目 3293: 蓝桥杯2024年第十五届决赛真题-数位翻转
题目 3293: 蓝桥杯2024年第十五届决赛真题-数位翻转 时间限制: 2s 内存限制: 192MB 提交: 1046 解决: 318 题目描述 小明创造了一个函数 f(x) 用来翻转 x 的二进制的数位(无前导 0)。比如f(11) 13,因为 11 (1011)2,将其左右翻转…...
Reactor 和 Preactor
Reactor 和 Preactor 是两个在工业控制、生产调度和事件驱动系统中非常重要的设计模式或框架,不少人会用这两个名词来描述不同的编程思想或技术架构。 一、Reactor 模式(反应器模式) 1. 概述 Reactor 模式其实是一种I/O事件通知的设计思想…...

【sa-token】 sa-token非 web 上下文无法获取 HttpServletRequest。
Springboot cloud gateway集成sa-token中报错 cn.dev33.satoken.exception.NotWebContextException: 非 web 上下文无法获取 HttpServletRequestat cn.dev33.satoken.spring.SpringMVCUtil.getRequest(SpringMVCUtil.java:45) ~[sa-token-spring-boot-starter-1.38.0.jar:?]官…...
论爱情《态度》
我犹记得,当吴军的《态度》到手之后,从中间翻开的第一页,便是此。 “合适的人,会让你看到,和得到全世界” -- 第22封 其实在我初中、高中的时候,我便产生一个问题,为什么学校要禁止谈恋爱。 …...

多台电脑共用一个ip地址可以吗?会怎么样
在互联网使用日益普及的今天,许多人都面临着多台设备共享网络的需求。一个常见的问题随之而来:多台电脑共用一个IP地址可以吗?这样做会带来哪些影响?本文将深入探讨这一话题。 一、多台电脑共用一个IP地址可以吗? 多…...

线程(上)【Linux操作系统】
文章目录 线程概念及其相关知识线程的概念及一些重要认识重要认识Linux中线程的实现Linux中的被调度的执行流是被task_struct描述的 线程是如何瓜分进程的代码和数据的?对于数据:对于代码: 线程的优点线程的缺点线程调度细节调度:…...
FPGA中的“BPI“指什么
在FPGA(现场可编程门阵列)中,BPI 的全称是 “Byte Peripheral Interface” 或 “Bank Parallel Interface”,具体指一种 并行NOR闪存配置接口,主要用于FPGA的配置(Configuration)过程。以下是BP…...
Splunk Validated Architecture (SVA):构建企业级可观测性与安全的基石
Splunk Validated Architecture (SVA) 是 Splunk 官方提供的一套经过严格测试、性能验证和最佳实践指导的参考架构蓝图。它并非单一固定方案,而是根据企业数据规模、性能需求、高可用性目标和合规要求,提供一系列可落地的部署模型。SVA 的核心价值在于为…...
Python爬虫(40)基于Selenium与ScrapyRT构建高并发动态网页爬虫架构:原理、实现与性能优化
目录 一、引言二、技术背景1. 动态页面处理痛点2. 架构设计目标 三、核心组件详解1. Selenium Grid集群部署2. ScrapyRT服务化改造3. 智能等待策略 四、系统架构图五、性能优化实践1. 资源隔离策略2. 并发控制算法3. 监控体系 六、总结与展望🌈Python爬虫相关文章&a…...
深入解析 Python 字典:从基础到高级应用
文章大纲 引言:什么是字典? 在 Python 编程中,字典(Dictionary)是一种极其重要的数据结构,它以键值对(key-value pair)的形式存储数据,能够高效地进行数据的查找和操作。相比于列表(List)这种依赖整数索引的序列类型,字典通过自定义的键来访问对应的值,提供了更…...

进程同步:生产者-消费者 题目
正确答案: 问题类型: 经典生产者 - 消费者问题 同时涉及同步和互斥。 同步:生产者与消费者通过信号量协调生产 / 消费节奏(如缓冲区满时生产者等待,空时消费者等待)。互斥:对共享缓冲区的访问需…...
Linux轻量级文件传输——tftp命令
摘要 TFTP是基于UDP/69端口的轻量文件传输协议。本文整理tftp命令参数/交互命令,提供示例,涵盖文件上传下载、模式设置等核心操作,帮助快速掌握基础文件传输。 一、TFTP核心特性 tftp(Trivial File Transfer Protocol࿰…...
JavaSwing之--为组件添加背景
JavaSwing之–为组件添加背景 从实践角度,可以把Java Swing中的组件分为容器组件和普通组件,容器组件是为了更好的按照某种布局摆放各种组件,形成功能强大且友好的界面。 Swing中组件的背景可以分为两种类型,一种是背景色&#…...
MySQL项目实战演练:搭建用户管理系统的完整数据库结构【MySQL系列】
本项目适用于后台管理系统、电商用户中心、SaaS 用户模块等场景,特别适合开发者进行实战演练与面试准备。 一、项目背景与需求概述 我们将构建一个基础版的用户管理系统,具备以下业务功能: 用户注册与登录用户角色与权限分配日志记录与用户…...

展会聚焦丨漫途科技亮相2025西北水务博览会!
2025第三届西北水务数字化发展论坛暨供排水节水灌溉新技术设备博览会在兰州甘肃国际会展中心圆满落幕。本届展会以“科技赋能水资源,数智引领新动能”为主题,活动汇集水务集团、科研院所、技术供应商等全产业链参与者,旨在通过前沿技术展示与…...

【数据结构初阶】顺序表的应用
文章目录 顺序表的应用基于动态顺序表实现通讯录前言1.定义联系人数据2.给顺序表改名3.通讯录的初始化4.通讯录的销毁5.通讯录添加数据6.通讯录删除数据7.通讯录修改数据8.通讯录查找数据9.展示通讯录数据10.通讯录的最终实现 顺序表的应用 基于动态顺序表实现通讯录 前言 功…...
抽象工厂模式与策略模式结合使用小案例
目录 1.前言 1.前言 上一篇章就通过简单的案例来了解抽象工厂模式和策略模式的使用,现在就用个支付场景的小案例来演示两者设计模式的联合使用;...

C#数字图像处理(一)
文章目录 1.C#图像处理基础1.1 Bitmap类1.2 Bitmapdata类1.3 Graphics类1.4 Image类 2.彩色图像灰度化1.提取像素法2.内存法3.指针法三种方法的比较4.灰度图像二值化: 3.相关链接 Bitmap类、 Bitmapdata类和 Graphics类是C#图像处理中最重要的3个类,如果要用C# 进行…...