HarmonyOS智慧农业管理应用开发教程--高高种地--第10篇:农事记录与操作管理
通过本篇教程,你将学会:完成本教程后,你将拥有完整的农事记录管理功能。在实现农事记录管理功能之前,我们需要了解农事操作数据模型的结构。农事操作模型已在中定义。文件位置:(第163-192行)操作说明:模型设计要点:农事记录作为地块的一个属性存在,这样设计的好处是:农事操作的相关方法已在中实现。让我们查看这些方法。文件位置:核心方法:服务设计要点:农事记录列表页面用于展示和管理农事操作记录,支持筛选
第10篇:农事记录与操作管理
教程目标
通过本篇教程,你将学会:
- 理解农事操作数据模型设计
- 实现农事记录的增删改查
- 创建农事记录列表页面
- 创建添加农事记录页面
- 集成天气服务获取智能农事建议
- 实现农事记录编辑功能
- 管理农事记录中的图片和成本信息
完成本教程后,你将拥有完整的农事记录管理功能。
一、农事操作数据模型
在实现农事记录管理功能之前,我们需要了解农事操作数据模型的结构。农事操作模型已在 ProfessionalAgricultureModels.ets 中定义。
1.1 查看农事操作数据模型
文件位置:entry/src/main/ets/models/ProfessionalAgricultureModels.ets(第163-192行)
操作说明:
- 打开
entry/src/main/ets/models/ProfessionalAgricultureModels.ets - 查看
FarmOperation接口的定义
/**
* 农事操作记录
* 记录在地块上进行的各种农事操作
*/
export interface FarmOperation {
id: string; // 操作记录唯一标识
fieldId: string; // 关联地块ID
cropId?: string; // 关联作物ID(可选)
operationType: string; // 操作类型:整地/播种/施肥/灌溉/植保/除草/收获等
date: number; // 操作日期时间戳
operator: string; // 操作人
details: string; // ���作详细说明
materials?: MaterialUsage[]; // 使用的物资(可选)
laborHours?: number; // 用工工时(可选)
machineryUsed?: string[]; // 使用的机械(可选)
cost?: number; // 成本(元)
images?: ImageInfo[]; // 现场照片(可选)
weather?: string; // 天气情况(可选)
notes?: string; // 备注信息(可选)
createdAt: number; // 创建时间
}
/**
* 物资使用记录
* 记录农事操作中使用的物资信息
*/
export interface MaterialUsage {
materialName: string; // 物资名称
quantity: number; // 使用数量
unit: string; // 单位
unitPrice?: number; // 单价(元)
totalCost?: number; // 总成本(元)
}
模型设计要点:
| 设计要点 | 说明 |
|---|---|
| 时间存储 | 使用时间戳(number)存储日期,便于排序和计算 |
| 可选字段 | 使用 ? 标记非必填字段(如cropId、materials、cost) |
| 关联关系 | 通过 fieldId 与地块关联,通过 cropId 与作物关联 |
| 灵活扩展 | 支持物资记录、用工工时、机械使用等扩展信息 |
1.2 数据关系说明
FieldInfo(地块信息)
├── operations: FarmOperation[] // 农事操作记录数组
├── costRecords: CostRecord[] // 成本记录数组
└── salesRecords: SalesRecord[] // 销售记录数组
农事记录作为地块的一个属性存在,这样设计的好处是:
- 数据关联清晰,可以直接通过地块获取所有农事记录
- 便于按地块进行数据统计和分析
- 支持离线场景下的数据管理
二、农事操作服务
农事操作的相关方法已在 FieldService 中实现。让我们查看这些方法。
2.1 FieldService 中的农事操作方法
文件位置:entry/src/main/ets/services/FieldService.ets
核心方法:
/**
* 添加农事记录
* @param fieldId 地块ID
* @param operation 农事操作对象
* @returns Promise<boolean> 成功返回true,失败返回false
*/
async addFarmOperation(fieldId: string, operation: FarmOperation): Promise<boolean> {
const field = await this.getFieldById(fieldId);
if (field) {
// 初始化operations数组
if (!field.operations) {
field.operations = [];
}
// 添加操作记录
field.operations.push(operation);
// 更新地块信息
return await this.updateField(field);
}
return false;
}
/**
* 获取地块的所有农事记录
* @param fieldId 地块ID
* @returns Promise<FarmOperation[]> 农事记录列表
*/
async getFieldOperations(fieldId: string): Promise<FarmOperation[]> {
const field = await this.getFieldById(fieldId);
return field?.operations || [];
}
/**
* 生成操作记录ID
* @returns string 格式为 "op_时间戳_随机字符串"
*/
generateOperationId(): string {
return 'op_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
/**
* 获取最近的农事记录
* @param limit 返回数量限制,默认10条
* @returns Promise<FarmOperation[]> 按日期倒序排列的农事记录
*/
async getRecentOperations(limit: number = 10): Promise<FarmOperation[]> {
const fields = await this.getAllFields();
const allOperations: FarmOperation[] = [];
// 遍历所有地块收集农事记录
for (const field of fields) {
if (field.operations && field.operations.length > 0) {
for (const op of field.operations) {
allOperations.push(op);
}
}
}
// 按日期排序,最新的在前
allOperations.sort((a, b) => b.date - a.date);
return allOperations.slice(0, limit);
}
/**
* 记录灌溉操作(便捷方法)
* @param fieldId 地块ID
* @param details 操作详情
* @returns Promise<boolean>
*/
async recordIrrigation(fieldId: string, details: string): Promise<boolean> {
const operation: FarmOperation = {
id: this.generateOperationId(),
fieldId: fieldId,
operationType: '灌溉',
date: Date.now(),
operator: '当前用户',
details: details,
createdAt: Date.now()
};
return await this.addFarmOperation(fieldId, operation);
}
/**
* 记录施肥操作(便捷方法)
* @param fieldId 地块ID
* @param details 操作详情
* @returns Promise<boolean>
*/
async recordFertilization(fieldId: string, details: string): Promise<boolean> {
const operation: FarmOperation = {
id: this.generateOperationId(),
fieldId: fieldId,
operationType: '施肥',
date: Date.now(),
operator: '当前用户',
details: details,
createdAt: Date.now()
};
return await this.addFarmOperation(fieldId, operation);
}
/**
* 记录植保操作(便捷方法)
* @param fieldId 地块ID
* @param details 操作详情
* @returns Promise<boolean>
*/
async recordPestControl(fieldId: string, details: string): Promise<boolean> {
const operation: FarmOperation = {
id: this.generateOperationId(),
fieldId: fieldId,
operationType: '植保',
date: Date.now(),
operator: '当前用户',
details: details,
createdAt: Date.now()
};
return await this.addFarmOperation(fieldId, operation);
}
服务设计要点:
| 功能点 | 实现方式 |
|---|---|
| 单例模式 | 使用 getInstance() 确保全局唯一实例 |
| 数据持久化 | 通过 StorageUtil 保存到本地存储 |
| 关联管理 | 农事记录作为地块的子数据存储 |
| 便捷方法 | 提供灌溉、施肥、植保等常用操作的快捷方法 |
三、创建农事记录列表页面
农事记录列表页面用于展示和管理农事操作记录,支持筛选和搜索功能。
3.1 创建 FarmOperationPage.ets
文件位置:entry/src/main/ets/pages/Management/FarmOperationPage.ets
操作步骤:
- 在
entry/src/main/ets/pages/Management/目录下创建新文件 - 命名为
FarmOperationPage.ets - 输入以下代码
/**
* 农事记录页面
* 展示和管理农事操作记录
* 支持按类型筛选、搜索等功能
*/
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
import { FarmOperation, FieldInfo } from '../../models/ProfessionalAgricultureModels';
import { ImageInfo } from '../../models/CommonModels';
import { FieldService } from '../../services/FieldService';
@Entry
@ComponentV2
export struct FarmOperationPage {
// 状态变量
@Local operations: FarmOperation[] = []; // 所有农事记录
@Local filteredOperations: FarmOperation[] = []; // 筛选后的记录
@Local field: FieldInfo | null = null; // 当前地块(如果从地块详情进入)
@Local isLoading: boolean = true; // 加载状态
@Local selectedType: string = '全部'; // 选中的操作类型
@Local searchText: string = ''; // 搜索文本
// 服务实例
private fieldService = FieldService.getInstance();
private fieldId: string = ''; // 地块ID
private isFirstLoad: boolean = true; // 是否首次加载
// 操作类型选项
private operationTypes = ['全部', '播种', '施肥', '浇水', '除草', '打药', '收获', '其他'];
/**
* 页面即将出现时加载数据
*/
async aboutToAppear(): Promise<void> {
// 获取传入的地块ID参数
const params = router.getParams() as Record<string, Object>;
if (params && params['fieldId']) {
this.fieldId = params['fieldId'] as string;
}
await this.loadData();
}
/**
* 页面显示时刷新数据(从其他页面返回时)
*/
async onPageShow(): Promise<void> {
// 首次加载时跳过,避免重复加载
if (this.isFirstLoad) {
this.isFirstLoad = false;
return;
}
// 从其他页面返回时重新加载数据
await this.loadData();
}
/**
* 加载农事记录数据
*/
async loadData(): Promise<void> {
try {
if (this.fieldId) {
// 加载特定地块的农事记录
this.field = await this.fieldService.getFieldById(this.fieldId);
this.operations = await this.fieldService.getFieldOperations(this.fieldId);
} else {
// 加载所有地块的农事记录
const allFields = await this.fieldService.getAllFields();
this.operations = [];
for (const field of allFields) {
const ops = await this.fieldService.getFieldOperations(field.id);
for (const op of ops) {
this.operations.push(op);
}
}
}
this.filterOperations();
this.isLoading = false;
} catch (error) {
console.error('[FarmOperationPage] Failed to load operations:', error);
this.isLoading = false;
}
}
/**
* 筛选农事记录
*/
filterOperations(): void {
let filtered = this.operations;
// 按类型筛选
if (this.selectedType !== '全部') {
filtered = filtered.filter(op => op.operationType === this.selectedType);
}
// 按搜索文本筛选
if (this.searchText.trim().length > 0) {
const keyword = this.searchText.toLowerCase();
filtered = filtered.filter(op =>
op.operationType.toLowerCase().includes(keyword) ||
op.operator.toLowerCase().includes(keyword) ||
(op.details && op.details.toLowerCase().includes(keyword))
);
}
// 按日期排序(最新的在前)
this.filteredOperations = filtered.sort((a, b) => b.date - a.date);
}
build() {
Column() {
this.buildHeader()
if (this.isLoading) {
this.buildLoading()
} else {
Column() {
this.buildFilters()
this.buildList()
}
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.background'))
}
/**
* 构建顶部导航栏
*/
@Builder
buildHeader() {
Row() {
// 返回按钮
Button('< 返回')
.backgroundColor(Color.Transparent)
.fontColor($r('app.color.text_primary'))
.onClick(() => {
router.back();
})
// 标题区域
Column() {
Text('农事记录')
.fontSize(18)
.fontWeight(FontWeight.Bold)
if (this.field) {
Text(this.field.name)
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
.margin({ top: 2 })
} else {
Text('全部地块')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
.margin({ top: 2 })
}
}
.layoutWeight(1)
// 添加按钮
Button('+ 添加')
.backgroundColor(Color.Transparent)
.fontColor($r('app.color.primary_professional'))
.onClick(() => {
if (this.fieldId) {
// 从特定地块进入,直接添加该地块的农事记录
router.pushUrl({
url: 'pages/Management/AddFarmOperationPage',
params: { fieldId: this.fieldId }
});
} else {
// 从全部地块进入,提示用户先选择地块
promptAction.showToast({
message: '请先从地块管理中选择地块添加农事记录',
duration: 2500
});
}
})
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor($r('app.color.card_background'))
.shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
}
/**
* 构建加载状态
*/
@Builder
buildLoading() {
Column() {
Text('加载中...')
.fontSize(16)
.fontColor($r('app.color.text_secondary'))
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
/**
* 构建筛选区域
*/
@Builder
buildFilters() {
Column({ space: 12 }) {
// 搜索框
TextInput({ placeholder: '搜索操作类型、操作人或详情...' })
.fontSize(15)
.height(40)
.backgroundColor($r('app.color.card_background'))
.onChange((value: string) => {
this.searchText = value;
this.filterOperations();
})
// 类型筛选器(横向滚动)
Scroll() {
Row({ space: 8 }) {
ForEach(this.operationTypes, (type: string) => {
Text(type)
.fontSize(14)
.fontColor(this.selectedType === type ?
Color.White : $r('app.color.text_primary'))
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(this.selectedType === type ?
$r('app.color.primary_professional') : $r('app.color.card_background'))
.borderRadius(16)
.onClick(() => {
this.selectedType = type;
this.filterOperations();
})
})
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
}
.width('100%')
.padding(16)
}
/**
* 构建记录列表
*/
@Builder
buildList() {
if (this.filteredOperations.length === 0) {
// 空状态
Column({ space: 16 }) {
Text('📝')
.fontSize(48)
Text(this.searchText || this.selectedType !== '全部' ? '没有找到匹配的记录' : '还没有农事记录')
.fontSize(16)
.fontColor($r('app.color.text_secondary'))
Text('点击右上角添加新的农事操作')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
// 记录列表
Column() {
Text(`共 ${this.filteredOperations.length} 条记录`)
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
.padding({ left: 16, right: 16, bottom: 8 })
Scroll() {
Column({ space: 12 }) {
ForEach(this.filteredOperations, (operation: FarmOperation) => {
this.buildOperationCard(operation)
})
}
.padding({ left: 16, right: 16, bottom: 16 })
}
.layoutWeight(1)
.scrollBar(BarState.Auto)
}
.layoutWeight(1)
}
}
/**
* 构建农事记录卡片
*/
@Builder
buildOperationCard(operation: FarmOperation) {
Column({ space: 12 }) {
// 顶部:图标、类型、日期
Row() {
Text(this.getOperationIcon(operation.operationType))
.fontSize(20)
Text(operation.operationType)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 8 })
Blank()
Text(this.formatDate(operation.date))
.fontSize(13)
.fontColor($r('app.color.text_secondary'))
}
.width('100%')
// 详细说明
if (operation.details) {
Text(operation.details)
.fontSize(14)
.fontColor($r('app.color.text_primary'))
.lineHeight(20)
.width('100%')
}
// 底部:操作人和成本
Row() {
Text('操作人:' + operation.operator)
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
if (operation.cost !== undefined && operation.cost > 0) {
Blank()
Text('成本:¥' + operation.cost.toFixed(2))
.fontSize(12)
.fontColor('#FF6B6B')
}
}
.width('100%')
// 照片展示
if (operation.images && operation.images.length > 0) {
Grid() {
ForEach(operation.images.slice(0, 3), (img: ImageInfo) => {
GridItem() {
Image('file://' + img.uri)
.width('100%')
.height(80)
.objectFit(ImageFit.Cover)
.borderRadius(8)
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(8)
.width('100%')
}
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
.shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
.onClick(() => {
// 点击卡片跳转到编辑页面
router.pushUrl({
url: 'pages/Management/EditFarmOperationPage',
params: {
fieldId: operation.fieldId,
operationId: operation.id
}
});
})
}
/**
* 获取操作类型对应的图标
*/
private getOperationIcon(type: string): string {
const iconMap: Record<string, string> = {
'播种': '🌱',
'施肥': '🌿',
'浇水': '💧',
'除草': '🌾',
'打药': '🛡️',
'收获': '🌻',
'其他': '📋'
};
return iconMap[type] || '📝';
}
/**
* 格式化日期时间
*/
private formatDate(timestamp: number): string {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
}
页面功能说明:
| 功能 | 实现方式 |
|---|---|
| 数据加载 | 支持加载单个地块或所有地块的农事记录 |
| 类型筛选 | 横向滚动的标签选择器 |
| 搜索功能 | 按操作类型、操作人、详情进行模糊搜索 |
| 卡片展示 | 显示操作类型、日期、详情、操作人、成本、照片 |
| 空状态 | 无数据时显示友好提示 |
四、创建添加农事记录页面
添加农事记录页面允许用户记录各种农事操作,并集成天气服务提供智能建议。
4.1 操作类型定义
首先定义操作类型的数据结构:
/**
* 操作类型模板接口
*/
interface OperationType {
type: string; // 操作类型名称
icon: string; // 操作图标
template: string; // 操作说明模板
}
/**
* 天气对农事操作的影响
*/
interface WeatherAdvice {
unsuitable: string[]; // 不宜进行的操作
suitable: string[]; // 适宜进行的操作
tips: string; // 提示信息
}
4.2 创建 AddFarmOperationPage.ets
文件位置:entry/src/main/ets/pages/Management/AddFarmOperationPage.ets
操作步骤:
- 在
entry/src/main/ets/pages/Management/目录下创建新文件 - 命名为
AddFarmOperationPage.ets - 输入以下代码
/**
* 添加农事记录页面
* 支持记录农事操作,集成天气服务提供智能建议
*/
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
import { FarmOperation } from '../../models/ProfessionalAgricultureModels';
import { ImageInfo } from '../../models/CommonModels';
import { FieldService } from '../../services/FieldService';
import { ImageService, ImagePickResult } from '../../services/ImageService';
import { common } from '@kit.AbilityKit';
import { WeatherSearch, WeatherSearchQuery, LocalWeatherLive, LocalWeatherLiveResult, OnWeatherSearchListener } from '@amap/amap_lbs_search';
// 操作类型模板接口
interface OperationType {
type: string;
icon: string;
template: string;
}
// 天气对农事操作的影响
interface WeatherAdvice {
unsuitable: string[]; // 不宜进行的操作
suitable: string[]; // 适宜进行的操作
tips: string; // 提示信息
}
@Entry
@ComponentV2
struct AddFarmOperationPage {
// ===== 状态变量 =====
@Local selectedType: string = ''; // 选中的操作类型
@Local selectedDate: number = Date.now(); // 操作日期
@Local operator: string = '当前用户'; // 操作人
@Local details: string = ''; // 详细说明
@Local cost: string = ''; // 成本
@Local selectedImages: ImageInfo[] = []; // 选择的图片
@Local isSaving: boolean = false; // 保存中状态
// 天气相关状态
@Local currentWeather: string = ''; // 当前天气
@Local currentTemperature: string = ''; // 当前温度
@Local windDirection: string = ''; // 风向
@Local windPower: string = ''; // 风力
@Local humidity: string = ''; // 湿度
@Local weatherLoading: boolean = true; // 天气加载状态
@Local weatherAdvice: WeatherAdvice = { unsuitable: [], suitable: [], tips: '' };
// ===== 服务实例 =====
private fieldService = FieldService.getInstance();
private imageService = ImageService.getInstance();
private fieldId: string = '';
// ===== 操作类型模板配置 =====
private operationTypes: OperationType[] = [
{ type: '播种', icon: '🌱', template: '进行播种作业' },
{ type: '施肥', icon: '🌿', template: '进行施肥作业' },
{ type: '浇水', icon: '💧', template: '进行浇水作业' },
{ type: '除草', icon: '🌾', template: '进行除草作业' },
{ type: '打药', icon: '🛡️', template: '进行植保打药作业' },
{ type: '收获', icon: '🌻', template: '进行收获作业' },
{ type: '整地', icon: '🚜', template: '进行整地作业' },
{ type: '修剪', icon: '✂️', template: '进行修剪作业' },
{ type: '其他', icon: '📋', template: '' }
];
/**
* 页面初始化
*/
aboutToAppear(): void {
// 获取传入的地块ID
const params = router.getParams() as Record<string, Object>;
if (params && params['fieldId']) {
this.fieldId = params['fieldId'] as string;
}
// 初始化ImageService
const context = getContext(this) as common.UIAbilityContext;
this.imageService.initialize(context);
// 获取天气信息
this.loadWeatherInfo(context);
}
/**
* 加载天气信息
*/
private loadWeatherInfo(context: Context): void {
try {
// 从全局存储获取城市信息
const globalLocation = AppStorage.get<Record<string, string>>('globalUserLocation');
const city = globalLocation?.city || '武汉';
const weatherSearch = new WeatherSearch(context);
const query = new WeatherSearchQuery(city, 1); // 1表示实时天气
const listener: OnWeatherSearchListener = {
onWeatherLiveSearched: (result: LocalWeatherLiveResult | undefined, rCode: number) => {
if (rCode === 1000 && result) {
const liveWeather: LocalWeatherLive = result.getLiveResult();
if (liveWeather) {
this.currentWeather = liveWeather.getWeather() || '未知';
this.currentTemperature = liveWeather.getTemperature() || '--';
this.windDirection = liveWeather.getWindDirection() || '';
this.windPower = liveWeather.getWindPower() || '';
this.humidity = liveWeather.getHumidity() || '';
// 根据天气生成农事建议
this.generateWeatherAdvice();
}
}
this.weatherLoading = false;
},
onWeatherForecastSearched: () => {}
};
weatherSearch.setOnWeatherSearchListener(listener);
weatherSearch.setQuery(query);
weatherSearch.searchWeatherAsyn();
} catch (error) {
console.error('[AddFarmOperationPage] Failed to load weather:', error);
this.weatherLoading = false;
this.weatherAdvice = {
unsuitable: [],
suitable: ['播种', '施肥', '浇水', '除草', '打药', '收获', '整地', '修剪'],
tips: '天气信息获取失败,请根据实际情况安排农事'
};
}
}
/**
* 根据天气生成农事建议
*/
private generateWeatherAdvice(): void {
const weather = this.currentWeather.toLowerCase();
const temp = parseInt(this.currentTemperature) || 20;
const windPower = parseInt(this.windPower) || 0;
let unsuitable: string[] = [];
let suitable: string[] = [];
let tips = '';
// 雨天
if (weather.includes('雨') || weather.includes('雷') || weather.includes('暴')) {
unsuitable = ['播种', '施肥', '打药', '收获', '整地'];
suitable = ['其他'];
tips = '⚠️ 雨天不宜进行户外农事作业,施肥打药易被雨水冲刷,收获作物易受潮';
}
// 大风天
else if (windPower >= 5) {
unsuitable = ['打药', '施肥'];
suitable = ['浇水', '除草', '修剪', '其他'];
tips = '⚠️ 大风天气不宜打药施肥,药剂易飘散造成浪费和污染';
}
// 高温天气
else if (temp >= 35) {
unsuitable = ['打药', '施肥', '整地'];
suitable = ['浇水', '收获'];
tips = '⚠️ 高温天气避免中午作业,打药易产生药害,建议早晚进行';
}
// 低温天气
else if (temp <= 5) {
unsuitable = ['播种', '打药'];
suitable = ['整地', '修剪'];
tips = '⚠️ 低温天气不宜播种,药剂效果差,可进行整地修剪等准备工作';
}
// 阴天
else if (weather.includes('阴') || weather.includes('多云')) {
unsuitable = [];
suitable = ['播种', '施肥', '浇水', '除草', '打药', '收获', '整地', '修剪'];
tips = '✅ 阴天适宜大多数农事作业,光照柔和,作业舒适';
}
// 晴天
else if (weather.includes('晴')) {
if (temp >= 30) {
unsuitable = ['打药'];
suitable = ['播种', '施肥', '浇水', '除草', '收获', '整地', '修剪'];
tips = '☀️ 晴天高温,打药建议在早晚进行,避免药害';
} else {
unsuitable = [];
suitable = ['播种', '施肥', '浇水', '除草', '打药', '收获', '整地', '修剪'];
tips = '✅ 天气晴好,适宜各类农事作业';
}
}
// 默认
else {
unsuitable = [];
suitable = ['播种', '施肥', '浇水', '除草', '打药', '收获', '整地', '修剪'];
tips = '请根据实际天气情况合理安排农事作业';
}
this.weatherAdvice = { unsuitable, suitable, tips };
}
/**
* 检查操作是否不宜进行
*/
private isUnsuitableOperation(type: string): boolean {
return this.weatherAdvice.unsuitable.includes(type);
}
build() {
Column() {
this.buildHeader()
Scroll() {
Column({ space: 16 }) {
this.buildWeatherTip()
this.buildOperationType()
this.buildBasicInfo()
this.buildImages()
}
.padding({ left: 16, right: 16, top: 16, bottom: 80 })
}
.layoutWeight(1)
.scrollBar(BarState.Auto)
this.buildFooter()
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.background'))
}
/**
* 构建顶部导航栏
*/
@Builder
buildHeader() {
Row() {
Button('< 取消')
.backgroundColor(Color.Transparent)
.fontColor($r('app.color.text_primary'))
.onClick(() => {
router.back();
})
Text('添加农事记录')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Text(' ')
.width(60)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor($r('app.color.card_background'))
.shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
}
/**
* 构建天气提示卡片
*/
@Builder
buildWeatherTip() {
Column({ space: 10 }) {
// 天气信息头部
Row({ space: 8 }) {
Text('🌤️')
.fontSize(20)
Text('当前天气')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
Blank()
if (this.weatherLoading) {
LoadingProgress()
.width(20)
.height(20)
.color($r('app.color.primary_professional'))
}
}
.width('100%')
if (!this.weatherLoading && this.currentWeather) {
// 天气详情
Row({ space: 16 }) {
// 天气和温度
Column({ space: 4 }) {
Text(this.currentWeather)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
Text(`${this.currentTemperature}°C`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.primary_professional'))
}
.alignItems(HorizontalAlign.Start)
// 分隔线
Divider()
.vertical(true)
.height(50)
.color($r('app.color.border'))
// 风力湿度
Column({ space: 4 }) {
Row({ space: 4 }) {
Text('💨')
.fontSize(14)
Text(`${this.windDirection}风 ${this.windPower}级`)
.fontSize(13)
.fontColor($r('app.color.text_secondary'))
}
Row({ space: 4 }) {
Text('💧')
.fontSize(14)
Text(`湿度 ${this.humidity}%`)
.fontSize(13)
.fontColor($r('app.color.text_secondary'))
}
}
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ top: 8, bottom: 8 })
// 农事建议
if (this.weatherAdvice.tips) {
Column({ space: 8 }) {
Text(this.weatherAdvice.tips)
.fontSize(14)
.fontColor(this.weatherAdvice.unsuitable.length > 0 ? '#E65100' : '#2E7D32')
.width('100%')
// 不宜操作提示
if (this.weatherAdvice.unsuitable.length > 0) {
Row({ space: 6 }) {
Text('不宜:')
.fontSize(12)
.fontColor('#E65100')
.fontWeight(FontWeight.Medium)
ForEach(this.weatherAdvice.unsuitable, (item: string) => {
Text(item)
.fontSize(11)
.fontColor('#E65100')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#FFF3E0')
.borderRadius(4)
})
}
.width('100%')
}
}
.width('100%')
.padding(10)
.backgroundColor(this.weatherAdvice.unsuitable.length > 0 ? '#FFF8E1' : '#E8F5E9')
.borderRadius(8)
}
} else if (!this.weatherLoading) {
Text('天气信息加载失败')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
}
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建操作类型选择器
*/
@Builder
buildOperationType() {
Column({ space: 12 }) {
Text('操作类型 *')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
Grid() {
ForEach(this.operationTypes, (item: OperationType) => {
GridItem() {
Stack({ alignContent: Alignment.TopEnd }) {
Column({ space: 6 }) {
Text(item.icon)
.fontSize(28)
Text(item.type)
.fontSize(13)
.fontColor(this.selectedType === item.type ?
Color.White : $r('app.color.text_primary'))
}
.width('100%')
.height(80)
.justifyContent(FlexAlign.Center)
.backgroundColor(this.selectedType === item.type ?
$r('app.color.primary_professional') :
(this.isUnsuitableOperation(item.type) ? '#FFF8E1' : $r('app.color.background')))
.borderRadius(12)
.borderWidth(2)
.borderColor(this.selectedType === item.type ?
$r('app.color.primary_professional') :
(this.isUnsuitableOperation(item.type) ? '#FFB74D' : $r('app.color.border')))
.onClick(() => {
// 如果选择了不宜操作,给出提示
if (this.isUnsuitableOperation(item.type)) {
promptAction.showDialog({
title: '天气提醒',
message: `当前天气(${this.currentWeather})不太适宜进行「${item.type}」操作。\n\n${this.weatherAdvice.tips}\n\n是否仍要选择此操作?`,
buttons: [
{ text: '取消', color: '#666666' },
{ text: '仍然选择', color: '#E65100' }
]
}).then((result) => {
if (result.index === 1) {
this.selectedType = item.type;
if (item.template && !this.details) {
this.details = item.template;
}
}
});
} else {
this.selectedType = item.type;
if (item.template && !this.details) {
this.details = item.template;
}
}
})
// 不宜操作标识
if (this.isUnsuitableOperation(item.type) && this.selectedType !== item.type) {
Text('⚠️')
.fontSize(14)
.margin({ top: 4, right: 4 })
}
}
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建基本信息表单
*/
@Builder
buildBasicInfo() {
Column({ space: 12 }) {
Text('记录信息')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
Column({ space: 16 }) {
// 操作日期
Column({ space: 8 }) {
Text('操作日期')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
Text(this.formatDate(this.selectedDate))
.fontSize(15)
.fontColor($r('app.color.text_primary'))
.width('100%')
.padding(12)
.backgroundColor($r('app.color.background'))
.borderRadius(8)
}
.width('100%')
// 操作人
Column({ space: 8 }) {
Text('操作人')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
TextInput({ placeholder: '请输入操作人姓名', text: this.operator })
.fontSize(15)
.onChange((value: string) => {
this.operator = value;
})
}
.width('100%')
// 详细说明
Column({ space: 8 }) {
Text('详细说明')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
TextArea({ placeholder: '记录操作的详细情况...', text: this.details })
.fontSize(15)
.height(100)
.onChange((value: string) => {
this.details = value;
})
}
.width('100%')
// 成本
Column({ space: 8 }) {
Text('成本(元)')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
TextInput({ placeholder: '例如: 150' })
.fontSize(15)
.type(InputType.Number)
.onChange((value: string) => {
this.cost = value;
})
}
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.background'))
.borderRadius(8)
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建图片选择区域
*/
@Builder
buildImages() {
Column({ space: 12 }) {
Text('现场照片')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
if (this.selectedImages.length > 0) {
// 已选择图片的展示
Column({ space: 8 }) {
Grid() {
ForEach(this.selectedImages, (img: ImageInfo, index: number) => {
GridItem() {
Stack({ alignContent: Alignment.TopEnd }) {
Image('file://' + img.uri)
.width('100%')
.height(100)
.objectFit(ImageFit.Cover)
.borderRadius(8)
// 删除按钮
Column() {
Text('×')
.fontSize(20)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
}
.width(24)
.height(24)
.justifyContent(FlexAlign.Center)
.backgroundColor('#EF5350')
.borderRadius(12)
.margin({ top: 4, right: 4 })
.shadow({ radius: 2, color: '#00000040', offsetX: 0, offsetY: 1 })
.onClick(() => {
this.selectedImages.splice(index, 1);
})
}
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(8)
.columnsGap(8)
.width('100%')
Button('+ 添加更多照片')
.width('100%')
.height(40)
.fontSize(14)
.backgroundColor($r('app.color.background'))
.fontColor($r('app.color.primary_professional'))
.borderRadius(8)
.onClick(() => {
this.showImagePickerMenu();
})
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.background'))
.borderRadius(8)
} else {
// 未选择图片的空状态
Column({ space: 12 }) {
Text('📷')
.fontSize(48)
Text('添加现场照片')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
Button('选择照片')
.fontSize(14)
.height(40)
.width(120)
.backgroundColor($r('app.color.primary_professional'))
.fontColor(Color.White)
.borderRadius(20)
.onClick(() => {
this.showImagePickerMenu();
})
}
.width('100%')
.height(180)
.justifyContent(FlexAlign.Center)
.backgroundColor($r('app.color.background'))
.borderRadius(8)
.borderWidth(1)
.borderColor($r('app.color.border'))
.borderStyle(BorderStyle.Dashed)
.padding(16)
}
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建底部按钮栏
*/
@Builder
buildFooter() {
Row({ space: 12 }) {
Button('取消')
.width('30%')
.height(48)
.fontSize(16)
.backgroundColor($r('app.color.background'))
.fontColor($r('app.color.text_primary'))
.borderRadius(24)
.onClick(() => {
router.back();
})
Button(this.isSaving ? '保存中...' : '保存')
.layoutWeight(1)
.height(48)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor(this.canSave() ?
$r('app.color.primary_professional') : $r('app.color.border'))
.fontColor(Color.White)
.borderRadius(24)
.enabled(this.canSave() && !this.isSaving)
.onClick(async () => {
await this.saveOperation();
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor($r('app.color.card_background'))
.shadow({ radius: 8, color: $r('app.color.shadow_light'), offsetY: -2 })
}
/**
* 显示图片选择菜单
*/
private showImagePickerMenu(): void {
promptAction.showActionMenu({
title: '选择图片来源',
buttons: [
{ text: '拍照', color: '#000000' },
{ text: '从相册选择', color: '#000000' }
]
}).then(async (result) => {
if (result.index === 0) {
await this.takePicture();
} else if (result.index === 1) {
await this.pickFromGallery();
}
}).catch((error: Error) => {
console.error('Action menu error:', error);
});
}
/**
* 拍照
*/
private async takePicture(): Promise<void> {
try {
const result: ImagePickResult = await this.imageService.takePicture();
if (result.success && result.imageInfo) {
this.selectedImages.push(result.imageInfo);
promptAction.showToast({ message: '照片已添加', duration: 2000 });
} else if (result.error) {
promptAction.showToast({ message: result.error, duration: 2000 });
}
} catch (error) {
console.error('Failed to take picture:', error);
promptAction.showToast({ message: '拍照失败', duration: 2000 });
}
}
/**
* 从相册选择
*/
private async pickFromGallery(): Promise<void> {
try {
const result: ImagePickResult = await this.imageService.pickFromGallery();
if (result.success && result.imageInfo) {
this.selectedImages.push(result.imageInfo);
promptAction.showToast({ message: '图片已添加', duration: 2000 });
} else if (result.error) {
promptAction.showToast({ message: result.error, duration: 2000 });
}
} catch (error) {
console.error('Failed to pick from gallery:', error);
promptAction.showToast({ message: '选择图片失败', duration: 2000 });
}
}
/**
* 表单验证
*/
private canSave(): boolean {
return this.selectedType.trim().length > 0 &&
this.operator.trim().length > 0;
}
/**
* 保存农事记录
*/
private async saveOperation(): Promise<void> {
if (!this.canSave()) {
return;
}
this.isSaving = true;
try {
const operation: FarmOperation = {
id: this.fieldService.generateOperationId(),
fieldId: this.fieldId,
operationType: this.selectedType,
date: this.selectedDate,
operator: this.operator.trim(),
details: this.details.trim(),
cost: this.cost.trim() ? parseFloat(this.cost) : undefined,
images: this.selectedImages,
weather: this.currentWeather,
createdAt: Date.now()
};
const success = await this.fieldService.addFarmOperation(this.fieldId, operation);
if (success) {
promptAction.showToast({ message: '农事记录添加成功', duration: 2000 });
router.back();
} else {
throw Error('保存失败');
}
} catch (error) {
console.error('Failed to save operation:', error);
promptAction.showToast({ message: '保存失败,请重试', duration: 2000 });
} finally {
this.isSaving = false;
}
}
/**
* 格式化日期时间
*/
private formatDate(timestamp: number): string {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
}
页面功能说明:
| 功能 | 实现方式 |
|---|---|
| 天气集成 | 使用高德天气API获取实时天气 |
| 智能建议 | 根据天气条件自动生成农事操作建议 |
| 不宜操作警告 | 选择不宜操作时弹出确认对话框 |
| 操作类型选择 | 九宫格布局,带图标和模板 |
| 图片管理 | 支持拍照和相册选择,可删除 |
| 表单验证 | 验证必填字段后才能保存 |
五、创建编辑农事记录页面
编辑页面允许用户查看和修改已有的农事记录。
5.1 创建 EditFarmOperationPage.ets
文件位置:entry/src/main/ets/pages/Management/EditFarmOperationPage.ets
操作步骤:
- 在
entry/src/main/ets/pages/Management/目录下创建新文件 - 命名为
EditFarmOperationPage.ets - 输入以下代码
/**
* 编辑农事记录页面
* 允许查看和修改已有的农事操作记录
*/
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
import { FarmOperation, FieldInfo } from '../../models/ProfessionalAgricultureModels';
import { ImageInfo } from '../../models/CommonModels';
import { FieldService } from '../../services/FieldService';
import { ImageService, ImagePickResult } from '../../services/ImageService';
import { common } from '@kit.AbilityKit';
@Entry
@ComponentV2
struct EditFarmOperationPage {
// 状态变量
@Local operation: FarmOperation | null = null; // 农事记录
@Local field: FieldInfo | null = null; // 地块信息
@Local isLoading: boolean = true; // 加载状态
@Local isSaving: boolean = false; // 保存状态
@Local isDeleting: boolean = false; // 删除状态
// 表单数据
@Local operationType: string = '';
@Local operationDate: number = Date.now();
@Local operator: string = '';
@Local details: string = '';
@Local cost: string = '';
@Local notes: string = '';
@Local selectedImages: ImageInfo[] = [];
// 服务实例
private fieldService = FieldService.getInstance();
private imageService = ImageService.getInstance();
private fieldId: string = '';
private operationId: string = '';
/**
* 页面初始化
*/
async aboutToAppear(): Promise<void> {
const params = router.getParams() as Record<string, Object>;
if (params) {
this.fieldId = params['fieldId'] as string;
this.operationId = params['operationId'] as string;
}
// 初始化ImageService
const context = getContext(this) as common.UIAbilityContext;
this.imageService.initialize(context);
await this.loadData();
}
/**
* 加载数据
*/
async loadData(): Promise<void> {
try {
// 加载地块信息
this.field = await this.fieldService.getFieldById(this.fieldId);
// 加载农事记录
const operations = await this.fieldService.getFieldOperations(this.fieldId);
this.operation = operations.find(op => op.id === this.operationId) || null;
if (this.operation) {
// 填充表单数据
this.operationType = this.operation.operationType;
this.operationDate = this.operation.date;
this.operator = this.operation.operator;
this.details = this.operation.details || '';
this.cost = this.operation.cost?.toString() || '';
this.notes = this.operation.notes || '';
this.selectedImages = this.operation.images || [];
}
this.isLoading = false;
} catch (error) {
console.error('[EditFarmOperationPage] Failed to load data:', error);
promptAction.showToast({ message: '加载失败', duration: 2000 });
router.back();
}
}
build() {
Column() {
this.buildHeader()
if (this.isLoading) {
this.buildLoading()
} else if (!this.operation) {
this.buildNotFound()
} else {
Scroll() {
Column({ space: 16 }) {
this.buildOperationInfo()
this.buildEditForm()
this.buildImages()
}
.padding({ left: 16, right: 16, top: 16, bottom: 80 })
}
.layoutWeight(1)
this.buildFooter()
}
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.background'))
}
/**
* 构建顶部导航栏
*/
@Builder
buildHeader() {
Row() {
Button('< 返回')
.backgroundColor(Color.Transparent)
.fontColor($r('app.color.text_primary'))
.onClick(() => {
router.back();
})
Text('农事记录详情')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Text(' ')
.width(60)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor($r('app.color.card_background'))
.shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
}
/**
* 构建加载状态
*/
@Builder
buildLoading() {
Column() {
LoadingProgress()
.width(48)
.height(48)
.color($r('app.color.primary_professional'))
Text('加载中...')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.margin({ top: 16 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
/**
* 构建未找到状态
*/
@Builder
buildNotFound() {
Column({ space: 16 }) {
Text('📋')
.fontSize(48)
Text('记录不存在')
.fontSize(16)
.fontColor($r('app.color.text_primary'))
Button('返回')
.onClick(() => router.back())
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
/**
* 构建操作信息展示
*/
@Builder
buildOperationInfo() {
Column({ space: 12 }) {
Row() {
Text(this.getOperationIcon(this.operationType))
.fontSize(32)
Column({ space: 4 }) {
Text(this.operationType)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
if (this.field) {
Text(this.field.name)
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
}
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
.layoutWeight(1)
}
.width('100%')
Divider()
Row({ space: 24 }) {
Column({ space: 4 }) {
Text('操作人')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
Text(this.operator)
.fontSize(14)
.fontColor($r('app.color.text_primary'))
}
.alignItems(HorizontalAlign.Start)
Column({ space: 4 }) {
Text('日期')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
Text(this.formatDate(this.operationDate))
.fontSize(14)
.fontColor($r('app.color.text_primary'))
}
.alignItems(HorizontalAlign.Start)
if (this.operation?.weather) {
Column({ space: 4 }) {
Text('天气')
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
Text(this.operation.weather)
.fontSize(14)
.fontColor($r('app.color.text_primary'))
}
.alignItems(HorizontalAlign.Start)
}
}
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建编辑表单
*/
@Builder
buildEditForm() {
Column({ space: 12 }) {
Text('编辑信息')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
Column({ space: 16 }) {
// 详细说明
Column({ space: 8 }) {
Text('详细说明')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
TextArea({ placeholder: '记录操作的详细情况...', text: this.details })
.fontSize(15)
.height(100)
.onChange((value: string) => {
this.details = value;
})
}
.width('100%')
// 成本
Column({ space: 8 }) {
Text('成本(元)')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
TextInput({ placeholder: '例如: 150', text: this.cost })
.fontSize(15)
.type(InputType.Number)
.onChange((value: string) => {
this.cost = value;
})
}
.width('100%')
// 备注
Column({ space: 8 }) {
Text('备注')
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('100%')
TextArea({ placeholder: '添加备注信息...', text: this.notes })
.fontSize(15)
.height(80)
.onChange((value: string) => {
this.notes = value;
})
}
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.background'))
.borderRadius(8)
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建图片展示
*/
@Builder
buildImages() {
if (this.selectedImages.length === 0) {
return;
}
Column({ space: 12 }) {
Text('现场照片')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
Grid() {
ForEach(this.selectedImages, (img: ImageInfo, index: number) => {
GridItem() {
Stack({ alignContent: Alignment.TopEnd }) {
Image('file://' + img.uri)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius(8)
.onClick(() => {
// 点击图片可以预览(此处简化)
})
}
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(8)
.columnsGap(8)
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_background'))
.borderRadius(12)
}
/**
* 构建底部按钮栏
*/
@Builder
buildFooter() {
Column({ space: 12 }) {
// 保存按钮
Button(this.isSaving ? '保存中...' : '保存修改')
.width('100%')
.height(48)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor($r('app.color.primary_professional'))
.fontColor(Color.White)
.borderRadius(24)
.enabled(!this.isSaving)
.onClick(async () => {
await this.saveChanges();
})
// 删除按钮
Button(this.isDeleting ? '删除中...' : '删除记录')
.width('100%')
.height(44)
.fontSize(14)
.backgroundColor(Color.Transparent)
.fontColor('#EF5350')
.borderRadius(22)
.borderWidth(1)
.borderColor('#EF5350')
.enabled(!this.isDeleting)
.onClick(() => {
this.confirmDelete();
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor($r('app.color.card_background'))
.shadow({ radius: 8, color: $r('app.color.shadow_light'), offsetY: -2 })
}
/**
* 保存修改
*/
private async saveChanges(): Promise<void> {
if (!this.operation || !this.field) {
return;
}
this.isSaving = true;
try {
// 更新操作记录
this.operation.details = this.details.trim();
this.operation.cost = this.cost.trim() ? parseFloat(this.cost) : undefined;
this.operation.notes = this.notes.trim();
// 保存到服务
const success = await this.fieldService.updateField(this.field);
if (success) {
promptAction.showToast({ message: '保存成功', duration: 2000 });
router.back();
} else {
throw Error('保存失败');
}
} catch (error) {
console.error('Failed to save changes:', error);
promptAction.showToast({ message: '保存失败,请重试', duration: 2000 });
} finally {
this.isSaving = false;
}
}
/**
* 确认删除
*/
private confirmDelete(): void {
promptAction.showDialog({
title: '确认删除',
message: '删除后无法恢复,确定要删除这条农事记录吗?',
buttons: [
{ text: '取消', color: '#666666' },
{ text: '删除', color: '#EF5350' }
]
}).then(async (result) => {
if (result.index === 1) {
await this.deleteOperation();
}
});
}
/**
* 删除操作记录
*/
private async deleteOperation(): Promise<void> {
if (!this.operation || !this.field) {
return;
}
this.isDeleting = true;
try {
// 从数组中移除
if (this.field.operations) {
this.field.operations = this.field.operations.filter(op => op.id !== this.operationId);
}
// 保存到服务
const success = await this.fieldService.updateField(this.field);
if (success) {
promptAction.showToast({ message: '删除成功', duration: 2000 });
router.back();
} else {
throw Error('删除失败');
}
} catch (error) {
console.error('Failed to delete operation:', error);
promptAction.showToast({ message: '删除失败,请重试', duration: 2000 });
} finally {
this.isDeleting = false;
}
}
/**
* 获取操作类型图标
*/
private getOperationIcon(type: string): string {
const iconMap: Record<string, string> = {
'播种': '🌱',
'施肥': '🌿',
'浇水': '💧',
'除草': '🌾',
'打药': '🛡️',
'收获': '🌻',
'其他': '📋'
};
return iconMap[type] || '📝';
}
/**
* 格式化日期
*/
private formatDate(timestamp: number): string {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
}
}
六、配置页面路由
在 main_pages.json 中添加农事记录相关页面的路由。
文件位置:entry/src/main/resources/base/profile/main_pages.json
{
"src": [
"pages/WelcomePage",
"pages/Index",
"pages/Map/FieldMapPage",
"pages/Management/FieldManagementPage",
"pages/Management/AddFieldPage",
"pages/Management/EditFieldPage",
"pages/Management/FieldDetailPage",
"pages/Management/FarmOperationPage",
"pages/Management/AddFarmOperationPage",
"pages/Management/EditFarmOperationPage",
"pages/Management/CropManagementPage",
"pages/Management/AddCropPage",
"pages/Management/CropDetailPage",
"pages/OnboardingFlow/ModeSelectionPage",
"pages/OnboardingFlow/LocationPage",
"pages/OnboardingFlow/GoalsPage"
]
}
七、运行与测试
7.1 测试步骤
-
启动应用
- 点击运行按钮或按
Shift + F10 - 等待应用编译并安装到模拟器
- 点击运行按钮或按
-
进入农事记录页面
- 进入地图首页
- 点击"地块管理"
- 选择一个地块
- 点击"农事记录"
-
添加农事记录
- 点击右上角"+ 添加"按钮
- 查看天气建议
- 选择操作类型(尝试选择不宜操作,查看警告提示)
- 填写操作人、详细说明、成本
- 添加照片(可选)
- 点击"保存"
-
查看记录列表
- 返回记录列表
- 查看新添加的记录
- 使用类型筛选器筛选
- 使用搜索框搜索
-
编辑农事记录
- 点击记录卡片进入详情页
- 修改详细说明或备注
- 点击"保存修改"
- 尝试删除记录
7.2 预期效果
| 功能 | 预期效果 |
|---|---|
| 记录列表 | 显示所有农事记录,最新的在前 |
| 类型筛选 | 点击不同类型按钮筛选记录 |
| 搜索功能 | 输入关键词模糊搜索 |
| 天气建议 | 显示当前天气和农事操作建议 |
| 不宜操作警告 | 选择不宜操作时弹出确认对话框 |
| 照片管理 | 支持拍照和相册选择 |
| 保存功能 | 保存成功后返回列表并刷新 |
| 删除功能 | 删除确认对话框,删除成功后返回 |
八、常见问题与解决方案
8.1 农事记录保存失败
问题:点击保存后提示"保存失败"
解决方案:
- 检查是否已创建地块
- 确认必填字段(操作类型、操作人)已填写
- 查看控制台错误日志
8.2 天气信息不显示
问题:天气提示卡片显示"天气信息加载失败"
解决方案:
- 检查网络连接
- 确认高德API Key配置正确
- 检查位置权限是否已授予
8.3 图片无法上传
问题:点击拍照或相册选择后没有反应
解决方案:
- 检查相机和存储权限
- 确认ImageService已正确初始化
- 查看控制台错误日志
8.4 记录列表为空
问题:添加记录后列表仍显示为空
解决方案:
- 确认保存成功(查看Toast提示)
- 下拉刷新或重新进入页面
- 检查数据是否正确保存到存储
九、总结
本篇教程完成了:
- ✅ 农事操作数据模型理解
- ✅ FieldService中的农事操作方法
- ✅ 农事记录列表页面(支持筛选和搜索)
- ✅ 添加农事记录页面(集成天气服务)
- ✅ 编辑农事记录页面(支持修改和删除)
- ✅ 天气智能建议功能
- ✅ 图片管理功能
- ✅ 页面路由配置
关键技术点:
| 技术点 | 说明 |
|---|---|
| 数据关联 | 农事记录作为地块的子数据存储 |
| 天气集成 | 使用高德天气API获取实时天气 |
| 智能建议 | 根据天气条件生成农事操作建议 |
| 图片管理 | 使用ImageService处理拍照和相册选择 |
| 列表筛选 | 支持按类型筛选和关键词搜索 |
| 卡片设计 | 使用Stack组件实现不宜操作标识 |
十、下一步
在下一篇教程中,我们将学习:
- 任务数据模型设计
- 任务管理服务实现
- 任务列表与筛选
- 任务提醒通知
- 自动任务生成逻辑
教程版本:v1.0
更新日期:2026-01
适用版本:DevEco Studio 5.0+, HarmonyOS API 17+
更多推荐


所有评论(0)