在这里插入图片描述

前言

地址管理是商城应用中订单配送的基础功能,用户需要添加、编辑、删除收货地址,并在下单时选择配送地址。一个设计良好的地址管理组件能够让用户快速完成地址操作,减少下单过程中的摩擦。本文将详细介绍如何在Flutter和OpenHarmony平台上开发地址管理相关组件,包括地址列表、地址编辑表单、地址选择器等核心模块。

地址信息的准确性直接影响订单的配送成功率,因此地址表单的设计需要引导用户填写完整准确的信息。同时,为了提升用户体验,我们还需要支持地址的快速选择、默认地址设置、地址智能解析等功能,让用户能够以最少的操作完成地址管理。

Flutter地址数据模型

首先定义地址数据的模型结构:

class Address {
  final String id;
  final String name;
  final String phone;
  final String province;
  final String city;
  final String district;
  final String detail;
  final bool isDefault;

  const Address({
    required this.id,
    required this.name,
    required this.phone,
    required this.province,
    required this.city,
    required this.district,
    required this.detail,
    this.isDefault = false,
  });

  String get fullAddress => '$province$city$district$detail';
}

Address类包含了收货地址的所有必要字段。name是收货人姓名,phone是联系电话,province、city、district分别是省、市、区三级行政区划,detail是详细地址。isDefault标记是否为默认地址,下单时会自动选中默认地址。fullAddress是一个计算属性,将省市区和详细地址拼接成完整的地址字符串,方便在界面上展示。这种数据模型的设计符合国内电商的地址规范,便于与物流系统对接。

地址列表组件

class AddressList extends StatelessWidget {
  final List<Address> addresses;
  final String? selectedId;
  final ValueChanged<Address>? onSelect;
  final ValueChanged<Address>? onEdit;
  final ValueChanged<Address>? onDelete;

  const AddressList({
    Key? key,
    required this.addresses,
    this.selectedId,
    this.onSelect,
    this.onEdit,
    this.onDelete,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return ListView.separated(
      padding: const EdgeInsets.all(16),
      itemCount: addresses.length,
      separatorBuilder: (_, __) => const SizedBox(height: 12),
      itemBuilder: (context, index) {
        return _buildAddressCard(addresses[index]);
      },
    );
  }
}

AddressList组件展示用户的所有收货地址。selectedId用于标记当前选中的地址,在地址选择场景下使用。onSelect、onEdit、onDelete分别是选择、编辑、删除操作的回调函数。ListView.separated使用分隔构建器在地址卡片之间添加间距,比在每个卡片上设置margin更加灵活。这种设计将列表的展示逻辑与业务操作分离,组件可以在不同场景下复用。

地址卡片的实现:

Widget _buildAddressCard(Address address) {
  final isSelected = address.id == selectedId;
  
  return GestureDetector(
    onTap: () => onSelect?.call(address),
    child: Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(
          color: isSelected 
            ? const Color(0xFFE53935) 
            : const Color(0xFFEEEEEE),
          width: isSelected ? 2 : 1,
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildHeader(address),
          const SizedBox(height: 8),
          _buildAddressText(address),
          const SizedBox(height: 12),
          _buildActions(address),
        ],
      ),
    ),
  );
}

地址卡片根据选中状态显示不同的边框样式,选中时使用红色粗边框,未选中时使用灰色细边框。Container设置白色背景和圆角,内部使用Column垂直排列收货人信息、地址文本和操作按钮。GestureDetector包装整个卡片,点击时触发选择回调。这种视觉设计让用户能够清晰地识别当前选中的地址,同时保持界面的整洁美观。

收货人信息头部:

Widget _buildHeader(Address address) {
  return Row(
    children: [
      Text(
        address.name,
        style: const TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.w600,
          color: Color(0xFF333333),
        ),
      ),
      const SizedBox(width: 12),
      Text(
        address.phone,
        style: const TextStyle(
          fontSize: 14,
          color: Color(0xFF666666),
        ),
      ),
      const Spacer(),
      if (address.isDefault)
        Container(
          padding: const EdgeInsets.symmetric(
            horizontal: 6,
            vertical: 2,
          ),
          decoration: BoxDecoration(
            color: const Color(0xFFFFF0F0),
            borderRadius: BorderRadius.circular(2),
          ),
          child: const Text(
            '默认',
            style: TextStyle(
              fontSize: 10,
              color: Color(0xFFE53935),
            ),
          ),
        ),
    ],
  );
}

头部信息包含收货人姓名、电话和默认标签。姓名使用较大字号和粗体突出显示,电话使用较小字号作为辅助信息。Spacer将默认标签推到右侧,只有当地址是默认地址时才显示标签。标签使用浅红色背景和红色文字,与主题色保持一致。这种布局将最重要的信息放在最显眼的位置,用户可以快速识别每个地址的关键信息。

地址操作按钮

Widget _buildActions(Address address) {
  return Row(
    children: [
      _buildActionButton(
        icon: Icons.edit_outlined,
        label: '编辑',
        onTap: () => onEdit?.call(address),
      ),
      const SizedBox(width: 24),
      _buildActionButton(
        icon: Icons.delete_outline,
        label: '删除',
        onTap: () => onDelete?.call(address),
      ),
    ],
  );
}

Widget _buildActionButton({
  required IconData icon,
  required String label,
  required VoidCallback onTap,
}) {
  return GestureDetector(
    onTap: onTap,
    child: Row(
      children: [
        Icon(icon, size: 16, color: const Color(0xFF999999)),
        const SizedBox(width: 4),
        Text(
          label,
          style: const TextStyle(
            fontSize: 12,
            color: Color(0xFF999999),
          ),
        ),
      ],
    ),
  );
}

操作按钮区域包含编辑和删除两个功能按钮。每个按钮由图标和文字组成,使用灰色配色表明这是次要操作,不会与主要的选择操作产生视觉冲突。_buildActionButton是一个可复用的按钮构建方法,接收图标、文字和点击回调作为参数。Row水平排列两个按钮,24像素的间距使它们保持适当的视觉分隔。这种设计让用户可以方便地对地址进行管理操作。

OpenHarmony地址列表实现

@Component
struct AddressList {
  @Prop addresses: AddressInfo[] = []
  @Prop selectedId: string = ''
  private onSelect: (address: AddressInfo) => void = () => {}
  private onEdit: (address: AddressInfo) => void = () => {}
  private onDelete: (address: AddressInfo) => void = () => {}

  build() {
    List() {
      ForEach(this.addresses, (address: AddressInfo) => {
        ListItem() {
          this.AddressCard(address)
        }
        .margin({ bottom: 12 })
      })
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

OpenHarmony的地址列表使用List组件实现。@Prop装饰的属性从父组件接收数据,包括地址列表和选中ID。ForEach遍历地址数组,为每个地址创建ListItem。每个列表项设置底部外边距实现项间分隔。List组件设置100%的宽高填充父容器,padding设置内边距。这种实现方式与Flutter版本结构一致,确保两个平台的功能和体验统一。

地址数据接口定义:

interface AddressInfo {
  id: string
  name: string
  phone: string
  province: string
  city: string
  district: string
  detail: string
  isDefault: boolean
}

TypeScript接口定义了与Flutter相同的地址数据结构。所有字段都是必需的,isDefault使用boolean类型表示是否为默认地址。接口定义为组件提供了类型安全保障,在编译时就能发现类型错误,减少运行时问题。

地址卡片ArkUI实现

@Builder
AddressCard(address: AddressInfo) {
  Column() {
    this.CardHeader(address)
    
    Text(address.province + address.city + address.district + address.detail)
      .fontSize(14)
      .fontColor('#666666')
      .margin({ top: 8 })
      .maxLines(2)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
    
    this.CardActions(address)
  }
  .width('100%')
  .padding(16)
  .backgroundColor(Color.White)
  .borderRadius(8)
  .border({
    width: this.selectedId === address.id ? 2 : 1,
    color: this.selectedId === address.id ? '#E53935' : '#EEEEEE'
  })
  .onClick(() => {
    this.onSelect(address)
  })
}

@Builder装饰器定义了地址卡片的构建方法。Column垂直排列头部信息、地址文本和操作按钮。地址文本通过字符串拼接生成完整地址,限制两行显示并设置溢出省略。border属性根据选中状态设置不同的边框宽度和颜色。onClick事件处理器在用户点击卡片时触发选择回调。这种实现方式简洁高效,与Flutter版本的视觉效果完全一致。

卡片头部ArkUI实现:

@Builder
CardHeader(address: AddressInfo) {
  Row() {
    Text(address.name)
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
      .fontColor('#333333')
    
    Text(address.phone)
      .fontSize(14)
      .fontColor('#666666')
      .margin({ left: 12 })
    
    Blank()
    
    if (address.isDefault) {
      Text('默认')
        .fontSize(10)
        .fontColor('#E53935')
        .padding({ left: 6, right: 6, top: 2, bottom: 2 })
        .backgroundColor('#FFF0F0')
        .borderRadius(2)
    }
  }
  .width('100%')
}

Row水平排列收货人姓名、电话和默认标签。Blank组件占据剩余空间,将默认标签推到右侧,相当于Flutter中的Spacer。条件渲染使用if语句,只有当isDefault为true时才显示默认标签。样式设置与Flutter版本保持一致,确保跨平台的视觉统一性。

地址编辑表单

class AddressForm extends StatefulWidget {
  final Address? address;
  final ValueChanged<Address>? onSave;

  const AddressForm({
    Key? key,
    this.address,
    this.onSave,
  }) : super(key: key);

  
  State<AddressForm> createState() => _AddressFormState();
}

class _AddressFormState extends State<AddressForm> {
  final _nameController = TextEditingController();
  final _phoneController = TextEditingController();
  final _detailController = TextEditingController();
  String _selectedRegion = '';
  bool _isDefault = false;

  
  void initState() {
    super.initState();
    if (widget.address != null) {
      _nameController.text = widget.address!.name;
      _phoneController.text = widget.address!.phone;
      _detailController.text = widget.address!.detail;
      _selectedRegion = '${widget.address!.province}${widget.address!.city}${widget.address!.district}';
      _isDefault = widget.address!.isDefault;
    }
  }
}

AddressForm组件用于添加和编辑地址。当address参数不为空时,表示编辑模式,需要将现有地址数据填充到表单中。组件使用多个TextEditingController管理输入框的文本内容,_selectedRegion存储选择的省市区,_isDefault存储是否设为默认地址。initState中根据传入的地址数据初始化表单状态,这种设计使同一个组件可以同时支持添加和编辑两种场景。

表单输入项:

Widget _buildFormField({
  required String label,
  required TextEditingController controller,
  String? hintText,
  TextInputType? keyboardType,
  int maxLines = 1,
}) {
  return Padding(
    padding: const EdgeInsets.only(bottom: 16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          label,
          style: const TextStyle(
            fontSize: 14,
            color: Color(0xFF333333),
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: controller,
          keyboardType: keyboardType,
          maxLines: maxLines,
          decoration: InputDecoration(
            hintText: hintText,
            hintStyle: const TextStyle(
              fontSize: 14,
              color: Color(0xFFCCCCCC),
            ),
            contentPadding: const EdgeInsets.all(12),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(4),
              borderSide: const BorderSide(color: Color(0xFFEEEEEE)),
            ),
          ),
        ),
      ],
    ),
  );
}

_buildFormField是一个可复用的表单项构建方法,接收标签、控制器、提示文字、键盘类型和行数作为参数。Column垂直排列标签和输入框,标签在上方清晰标识输入内容。TextField使用OutlineInputBorder显示边框样式,contentPadding设置内边距使输入区域更加舒适。这种封装方式减少了重复代码,使表单的构建更加简洁。

总结

本文详细介绍了Flutter和OpenHarmony平台上地址管理组件的开发过程。地址管理作为商城应用的基础功能,其设计质量直接影响用户的下单体验。通过地址列表、地址卡片、地址表单等组件的合理设计,我们为用户提供了便捷的地址管理功能。在实际项目中,还可以进一步添加地址智能解析、地图定位选址、地址收藏等高级功能,提升用户体验。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐