如果你想做一个养蜂记录App,需要用到Preferences和日历标记
欢迎大家去鸿蒙应用市场搜索下载「蜂勤录」,体验一下这款养蜂记录工具。你可以在里面标记巢脾状态、管理蜂王日历、统计产蜜量,把每个蜂箱的情况都掌握在手中。
写在前面
养蜂是一件需要耐心和细心的事情。一个蜂群的健康状况、蜂王的产卵情况、巢脾的储蜜进度,这些都需要定期检查和记录。如果你养了不止一箱蜂,那记录就更重要了 -- 哪箱蜂上脾快、哪箱蜂有分蜂迹象、哪箱蜂该取蜜了,全靠平时的记录来追踪。
蜂王的日历也是一个重要的功能。蜂王什么时候羽化、什么时候开始产卵、什么时候该更换,这些时间节点都需要记录。特别是蜂王的年龄 -- 一只蜂王一般能用1-2年,超过两年产卵力就会下降,需要及时更换。
还有产蜜量统计。每次取蜜多少斤、什么花期的蜜、品质如何,这些数据积累下来,你就能分析出一年中哪个花期产蜜最多、哪箱蜂产量最高。
这就是「蜂勤录」这个App要做的三件事:巢脾状态标记、蜂王日历、产蜜量统计。
这篇文章聊什么
这篇文章主要聊三件事:
- 日历标记的数据结构 -- 怎么在日历上标记有检查记录的日期,以及当天的巢脾状态。
- 蜂王信息的日历管理 -- 怎么记录蜂王的关键时间节点(羽化、产卵、更换),并在日历上显示。
- 产蜜量的统计计算 -- 怎么按月份、按蜂箱统计产蜜量,并计算年度总产量。
第一步:巢脾状态标记和日历
巢脾是蜂箱里蜜蜂用来储蜜、储粉和育虫的蜡质结构。每次开箱检查的时候,我们需要记录每个巢脾的状态:是储蜜脾、储粉脾、育虫脾还是空脾。
React版本:巢脾状态和日历
// 巢脾状态枚举
const COMB_STATUS = {
HONEY: 'honey', // 储蜜脾
POLLEN: 'pollen', // 储粉脾
BROOD: 'brood', // 育虫脾
EMPTY: 'empty', // 空脾
CAPPED: 'capped', // 封盖脾
};
// 日历标记数据结构
const defaultCalendarMarks = {
'2024-03-15': { checked: true, frames: 10, honeyFrames: 4, broodFrames: 3, note: '蜂群状态良好' },
'2024-03-22': { checked: true, frames: 10, honeyFrames: 5, broodFrames: 4, note: '储蜜增加' },
};
function CombCalendar() {
const [marks, setMarks] = useState(() => {
const saved = localStorage.getItem('comb_calendar');
return saved ? JSON.parse(saved) : defaultCalendarMarks;
});
const [selectedDate, setSelectedDate] = useState('2024-03-15');
const markDate = (date, data) => {
setMarks(prev => {
const updated = { ...prev, [date]: data };
localStorage.setItem('comb_calendar', JSON.stringify(updated));
return updated;
});
};
// 获取当前月份的日历数据
const getCalendarDays = (year, month) => {
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const days = [];
for (let d = 1; d <= lastDay.getDate(); d++) {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
days.push({
date: dateStr,
day: d,
isMarked: !!marks[dateStr],
mark: marks[dateStr] || null,
});
}
return days;
};
return (
<div className="comb-calendar">
<h3>巢脾检查日历</h3>
{/* 日历网格和标记详情 */}
</div>
);
}
日历标记的数据结构是一个对象,key是日期字符串(YYYY-MM-DD格式),value是当天的检查记录。这种结构的好处是,查找某一天是否有记录只需要marks[dateStr],非常高效。
ArkTS版本:巢脾状态和日历
import { preferences } from '@kit.ArkData';
// 巢脾检查记录
interface CombCheckRecord {
checked: boolean;
totalFrames: number; // 总巢框数
honeyFrames: number; // 储蜜框数
pollenFrames: number; // 储粉框数
broodFrames: number; // 育虫框数
emptyFrames: number; // 空框数
note: string;
}
// 日历标记类型
type CalendarMarks = Record<string, CombCheckRecord>;
let dataStore: preferences.Preferences | null = null;
@Entry
@Component
struct CombCalendarPage {
@State marks: CalendarMarks = {};
@State currentYear: number = 2024;
@State currentMonth: number = 2; // 0-indexed, 2 = 三月
@State selectedDate: string = '';
@State editTotal: number = 10;
@State editHoney: number = 4;
@State editBrood: number = 3;
@State editPollen: number = 1;
@State editNote: string = '';
async aboutToAppear() {
const now = new Date();
this.currentYear = now.getFullYear();
this.currentMonth = now.getMonth();
try {
dataStore = await preferences.getPreferences(getContext(this), 'fengqinlu_store');
const json = await dataStore.get('comb_calendar', '{}') as string;
if (json !== '{}') {
this.marks = JSON.parse(json) as CalendarMarks;
}
} catch (err) {
console.error('加载巢脾数据失败', JSON.stringify(err));
}
}
// 生成日历天数
getCalendarDays(): { date: string; day: number; isMarked: boolean }[] {
const firstDay = new Date(this.currentYear, this.currentMonth, 1);
const lastDay = new Date(this.currentYear, this.currentMonth + 1, 0);
const days: { date: string; day: number; isMarked: boolean }[] = [];
// 前面的空白填充
for (let i = 0; i < firstDay.getDay(); i++) {
days.push({ date: '', day: 0, isMarked: false });
}
for (let d = 1; d <= lastDay.getDate(); d++) {
const dateStr = `${this.currentYear}-${String(this.currentMonth + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
days.push({
date: dateStr,
day: d,
isMarked: !!this.marks[dateStr],
});
}
return days;
}
// 保存检查记录
async saveCheck() {
if (this.selectedDate === '') return;
const record: CombCheckRecord = {
checked: true,
totalFrames: this.editTotal,
honeyFrames: this.editHoney,
pollenFrames: this.editPollen,
broodFrames: this.editBrood,
emptyFrames: this.editTotal - this.editHoney - this.editPollen - this.editBrood,
note: this.editNote,
};
this.marks = { ...this.marks, [this.selectedDate]: record };
if (dataStore) {
await dataStore.put('comb_calendar', JSON.stringify(this.marks));
await dataStore.flush();
}
this.editNote = '';
}
// 选择日期
selectDate(dateStr: string) {
this.selectedDate = dateStr;
if (this.marks[dateStr]) {
const record = this.marks[dateStr];
this.editTotal = record.totalFrames;
this.editHoney = record.honeyFrames;
this.editBrood = record.broodFrames;
this.editPollen = record.pollenFrames;
this.editNote = record.note;
} else {
this.editTotal = 10;
this.editHoney = 4;
this.editBrood = 3;
this.editPollen = 1;
this.editNote = '';
}
}
// 切换月份
prevMonth() {
if (this.currentMonth === 0) {
this.currentMonth = 11;
this.currentYear--;
} else {
this.currentMonth--;
}
}
nextMonth() {
if (this.currentMonth === 11) {
this.currentMonth = 0;
this.currentYear++;
} else {
this.currentMonth++;
}
}
build() {
Scroll() {
Column() {
Text('巢脾检查日历')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 月份导航
Row() {
Button('<')
.width(50)
.onClick(() => { this.prevMonth(); })
Text(`${this.currentYear}年${this.currentMonth + 1}月`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Button('>')
.width(50)
.onClick(() => { this.nextMonth(); })
}
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 12 })
// 星期标题
Row() {
ForEach(['日', '一', '二', '三', '四', '五', '六'], (day: string) => {
Text(day)
.fontSize(13)
.fontColor('#999999')
.layoutWeight(1)
.textAlign(TextAlign.Center)
}, (day: string) => day)
}
.width('90%')
.margin({ bottom: 4 })
// 日历网格
Grid() {
ForEach(this.getCalendarDays(), (item: { date: string; day: number; isMarked: boolean }) => {
if (item.date === '') {
GridItem() {
Text('')
.fontSize(14)
.width('100%')
.height(40)
}
} else {
GridItem() {
Text(`${item.day}`)
.fontSize(14)
.width('100%')
.height(40)
.textAlign(TextAlign.Center)
.fontColor(this.selectedDate === item.date ? '#FFFFFF' : '#333333')
.backgroundColor(
this.selectedDate === item.date ? '#2196F3' :
item.isMarked ? '#E8F5E9' : '#FFFFFF'
)
.borderRadius(20)
.onClick(() => { this.selectDate(item.date); })
}
}
}, (item: { date: string; day: number }, index: number) => `${index}`)
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
.columnsGap(4)
.rowsGap(4)
.width('90%')
.margin({ bottom: 20 })
// 选中日期的编辑区域
if (this.selectedDate !== '') {
Column() {
Text(`${this.selectedDate} 巢脾检查`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 })
// 总巢框数
Row() {
Text('总巢框')
.fontSize(14)
.width(80)
Text(`${this.editTotal} 框`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.margin({ left: 8, right: 8 })
Slider({ min: 1, max: 20, step: 1, value: this.editTotal, style: SliderStyle.OutSet })
.layoutWeight(1)
.onChange((value: number) => { this.editTotal = value; })
}
.width('100%')
.margin({ bottom: 8 })
// 储蜜框
Row() {
Text('储蜜框')
.fontSize(14)
.width(80)
Text(`${this.editHoney} 框`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#FF9800')
.margin({ left: 8, right: 8 })
Slider({ min: 0, max: this.editTotal, step: 1, value: this.editHoney, style: SliderStyle.OutSet })
.layoutWeight(1)
.onChange((value: number) => { this.editHoney = value; })
}
.width('100%')
.margin({ bottom: 8 })
// 育虫框
Row() {
Text('育虫框')
.fontSize(14)
.width(80)
Text(`${this.editBrood} 框`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#4CAF50')
.margin({ left: 8, right: 8 })
Slider({ min: 0, max: this.editTotal, step: 1, value: this.editBrood, style: SliderStyle.OutSet })
.layoutWeight(1)
.onChange((value: number) => { this.editBrood = value; })
}
.width('100%')
.margin({ bottom: 8 })
// 储粉框
Row() {
Text('储粉框')
.fontSize(14)
.width(80)
Text(`${this.editPollen} 框`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#FFD700')
.margin({ left: 8, right: 8 })
Slider({ min: 0, max: this.editTotal, step: 1, value: this.editPollen, style: SliderStyle.OutSet })
.layoutWeight(1)
.onChange((value: number) => { this.editPollen = value; })
}
.width('100%')
.margin({ bottom: 8 })
// 备注
TextArea({ placeholder: '检查备注...', text: this.editNote })
.width('100%')
.height(60)
.onChange((value: string) => { this.editNote = value; })
.margin({ bottom: 12 })
Button('保存检查记录')
.width('100%')
.backgroundColor('#4CAF50')
.fontColor('#FFFFFF')
.onClick(() => { this.saveCheck(); })
}
.width('90%')
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
}
}
.width('100%')
.padding(20)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
日历网格用了Grid组件,columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')定义了7等分的列布局,正好对应一周7天。getCalendarDays()方法生成当月的天数数组,包括前面的空白填充(让第一天对齐到正确的星期位置)。
有标记的日期用浅绿色背景显示,选中的日期用蓝色背景 + 白色文字显示。点击某个日期后,下方会显示编辑区域,可以填写当天的巢脾检查数据。
第二步:蜂王日历
蜂王是蜂群的核心。记录蜂王的关键时间节点,对于蜂群管理非常重要。
React版本:蜂王日历
const defaultQueens = [
{
id: '1',
hiveName: '1号箱',
queenBreed: '意蜂',
emergenceDate: '2024-03-01', // 羽化日期
firstEggDate: '2024-03-15', // 首次产卵日期
marked: true, // 是否标记
markColor: '#FF0000', // 标记颜色
notes: '产卵积极',
},
];
function QueenCalendar() {
const [queens, setQueens] = useState(() => {
const saved = localStorage.getItem('queen_calendar');
return saved ? JSON.parse(saved) : defaultQueens;
});
const updateQueen = (id, field, value) => {
setQueens(prev => {
const updated = prev.map(q => q.id === id ? { ...q, [field]: value } : q);
localStorage.setItem('queen_calendar', JSON.stringify(updated));
return updated;
});
};
// 计算蜂王年龄(天数)
const getQueenAge = (emergenceDate) => {
return Math.floor((Date.now() - new Date(emergenceDate).getTime()) / (1000 * 60 * 60 * 24));
};
return (
<div className="queen-calendar">
<h3>蜂王日历</h3>
{queens.map(queen => (
<div key={queen.id} className="queen-card">
<h4>{queen.hiveName} - {queen.queenBreed}</h4>
<p>蜂王年龄:{getQueenAge(queen.emergenceDate)}天</p>
<p>羽化日期:{queen.emergenceDate}</p>
<p>首次产卵:{queen.firstEggDate}</p>
</div>
))}
</div>
);
}
ArkTS版本:蜂王日历
interface QueenInfo {
id: string;
hiveName: string;
breed: string;
emergenceDate: string;
firstEggDate: string;
marked: boolean;
markColor: string;
notes: string;
}
@Entry
@Component
struct QueenCalendarPage {
@State queens: QueenInfo[] = [];
@State selectedQueenId: string = '';
@State editHiveName: string = '';
@State editBreed: number = 0;
@State editEmergence: string = '';
@State editFirstEgg: string = '';
@State editNotes: string = '';
private breedOptions: string[] = ['意蜂', '中蜂', '卡蜂', '东北黑蜂'];
async aboutToAppear() {
if (!dataStore) return;
try {
const json = await dataStore.get('queen_calendar', '') as string;
if (json !== '') {
this.queens = JSON.parse(json) as QueenInfo[];
}
} catch (err) {
console.error('加载蜂王数据失败', JSON.stringify(err));
}
}
// 计算蜂王年龄
getQueenAge(emergenceDate: string): number {
return Math.floor((Date.now() - new Date(emergenceDate).getTime()) / (1000 * 60 * 60 * 24));
}
// 获取蜂王状态评估
getQueenStatus(queen: QueenInfo): { text: string; color: string } {
const age = this.getQueenAge(queen.emergenceDate);
if (age < 30) return { text: '新王', color: '#4CAF50' };
if (age < 365) return { text: '壮年', color: '#2196F3' };
if (age < 730) return { text: '老龄', color: '#FF9800' };
return { text: '建议更换', color: '#F44336' };
}
get selectedQueen(): QueenInfo | undefined {
return this.queens.find((q: QueenInfo) => q.id === this.selectedQueenId);
}
// 添加蜂王
async addQueen() {
const queen: QueenInfo = {
id: Date.now().toString(),
hiveName: this.editHiveName,
breed: this.breedOptions[this.editBreed],
emergenceDate: this.editEmergence,
firstEggDate: this.editFirstEgg,
marked: false,
markColor: '#FF0000',
notes: this.editNotes,
};
this.queens = [...this.queens, queen];
if (dataStore) {
await dataStore.put('queen_calendar', JSON.stringify(this.queens));
await dataStore.flush();
}
this.editHiveName = '';
this.editEmergence = '';
this.editFirstEgg = '';
this.editNotes = '';
}
build() {
Scroll() {
Column() {
Text('蜂王日历')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 添加蜂王表单
Column() {
Text('添加蜂王')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 })
TextInput({ placeholder: '蜂箱名称', text: this.editHiveName })
.width('100%')
.onChange((value: string) => { this.editHiveName = value; })
.margin({ bottom: 8 })
Row() {
Text('蜂种')
.fontSize(14)
.width(60)
ForEach(this.breedOptions, (breed: string, index: number) => {
Text(breed)
.fontSize(13)
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
.backgroundColor(this.editBreed === index ? '#FF9800' : '#E0E0E0')
.fontColor(this.editBreed === index ? '#FFFFFF' : '#333333')
.borderRadius(14)
.onClick(() => { this.editBreed = index; })
}, (_breed: string, index: number) => `${index}`)
}
.width('100%')
.margin({ bottom: 8 })
TextInput({ placeholder: '羽化日期 YYYY-MM-DD', text: this.editEmergence })
.width('100%')
.onChange((value: string) => { this.editEmergence = value; })
.margin({ bottom: 8 })
TextInput({ placeholder: '首次产卵日期 YYYY-MM-DD', text: this.editFirstEgg })
.width('100%')
.onChange((value: string) => { this.editFirstEgg = value; })
.margin({ bottom: 8 })
TextArea({ placeholder: '备注...', text: this.editNotes })
.width('100%')
.height(50)
.onChange((value: string) => { this.editNotes = value; })
.margin({ bottom: 12 })
Button('添加蜂王')
.width('100%')
.backgroundColor('#FF9800')
.fontColor('#FFFFFF')
.onClick(() => { this.addQueen(); })
}
.width('90%')
.padding(16)
.backgroundColor('#FFF3E0')
.borderRadius(12)
.margin({ bottom: 20 })
// 蜂王列表
Text('蜂王列表')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.width('90%')
.margin({ bottom: 12 })
ForEach(this.queens, (queen: QueenInfo) => {
Column() {
Row() {
Text(queen.hiveName)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text(this.getQueenStatus(queen).text)
.fontSize(13)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.backgroundColor(this.getQueenStatus(queen).color)
.fontColor('#FFFFFF')
.borderRadius(10)
}
.width('100%')
.margin({ bottom: 8 })
Row() {
Text(`蜂种:${queen.breed}`)
.fontSize(13)
.fontColor('#666666')
.layoutWeight(1)
Text(`年龄:${this.getQueenAge(queen.emergenceDate)}天`)
.fontSize(13)
.fontColor('#999999')
}
.width('100%')
.margin({ bottom: 4 })
if (queen.notes) {
Text(queen.notes)
.fontSize(13)
.fontColor('#333333')
.width('100%')
}
}
.width('90%')
.padding(12)
.backgroundColor('#FAFAFA')
.borderRadius(8)
.margin({ bottom: 8 })
.alignItems(HorizontalAlign.Start)
}, (queen: QueenInfo) => queen.id)
}
.width('100%')
.padding(20)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
蜂王状态评估是根据年龄来判断的:30天以内是"新王",1年以内是"壮年",1-2年是"老龄",超过2年建议更换。这个评估标准是养蜂界的通用经验值。
第三步:产蜜量统计
React版本
function HoneyStats() {
const [records, setRecords] = useState(() => {
const saved = localStorage.getItem('honey_records');
return saved ? JSON.parse(saved) : [];
});
const addRecord = (record) => {
const updated = [{ ...record, id: Date.now().toString() }, ...records];
setRecords(updated);
localStorage.setItem('honey_records', JSON.stringify(updated));
};
// 按月统计
const monthlyStats = records.reduce((acc, r) => {
const month = r.date.substring(0, 7);
if (!acc[month]) acc[month] = 0;
acc[month] += r.amount;
return acc;
}, {});
const totalHoney = records.reduce((sum, r) => sum + r.amount, 0);
return (
<div className="honey-stats">
<h3>产蜜量统计</h3>
<p>年度总产量:{totalHoney} 斤</p>
{/* 月度统计和记录列表 */}
</div>
);
}
ArkTS版本:产蜜量统计
interface HoneyRecord {
id: string;
date: string;
hiveName: string;
amount: number; // 产量,斤
flowerSource: string; // 蜜源
quality: string; // 品质:优、良、中
}
@Entry
@Component
struct HoneyStatsPage {
@State records: HoneyRecord[] = [];
@State recordHive: string = '';
@State recordAmount: number = 5;
@State recordFlower: string = '';
@State recordQuality: number = 0;
private qualityOptions: string[] = ['优', '良', '中'];
async aboutToAppear() {
if (!dataStore) return;
try {
const json = await dataStore.get('honey_records', '[]') as string;
this.records = JSON.parse(json) as HoneyRecord[];
} catch (err) {
console.error('加载产蜜记录失败', JSON.stringify(err));
}
}
// 按月统计
getMonthlyStats(): { month: string; total: number }[] {
const stats: Record<string, number> = {};
this.records.forEach((r: HoneyRecord) => {
const month = r.date.substring(0, 7);
if (!stats[month]) stats[month] = 0;
stats[month] += r.amount;
});
return Object.entries(stats)
.map(([month, total]) => ({ month, total }))
.sort((a, b) => a.month.localeCompare(b.month));
}
// 年度总产量
getTotalHoney(): number {
return this.records.reduce((sum: number, r: HoneyRecord) => sum + r.amount, 0);
}
async addRecord() {
const record: HoneyRecord = {
id: Date.now().toString(),
date: new Date().toISOString().split('T')[0],
hiveName: this.recordHive,
amount: this.recordAmount,
flowerSource: this.recordFlower,
quality: this.qualityOptions[this.recordQuality],
};
this.records = [record, ...this.records];
if (dataStore) {
await dataStore.put('honey_records', JSON.stringify(this.records));
await dataStore.flush();
}
this.recordHive = '';
this.recordFlower = '';
}
build() {
Scroll() {
Column() {
Text('产蜜量统计')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 年度总产量
Row() {
Column() {
Text(`${this.getTotalHoney()}`)
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor('#FF9800')
Text('年度总产量(斤)')
.fontSize(13)
.fontColor('#999999')
}
.layoutWeight(1)
.padding(16)
.backgroundColor('#FFF3E0')
.borderRadius(12)
Column() {
Text(`${this.records.length}`)
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor('#4CAF50')
Text('取蜜次数')
.fontSize(13)
.fontColor('#999999')
}
.layoutWeight(1)
.padding(16)
.backgroundColor('#E8F5E9')
.borderRadius(12)
}
.width('90%')
.space(12)
.margin({ bottom: 20 })
// 月度统计
if (this.getMonthlyStats().length > 0) {
Text('月度统计')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.width('90%')
.margin({ bottom: 12 })
ForEach(this.getMonthlyStats(), (stat: { month: string; total: number }) => {
Row() {
Text(stat.month)
.fontSize(14)
.fontColor('#666666')
.layoutWeight(1)
Text(`${stat.total} 斤`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#FF9800')
}
.width('90%')
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(6)
.margin({ bottom: 4 })
}, (stat: { month: string }) => stat.month)
}
// 添加记录
Column() {
Text('记录取蜜')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 })
TextInput({ placeholder: '蜂箱名称', text: this.recordHive })
.width('100%')
.onChange((value: string) => { this.recordHive = value; })
.margin({ bottom: 8 })
Row() {
Text('产量')
.fontSize(14)
.width(60)
Text(`${this.recordAmount} 斤`)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.margin({ left: 8, right: 8 })
Slider({ min: 0.5, max: 50, step: 0.5, value: this.recordAmount, style: SliderStyle.OutSet })
.layoutWeight(1)
.onChange((value: number) => { this.recordAmount = value; })
}
.width('100%')
.margin({ bottom: 8 })
TextInput({ placeholder: '蜜源(如:油菜花)', text: this.recordFlower })
.width('100%')
.onChange((value: string) => { this.recordFlower = value; })
.margin({ bottom: 8 })
Row() {
Text('品质')
.fontSize(14)
.width(60)
ForEach(this.qualityOptions, (option: string, index: number) => {
Text(option)
.fontSize(14)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.recordQuality === index ? '#FF9800' : '#E0E0E0')
.fontColor(this.recordQuality === index ? '#FFFFFF' : '#333333')
.borderRadius(16)
.onClick(() => { this.recordQuality = index; })
}, (_option: string, index: number) => `${index}`)
}
.width('100%')
.margin({ bottom: 12 })
Button('记录取蜜')
.width('100%')
.backgroundColor('#FF9800')
.fontColor('#FFFFFF')
.onClick(() => { this.addRecord(); })
}
.width('90%')
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
.margin({ top: 16, bottom: 16 })
}
.width('100%')
.padding(20)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
}
}
月度统计的实现用了reduce来按月份分组求和,然后用Object.entries把对象转成数组,最后按月份排序。这样用户就能看到每个月的产蜜量变化趋势。
流程图
flowchart TD
A[用户打开App] --> B[加载日历标记数据]
B --> C[显示当月日历]
C --> D[用户点击某一天]
D --> E{该天是否有记录?}
E -->|有| F[加载已有记录到表单]
E -->|没有| G[显示空白表单]
F --> H[用户编辑巢脾数据]
G --> H
H --> I[保存到Preferences]
I --> J[日历上标记该天]
A --> K[用户进入蜂王日历]
K --> L[显示蜂王列表]
L --> M[用户添加新蜂王]
M --> N[填写蜂王信息]
N --> O[保存到Preferences]
O --> P[计算蜂王年龄和状态]
A --> Q[用户进入产蜜统计]
Q --> R[显示年度总产量]
R --> S[显示月度统计]
S --> T[用户记录取蜜]
T --> U[保存到Preferences]
U --> V[更新统计数据]
React vs ArkTS 对比表
| 功能点 | React (Web) | ArkTS (鸿蒙) |
|---|---|---|
| 日历网格 | CSS Grid | Grid组件 + columnsTemplate |
| 日期对象操作 | Date原生API | Date原生API(一致) |
| Record类型 | 普通对象 | Record<string, T> 类型 |
| 月份切换 | setState更新 | 直接修改@State变量 |
| 分组统计 | reduce + Object.entries | reduce + Object.entries(一致) |
| 状态标签 | 条件className | 条件backgroundColor |
总结
这篇文章我们用「蜂勤录」这个养蜂记录App,学习了三个功能的实现:
-
日历标记:用Grid组件构建7列日历网格,用Record<string, CombCheckRecord>存储每天的检查数据,有标记的日期用不同背景色显示。
-
蜂王日历:记录蜂王的关键信息(品种、羽化日期、首次产卵日期),根据年龄自动评估蜂王状态,用不同颜色的标签显示。
-
产蜜量统计:用reduce按月分组求和,用卡片式布局显示年度总产量和取蜜次数,月度统计按时间排序展示。
如果你对养蜂感兴趣,欢迎去鸿蒙应用市场搜索「蜂勤录」下载体验。