HarmonyOS智慧农业管理应用开发教程--高高种地---第4篇:引导流程与用户画像

第4篇:引导流程与用户画像

教程目标

通过本篇教程,你将学会:

  • 设计应用引导流程
  • 实现欢迎页面
  • 创建模式选择页面
  • 收集用户信息(位置、环境、目标)
  • 建立用户画像
  • 管理引导流程状态

完成本教程后,你将拥有一个完整的用户引导系统。


一、引导流程设计

1.1 引导流程概述

我们的应用引导流程包含以下步骤:

复制代码
欢迎页面 → 模式选择 → 位置信息 → 环境条件 → 种植目标 → 完成引导 → 地图首页
   ↓          ↓          ↓          ↓          ↓          ↓
 2秒展示   选择模式   收集位置   收集环境   收集目标   保存数据

设计要点

  • 简洁明了,不超过5个步骤
  • 每个步骤都有明确的目标
  • 支持跳过和返回
  • 数据持久化保存

1.2 创建引导服务

ets/services/ 目录下创建 OnboardingService.ets

typescript 复制代码
/**
 * 引导流程服务
 * 管理用户引导流程的状态和数据
 */

import { StorageUtil, StorageKeys } from '../utils/StorageUtil';
import { UserProfile, AppMode, LocationInfo } from '../models/CommonModels';

export class OnboardingService {
  private static instance: OnboardingService;

  private constructor() {}

  static getInstance(): OnboardingService {
    if (!OnboardingService.instance) {
      OnboardingService.instance = new OnboardingService();
    }
    return OnboardingService.instance;
  }

  /**
   * 检查是否已完成引导
   */
  async isOnboardingCompleted(): Promise<boolean> {
    return await StorageUtil.getBoolean(StorageKeys.ONBOARDING_COMPLETED, false);
  }

  /**
   * 保存用户资料
   */
  async saveUserProfile(profile: UserProfile): Promise<boolean> {
    try {
      await StorageUtil.saveObject('user_profile', profile);
      await StorageUtil.saveBoolean(StorageKeys.ONBOARDING_COMPLETED, true);
      await StorageUtil.saveString(StorageKeys.USER_MODE, profile.mode);
      await StorageUtil.saveString(StorageKeys.USER_NICKNAME, profile.nickname);
      console.info('[OnboardingService] User profile saved');
      return true;
    } catch (error) {
      console.error('[OnboardingService] Failed to save user profile:', error);
      return false;
    }
  }

  /**
   * 获取用户资料
   */
  async getUserProfile(): Promise<UserProfile | null> {
    try {
      const profile = await StorageUtil.getObject<UserProfile>('user_profile', null);
      return profile;
    } catch (error) {
      console.error('[OnboardingService] Failed to get user profile:', error);
      return null;
    }
  }

  /**
   * 重置引导流程
   */
  async resetOnboarding(): Promise<void> {
    await StorageUtil.saveBoolean(StorageKeys.ONBOARDING_COMPLETED, false);
    console.info('[OnboardingService] Onboarding reset');
  }
}

二、创建欢迎页面

2.1 创建 WelcomePage.ets

ets/pages/ 目录下创建 WelcomePage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { StorageUtil, StorageKeys } from '../utils/StorageUtil';
import { AppConstants } from '../constants/AppConstants';

@Entry
@ComponentV2
struct WelcomePage {
  @Local isLoading: boolean = true;

  async aboutToAppear(): Promise<void> {
    // 检查是否已完成引导
    setTimeout(async () => {
      const onboardingCompleted = await StorageUtil.getBoolean(StorageKeys.ONBOARDING_COMPLETED, false);
      
      if (onboardingCompleted) {
        // 已完成引导,直接跳转到主页面
        router.replaceUrl({
          url: 'pages/Index'
        });
      } else {
        // 未完成引导,跳转到模式选择页
        router.replaceUrl({
          url: 'pages/OnboardingFlow/ModeSelectionPage'
        });
      }
    }, 2000); // 显示欢迎页2秒
  }

  build() {
    Column() {
      // Logo和标语
      Column({ space: 24 }) {
        // App图标
        Text('🌱')
          .fontSize(80)
          .fontWeight(FontWeight.Bold)

        Text(AppConstants.APP_NAME)
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))

        Text(AppConstants.APP_SLOGAN)
          .fontSize(16)
          .fontColor($r('app.color.text_secondary'))
      }
      .justifyContent(FlexAlign.Center)
      .layoutWeight(1)

      // 加载指示器
      if (this.isLoading) {
        LoadingProgress()
          .width(40)
          .height(40)
          .color($r('app.color.primary_professional'))
          .margin({ bottom: 48 })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background'))
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

页面特点

  • 显示应用Logo和标语
  • 2秒后自动跳转
  • 根据引导状态决定跳转目标
  • 使用 router.replaceUrl 避免返回

三、创建模式选择页面

3.1 创建 OnboardingFlow 目录

ets/pages/ 下创建 OnboardingFlow 目录,用于存放引导流程相关页面。

3.2 创建 ModeSelectionPage.ets

OnboardingFlow/ 目录下创建 ModeSelectionPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { AppMode } from '../../models/CommonModels';
import { CommonCard } from '../../components/CommonComponents';

@Entry
@ComponentV2
struct ModeSelectionPage {
  @Local selectedMode: AppMode | null = null;

  build() {
    Column() {
      // 标题
      Column({ space: 12 }) {
        Text('选择使用模式')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))

        Text('根据您的需求选择合适的模式')
          .fontSize(14)
          .fontColor($r('app.color.text_secondary'))
      }
      .width('100%')
      .padding({ top: 60, bottom: 40 })
      .alignItems(HorizontalAlign.Start)

      // 模式选择卡片
      Column({ space: 16 }) {
        // 专业农业模式
        this.buildModeCard(
          '🌾',
          '专业农业模式',
          '适合农业从业者,提供地块管理、作物管理、农事记录等专业功能',
          AppMode.PROFESSIONAL_AGRICULTURE,
          ['地图定位', '地块管理', '产量预测', '成本核算']
        )

        // 家庭园艺模式
        this.buildModeCard(
          '🏡',
          '家庭园艺模式',
          '适合家庭用户,管理阳台、庭院植物,提供简单易用的种植指导',
          AppMode.HOME_GARDENING,
          ['植物管理', '浇水提醒', '生长记录', '知识学习']
        )
      }
      .layoutWeight(1)

      // 继续按钮
      Button('继续')
        .width('100%')
        .height(48)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .backgroundColor($r('app.color.primary_professional'))
        .borderRadius(8)
        .enabled(this.selectedMode !== null)
        .opacity(this.selectedMode !== null ? 1 : 0.5)
        .onClick(() => {
          this.onContinue();
        })
        .margin({ bottom: 24 })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor($r('app.color.background'))
  }
}

3.3 配置页面路由

打开 entry/src/main/resources/base/profile/main_pages.json,添加页面路由:

json 复制代码
{
  "src": [
    "pages/WelcomePage",
    "pages/Index",
    "pages/OnboardingFlow/ModeSelectionPage",
    "pages/OnboardingFlow/LocationPage",
    "pages/OnboardingFlow/ConditionsPage",
    "pages/OnboardingFlow/GoalsPage"
  ]
}

3.4 添加模式卡片构建方法

ModeSelectionPage 中添加:

typescript 复制代码
@Builder
buildModeCard(
  icon: string,
  title: string,
  description: string,
  mode: AppMode,
  features: string[]
) {
  CommonCard({
    backgroundColor: this.selectedMode === mode ?
      '#E6F7FF' : $r('app.color.card_background'),
    onClick: () => {
      this.selectedMode = mode;
    }
  }) {
    Column({ space: 12 }) {
      // 图标和标题
      Row({ space: 12 }) {
        Text(icon)
          .fontSize(40)

        Column({ space: 4 }) {
          Text(title)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor($r('app.color.text_primary'))

          Text(description)
            .fontSize(12)
            .fontColor($r('app.color.text_secondary'))
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)
      }
      .width('100%')

      // 特性列表
      Column({ space: 8 }) {
        ForEach(features, (feature: string) => {
          Row({ space: 8 }) {
            Text('✓')
              .fontSize(14)
              .fontColor($r('app.color.primary_professional'))

            Text(feature)
              .fontSize(14)
              .fontColor($r('app.color.text_secondary'))
          }
        })
      }
      .width('100%')
      .alignItems(HorizontalAlign.Start)

      // 选中标记
      if (this.selectedMode === mode) {
        Row() {
          Text('已选择')
            .fontSize(12)
            .fontColor($r('app.color.primary_professional'))
            .fontWeight(FontWeight.Medium)
        }
        .width('100%')
        .justifyContent(FlexAlign.End)
      }
    }
  }
}

onContinue() {
  if (this.selectedMode) {
    // 保存选择的模式到临时存储
    AppStorage.setOrCreate('temp_selected_mode', this.selectedMode);
    console.info('[ModeSelection] Selected mode:', this.selectedMode);

    // 跳转到位置信息页面
    router.pushUrl({
      url: 'pages/OnboardingFlow/LocationPage'
    });
  }
}

四、创建位置信息页面

4.1 创建 LocationPage.ets

OnboardingFlow/ 目录下创建 LocationPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { LocationInfo } from '../../models/CommonModels';
import { CommonCard, ActionButton } from '../../components/CommonComponents';

@Entry
@ComponentV2
struct LocationPage {
  @Local province: string = '';
  @Local city: string = '';
  @Local district: string = '';
  @Local climateZone: string = '';

  // 省份列表(简化版)
  private provinces: string[] = ['北京', '上海', '广东', '浙江', '江苏', '山东', '河南', '四川', '湖北', '湖南'];

  // 气候带列表
  private climateZones: string[] = ['温带季风气候', '亚热带季风气候', '热带季风气候', '温带大陆性气候', '高原山地气候'];

  build() {
    Column() {
      // 标题和进度
      Column({ space: 12 }) {
        Row() {
          Text('位置信息')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor($r('app.color.text_primary'))
            .layoutWeight(1)

          Text('1/3')
            .fontSize(14)
            .fontColor($r('app.color.text_secondary'))
        }
        .width('100%')

        Text('告诉我们您的位置,以便提供更精准的种植建议')
          .fontSize(14)
          .fontColor($r('app.color.text_secondary'))
      }
      .width('100%')
      .padding({ top: 60, bottom: 40 })
      .alignItems(HorizontalAlign.Start)

      // 表单内容
      Scroll() {
        Column({ space: 16 }) {
          // 省份选择
          CommonCard({ title: '省份', icon: '📍' }) {
            TextInput({ placeholder: '请输入省份' })
              .onChange((value: string) => {
                this.province = value;
              })
          }

          // 城市输入
          CommonCard({ title: '城市', icon: '🏙️' }) {
            TextInput({ placeholder: '请输入城市名称' })
              .onChange((value: string) => {
                this.city = value;
              })
          }

          // 区县输入
          CommonCard({ title: '区/县(可选)', icon: '🗺️' }) {
            TextInput({ placeholder: '请输入区县名称' })
              .onChange((value: string) => {
                this.district = value;
              })
          }

          // 气候带输入
          CommonCard({ title: '气候带', icon: '🌡️' }) {
            TextInput({ placeholder: '如:温带季风气候' })
              .onChange((value: string) => {
                this.climateZone = value;
              })
          }
        }
      }
      .layoutWeight(1)

      // 按钮组
      Row({ space: 12 }) {
        Button('上一步')
          .width('30%')
          .height(48)
          .fontSize(16)
          .backgroundColor($r('app.color.background'))
          .fontColor($r('app.color.text_primary'))
          .onClick(() => {
            router.back();
          })

        ActionButton({
          text: '下一步',
          type: 'primary',
          onClick: () => {
            this.onNext();
          }
        })
        .layoutWeight(1)
      }
      .width('100%')
      .margin({ bottom: 24 })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor($r('app.color.background'))
  }

  onNext() {
    // 验证必填项
    if (!this.province || !this.city || !this.climateZone) {
      console.warn('请填写完整的位置信息');
      return;
    }

    // 保存位置信息到临时存储
    const locationInfo: LocationInfo = {
      province: this.province,
      city: this.city,
      district: this.district,
      climateZone: this.climateZone
    };
    AppStorage.setOrCreate('temp_location_info', JSON.stringify(locationInfo));
    console.info('[LocationPage] Location info saved');

    // 跳转到完成页面(简化流程)
    router.pushUrl({
      url: 'pages/OnboardingFlow/GoalsPage'
    });
  }
}

五、创建目标设置页面

5.1 创建 GoalsPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { AppMode, UserProfile, LocationInfo } from '../../models/CommonModels';
import { OnboardingService } from '../../services/OnboardingService';
import { CommonCard, ActionButton } from '../../components/CommonComponents';

@Entry
@ComponentV2
struct GoalsPage {
  @Local nickname: string = '';
  @Local isLoading: boolean = false;

  private onboardingService = OnboardingService.getInstance();

  build() {
    Column() {
      // 标题
      Column({ space: 12 }) {
        Text('完成设置')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))

        Text('最后一步,设置您的昵称')
          .fontSize(14)
          .fontColor($r('app.color.text_secondary'))
      }
      .width('100%')
      .padding({ top: 60, bottom: 40 })
      .alignItems(HorizontalAlign.Start)

      // 表单
      Column({ space: 16 }) {
        CommonCard({ title: '昵称', icon: '👤' }) {
          TextInput({ placeholder: '请输入昵称' })
            .onChange((value: string) => {
              this.nickname = value;
            })
        }
      }
      .layoutWeight(1)

      // 完成按钮
      ActionButton({
        text: '完成',
        type: 'primary',
        loading: this.isLoading,
        onClick: () => {
          this.onComplete();
        }
      })
      .margin({ bottom: 24 })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor($r('app.color.background'))
  }

  async onComplete() {
    if (!this.nickname) {
      console.warn('请输入昵称');
      return;
    }

    this.isLoading = true;

    try {
      // 获取临时存储的数据
      const selectedMode = AppStorage.get<AppMode>('temp_selected_mode') || AppMode.PROFESSIONAL_AGRICULTURE;
      const locationInfoStr = AppStorage.get<string>('temp_location_info') || '{}';
      const locationInfo: LocationInfo = JSON.parse(locationInfoStr);

      // 创建用户资料
      const userProfile: UserProfile = {
        id: Date.now().toString(),
        nickname: this.nickname,
        mode: selectedMode,
        location: locationInfo,
        createdAt: Date.now(),
        lastUpdatedAt: Date.now(),
        onboardingCompleted: true
      };

      // 保存用户资料
      const success = await this.onboardingService.saveUserProfile(userProfile);

      if (success) {
        console.info('[GoalsPage] User profile saved successfully');

        // 跳转到主页面
        router.replaceUrl({
          url: 'pages/Index'
        });
      } else {
        console.error('[GoalsPage] Failed to save user profile');
      }
    } catch (error) {
      console.error('[GoalsPage] Error:', error);
    } finally {
      this.isLoading = false;
    }
  }
}

六、扩展 StorageUtil

StorageUtil.ets 中添加对象和布尔值存储方法:

typescript 复制代码
/**
 * 保存对象
 */
static async saveObject<T>(key: string, value: T): Promise<void> {
  try {
    const jsonString = JSON.stringify(value);
    await StorageUtil.saveString(key, jsonString);
  } catch (error) {
    console.error(`[StorageUtil] Failed to save object for key ${key}:`, error);
    throw error;
  }
}

/**
 * 获取对象
 */
static async getObject<T>(key: string, defaultValue: T | null = null): Promise<T | null> {
  try {
    const jsonString = await StorageUtil.getString(key, '');
    if (jsonString) {
      return JSON.parse(jsonString) as T;
    }
    return defaultValue;
  } catch (error) {
    console.error(`[StorageUtil] Failed to get object for key ${key}:`, error);
    return defaultValue;
  }
}

/**
 * 保存布尔值
 */
static async saveBoolean(key: string, value: boolean): Promise<void> {
  try {
    if (!StorageUtil.dataPreferences) {
      throw Error('Storage not initialized');
    }
    await StorageUtil.dataPreferences.put(key, value);
    await StorageUtil.dataPreferences.flush();
  } catch (error) {
    console.error(`[StorageUtil] Failed to save boolean:`, error);
    throw error;
  }
}

/**
 * 获取布尔值
 */
static async getBoolean(key: string, defaultValue: boolean = false): Promise<boolean> {
  try {
    if (!StorageUtil.dataPreferences) {
      throw Error('Storage not initialized');
    }
    const value = await StorageUtil.dataPreferences.get(key, defaultValue);
    return value as boolean;
  } catch (error) {
    console.error(`[StorageUtil] Failed to get boolean:`, error);
    return defaultValue;
  }
}

七、运行与测试

7.1 完整测试流程

  1. 清除应用数据
  2. 运行应用
  3. 按照以下步骤测试:

步骤1:欢迎页面

  • ✅ 显示应用Logo和标语
  • ✅ 2秒后自动跳转

步骤2:模式选择

  • ✅ 显示两种模式选项
  • ✅ 点击卡片可以选择模式
  • ✅ 选中后显示"已选择"标记
  • ✅ 点击"继续"按钮跳转

步骤3:位置信息

  • ✅ 显示进度"1/3"
  • ✅ 可以输入省份、城市、气候带
  • ✅ 点击"上一步"返回
  • ✅ 点击"下一步"跳转

步骤4:完成设置

  • ✅ 输入昵称
  • ✅ 点击"完成"保存数据
  • ✅ 跳转到主页面

7.2 验证数据持久化

重启应用,应该直接进入主页面,不再显示引导流程。


八、总结

本篇教程完成了:

  • ✅ 欢迎页面
  • ✅ 模式选择页面
  • ✅ 位置信息页面
  • ✅ 目标设置页面
  • ✅ 引导服务
  • ✅ 用户画像建立
  • ✅ 数据持久化

九、下一步

在下一篇教程中,我们将学习:

  • 高德地图SDK集成
  • 地图首页布局设计
  • 地图显示与交互
  • 定位服务实现

教程版本 :v1.0
更新日期 :2026-01
适用版本:DevEco Studio 5.0+, HarmonyOS API 17+

相关推荐
zhujian826372 小时前
二十八、【鸿蒙 NEXT】orm框架
数据库·华为·sqlite·harmonyos·orm框架
AI_零食2 小时前
鸿蒙跨端框架 Flutter 学习 Day 6:Future 在 UI 渲染中的心跳逻辑
学习·flutter·ui·华为·harmonyos·鸿蒙
信创天地2 小时前
信创日志全流程管控:ELK国产化版本与华为日志服务实战应用
运维·安全·elk·华为·rabbitmq·dubbo
信创天地2 小时前
国产关系型数据库部署与权限管理实战:人大金仓、达梦、南大通用、华为GaussDB
数据库·华为·gaussdb
夜雨声烦丿2 小时前
Flutter 框架跨平台鸿蒙开发 - 文字反转工具应用开发教程
flutter·华为·harmonyos
哈哈你是真的厉害2 小时前
基础入门 React Native 鸿蒙跨平台开发:实现一个简单的倒计时工具
react native·react.js·harmonyos
南村群童欺我老无力.2 小时前
Flutter 框架跨平台鸿蒙开发 - 虚拟骰子应用开发教程
flutter·华为·harmonyos
lbb 小魔仙2 小时前
【Harmonyos】开源鸿蒙跨平台训练营DAY2:多终端工程创建运行、代码提交至AtomGit平台自建公开仓库全流程(附带出现问题及解决方法)
windows·flutter·开源·harmonyos·鸿蒙·开源鸿蒙·鸿蒙开平台应用
AI_零食2 小时前
鸿蒙跨端框架Flutter学习day 2、常用UI组件-弹性布局进阶之道
学习·flutter·ui·华为·harmonyos·鸿蒙