HarmonyOS 6商城开发学习:收货地址列表增删改设为默认——List驱动@State与联系人/位置服务快捷填充实战
在HarmonyOS 6购物比价或电商类应用中,"收货地址管理"是下单链路的必经环节:用户可查看地址列表、新增/编辑/删除地址、设为默认,新增时支持从系统通讯录拉取姓名电话、从系统位置服务选取省市区及详细地址。常见坑是删除后列表不刷新、Radio默认选中状态错位、跳转回传新地址未合并。
本文将基于官方行业实践说明,用 List + @State数组驱动 + 路由参数回传 + contact/location权限调用示意 完整实现一个可商用的地址管理页。
一、需求拆解与数据模型
1. 功能点
-
地址列表按"默认置顶 + 其余按添加时间倒序"展示
-
左滑/按钮删除(带确认),删除默认地址自动将第一条置默认
-
点击"设为默认" → 对应项
isDefault=true,其余false,列表重排 -
新增/编辑页填写后
router.back()带回地址对象 → 主页onPageShow合并入列表 -
新增页"从通讯录导入"→ 拉
contactpicker(示意);"从位置选取"→ 拉geoLocationManager(示意)
2. 数据模型
// model/Address.ets
export interface Address {
id: string;
name: string; // 收货人
phone: string; // 手机号
province: string; // "广东省"
city: string; // "深圳市"
district: string; // "南山区"
detail: string; // "科技南路XX号XX栋"
isDefault: boolean;
tag?: string; // "公司"/"家"/undefined
}
模拟初始数据:
// mock/addressData.ets
import { Address } from '../model/Address';
export const INIT_ADDRESSES: Address[] = [
{
id: 'A001', name: '张三', phone: '138****0001',
province: '广东省', city: '深圳市', district: '南山区',
detail: '科技南路XX号XX栋', isDefault: true, tag: '家'
},
{
id: 'A002', name: '李四', phone: '139****0002',
province: '北京市', city: '北京市', district: '海淀区',
detail: '中关村大街XX号', isDefault: false, tag: '公司'
}
];
二、地址管理主页(列表 + 增删改默认)
// pages/AddressManagePage.ets
import { router } from '@kit.ArkUI';
import { INIT_ADDRESSES, Address } from '../mock/addressData';
import { promptAction } from '@kit.ArkUI';
const PAGE_ADDR_EDIT = 'pages/AddressEditPage'; // 新增/编辑页URL
@Entry
@Component
struct AddressManagePage {
// 地址列表(默认置顶排序)
@State addresses: Address[] = this.sortAddresses(INIT_ADDRESSES);
// 置顶默认 + 其余按原顺序
sortAddresses(list: Address[]): Address[] {
const def = list.filter(a => a.isDefault);
const rest = list.filter(a => !a.isDefault);
return [...def, ...rest];
}
// 删除
deleteAddr(id: string) {
// 简单确认(可用AlertDialog替换)
this.addresses = this.addresses.filter(a => a.id !== id);
// 若删掉的是默认且还有地址 → 第一条自动变默认
if (!this.addresses.some(a => a.isDefault) && this.addresses.length > 0) {
this.addresses[0].isDefault = true;
}
this.addresses = this.sortAddresses(this.addresses);
}
// 设为默认
setDefault(id: string) {
this.addresses.forEach(a => a.isDefault = (a.id === id));
this.addresses = this.sortAddresses(this.addresses);
}
// 接收编辑页回传的新/修改后的地址
mergeOrUpdate(addr: Address) {
const idx = this.addresses.findIndex(a => a.id === addr.id);
if (idx >= 0) {
this.addresses[idx] = addr; // 编辑
} else {
this.addresses.unshift(addr); // 新增
}
this.addresses = this.sortAddresses(this.addresses);
}
// 页面显示时处理路由回参
onPageShow() {
const ctx = this.getUIContext().getHostContext!();
const params = router.getParams() as Record<string, Address> | undefined;
if (params?.['savedAddress']) {
this.mergeOrUpdate(params['savedAddress']);
router.clearParams(); // 消费掉
}
}
build() {
Column() {
// 标题栏
Row {
Text('收货地址')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button('+ 新增')
.height(34)
.fontSize(13)
.backgroundColor('#FF5722')
.borderRadius(17)
.padding({ horizontal: 14 })
.onClick(() => {
router.pushUrl({ url: PAGE_ADDR_EDIT });
})
}
.padding({ horizontal: 16, top: 16, bottom: 10 })
// 地址列表
if (this.addresses.length === 0) {
Column() {
Text('暂无收货地址')
.fontSize(14)
.fontColor('#BBB')
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
List() {
ForEach(this.addresses, (item: Address) => {
ListItem() {
Column({ space: 6 }) {
// 顶部行:姓名+电话+标签+默认徽章
Row() {
Text(item.name).fontSize(16).fontWeight(FontWeight.Medium)
Text(item.phone).fontSize(13).fontColor('#888').margin({ left: 8 })
if (item.tag) {
Text(item.tag)
.fontSize(11)
.fontColor('#FF5722')
.border({ width: 1, color: '#FF5722' })
.borderRadius(4)
.padding({ horizontal: 6, vertical: 1 })
.margin({ left: 8 })
}
Blank()
if (item.isDefault) {
Text('默认')
.fontSize(11)
.fontColor('#4CAF50')
.border({ width: 1, color: '#4CAF50' })
.borderRadius(4)
.padding({ horizontal: 6, vertical: 1 })
}
}
// 地址详情
Text(`${item.province}${item.city}${item.district} ${item.detail}`)
.fontSize(13)
.fontColor('#555')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 操作区
Row({ space: 20 }) {
Text('编辑')
.fontSize(12)
.fontColor('#1976D2')
.onClick(() => {
// 传当前地址给编辑页
router.pushUrl({
url: PAGE_ADDR_EDIT,
params: { editAddress: item }
});
})
Text('删除')
.fontSize(12)
.fontColor('#F44336')
.onClick(() => this.deleteAddr(item.id))
Blank()
Text('设为默认')
.fontSize(12)
.fontColor(item.isDefault ? '#CCC' : '#FF5722')
.enabled(!item.isDefault)
.onClick(() => {
if (!item.isDefault) this.setDefault(item.id);
})
}
.margin({ top: 4 })
}
.padding(14)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 3, color: 'rgba(0,0,0,0.05)', offsetX: 0, offsetY: 1 })
}
}, (item: Address) => item.id)
}
.padding(12)
.layoutWeight(1)
.edgeEffect(EdgeEffect.Spring)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F6F8')
}
}
三、新增/编辑页(含通讯录与位置示意)
// pages/AddressEditPage.ets
import { router } from '@kit.ArkUI';
import { Address } from '../mock/addressData';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct AddressEditPage {
// 编辑时接收旧数据,新增时生成空
private editData: Address | undefined = (router.getParams() as any)?.['editAddress'];
@State id: string = this.editData?.id ?? `ADDR_${Date.now()}`;
@State name: string = this.editData?.name ?? '';
@State phone: string = this.editData?.phone ?? '';
@State province: string = this.editData?.province ?? '';
@State city: string = this.editData?.city ?? '';
@State district: string = this.editData?.district ?? '';
@State detail: string = this.editData?.detail ?? '';
@State tag: string = this.editData?.tag ?? '';
@State isDefault: boolean = this.editData?.isDefault ?? false;
// 保存回传
save() {
if (!this.name.trim() || !this.phone.trim() || !this.detail.trim()) {
this.getUIContext().getPromptAction().showToast({ message: '请填写必填项' });
return;
}
const addr: Address = {
id: this.id,
name: this.name.trim(),
phone: this.phone.trim(),
province: this.province,
city: this.city,
district: this.district,
detail: this.detail.trim(),
isDefault: this.isDefault,
tag: this.tag || undefined
};
router.back({ params: { savedAddress: addr } });
}
// ===== 示意:从系统通讯录拉姓名+电话 =====
pickContact() {
// 真实项目需调用 @ohos.contact.contactPicker.pickContacts()
// 下面为示意回填
this.name = '王五(示例)';
this.phone = '136****1234';
this.getUIContext().getPromptAction().showToast({ message: '已从通讯录导入' });
}
// ===== 示意:从系统位置服务选取省市区 =====
pickLocation() {
// 真实项目需调用 geoLocationManager.getAddressesFromLocation() 或系统选择器
this.province = '上海市';
this.city = '上海市';
this.district = '浦东新区';
this.getUIContext().getPromptAction().showToast({ message: '已选取位置' });
}
build() {
Column({ space: 14 }) {
Text(this.editData ? '编辑地址' : '新增地址')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.alignSelf(HorizontalAlign.Start)
.margin({ top: 16, left: 16 })
// 表单区
Scroll() {
Column({ space: 12 }) {
// 姓名+电话行
Row({ space: 8 }) {
TextInput({ placeholder: '收货人姓名', text: this.name })
.layoutWeight(1)
.onChange(v => this.name = v)
TextInput({ placeholder: '手机号', text: this.phone })
.layoutWeight(1)
.onChange(v => this.phone = v)
}
// 省市区
Row({ space: 8 }) {
Text(`${this.province || '省'}/${this.city || '市'}/${this.district || '区'}`)
.fontSize(13)
.fontColor(this.province ? '#333' : '#AAA')
.layoutWeight(1)
Button('选择位置')
.height(32)
.fontSize(12)
.backgroundColor('#1976D2')
.borderRadius(16)
.padding({ horizontal: 10 })
.onClick(() => this.pickLocation())
}
// 详细地址
TextArea({ placeholder: '详细地址(街道/楼号/门牌)', text: this.detail })
.onChange(v => this.detail = v)
.height(70)
// 标签
Row({ space: 8 }) {
this.tagBtn('家', '家');
this.tagBtn('公司', '公司');
this.tagBtn('学校', '学校');
}
// 设为默认
Row() {
Checkbox({ name: 'isDefault', select: this.isDefault })
.onChange(v => this.isDefault = v)
Text('设为默认收货地址').fontSize(13).margin({ left: 6 })
}
.margin({ top: 8 })
// 通讯录导入
Button('从通讯录导入姓名/电话')
.width('100%')
.height(38)
.backgroundColor('#F5F5F5')
.fontColor('#1976D2')
.borderRadius(8)
.onClick(() => this.pickContact())
}
.padding(16)
}
.layoutWeight(1)
// 保存按钮
Button('保存地址')
.width('90%')
.height(44)
.backgroundColor('#FF5722')
.borderRadius(22)
.margin({ bottom: 24 })
.onClick(() => this.save())
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
}
@Builder
tagBtn(label: string, val: string) {
Button(label)
.height(30)
.fontSize(12)
.backgroundColor(this.tag === val ? '#FF5722' : '#F0F0F0')
.fontColor(this.tag === val ? Color.White : '#666')
.borderRadius(15)
.padding({ horizontal: 12 })
.onClick(() => this.tag = val)
}
}
权限说明(开发注意):
通讯录导入需
ohos.permission.READ_CONTACTS(需在 module.json5 声明 + 动态申请)位置选取需
ohos.permission.LOCATION(精确)或ohos.permission.APPROXIMATELY_LOCATION,并引导用户开启定位
四、避坑指南
|
问题 |
原因 |
修复 |
|---|---|---|
|
删除/新增后列表不刷新 |
|
每次变动后 |
|
默认地址 Radio/徽章错乱 |
用 index 做默认判断 |
用 |
|
编辑后回传数据主页不更新 |
|
在 |
|
ForEach key用 index |
列表重排状态错位 |
key用 |
|
删默认地址后无默认 |
删除后未检查重选默认 |
删除后 |
五、总结:地址管理SOP
-
列表驱动:
@State addresses: Address[],增删改均生成新数组 → 重排(sortAddresses默认置顶) -
路由回传:编辑页
router.back({params:{savedAddress}}),管理页onPageShow合并或更新 -
快捷填充:新增页调 Contact Picker / Location Picker 回填字段(需对应权限)
-
默认逻辑:设为默认时全清旧默认再置 true;删除默认地址自动将第一条变默认
核心法则:HarmonyOS 6 电商地址管理 = List + @State数组不可变更新 + router params回传合并 + 默认置顶排序,简单可靠、无三方依赖。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。
更多推荐



所有评论(0)