##UniApp##
UNIapp在购物类应用中的实践与ArkTS实现
引言
随着移动电商的蓬勃发展,购物类应用已成为人们日常生活中不可或缺的一部分。UNIapp作为一款高效的跨平台开发框架,结合ArkTS的强大能力,为电商应用的开发提供了理想的解决方案。本文将探讨UNIapp在购物类应用中的优势,并通过ArkTS代码展示核心功能的实现。
UNIapp在购物应用中的优势
- 跨平台一致性:一套代码可同时发布到iOS、Android及各种小程序平台,保持一致的购物体验
- 性能与体验:接近原生的性能表现,确保商品列表流畅滚动和交互动画顺滑
- 开发效率:基于Vue.js的语法体系,快速实现复杂电商界面和交互逻辑
- 生态支持:丰富的插件市场提供支付、分享、统计等电商常用功能
购物应用核心功能实现
1. 商品首页与分类展示
// 电商首页组件
@Component
struct ShopHome {
@State banners: Banner[] = [] // 轮播广告
@State categories: Category[] = [] // 商品分类
@State hotProducts: Product[] = [] // 热销商品
@State newProducts: Product[] = [] // 新品上市
@State isLoading: boolean = false // 加载状态
aboutToAppear() {
this.loadHomeData()
}
build() {
Column() {
// 搜索栏
Row() {
TextInput({ placeholder: '搜索商品...' })
.layoutWeight(1)
.borderRadius(20)
.backgroundColor('#F5F5F5')
.padding(10)
Button({ icon: 'search' })
.margin({ left: 10 })
}
.padding(10)
Scroll() {
Column() {
// 轮播广告
Swiper() {
ForEach(this.banners, (banner: Banner) => {
Image(banner.imageUrl)
.width('100%')
.height(150)
.onClick(() => this.handleBannerClick(banner))
})
}
.indicator(true)
.autoPlay(true)
.interval(3000)
.height(150)
.margin({ bottom: 10 })
// 商品分类
Grid() {
ForEach(this.categories, (category: Category) => {
GridItem() {
Column() {
Image(category.icon)
.width(40)
.height(40)
Text(category.name)
.fontSize(12)
.margin({ top: 5 })
}
.onClick(() => this.navigateToCategory(category))
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr')
.height(80)
.margin({ bottom: 10 })
// 热销商品
SectionTitle('热销商品')
ProductGrid({ products: this.hotProducts })
// 新品上市
SectionTitle('新品上市')
ProductGrid({ products: this.newProducts })
}
}
.layoutWeight(1)
}
}
private async loadHomeData() {
this.isLoading = true
try {
const [banners, categories, hotProducts, newProducts] = await Promise.all([
http.get('/api/banners'),
http.get('/api/categories'),
http.get('/api/products/hot'),
http.get('/api/products/new')
])
this.banners = banners
this.categories = categories
this.hotProducts = hotProducts
this.newProducts = newProducts
} finally {
this.isLoading = false
}
}
private handleBannerClick(banner: Banner) {
// 处理广告点击,可能跳转商品详情或活动页面
router.push({ url: banner.link })
}
private navigateToCategory(category: Category) {
router.push({
url: 'pages/category',
params: { categoryId: category.id }
})
}
}
// 商品网格组件
@Component
struct ProductGrid {
@Prop products: Product[]
build() {
Grid() {
ForEach(this.products, (product: Product) => {
GridItem() {
ProductCard({ product: product })
}
}
}
.columnsTemplate('1fr 1fr')
.rowsTemplate('auto')
.columnsGap(10)
.rowsGap(10)
.margin({ bottom: 20 })
}
}
// 商品卡片组件
@Component
struct ProductCard {
@Prop product: Product
build() {
Column() {
Image(this.product.image)
.width('100%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
.borderRadius(5)
Text(this.product.name)
.fontSize(14)
.margin({ top: 5 })
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
Text(`¥${this.product.price.toFixed(2)}`)
.fontSize(16)
.fontColor('#FF5000')
.fontWeight(FontWeight.Bold)
if (this.product.originalPrice > this.product.price) {
Text(`¥${this.product.originalPrice.toFixed(2)}`)
.fontSize(12)
.fontColor('#999')
.margin({ left: 5 })
.decoration({ type: TextDecorationType.LineThrough })
}
}
.margin({ top: 5 })
Row() {
Text(`已售${this.product.sales}`)
.fontSize(12)
.fontColor('#999')
if (this.product.stock < 10) {
Text('仅剩少量')
.fontSize(12)
.fontColor('#FF5000')
.margin({ left: 5 })
}
}
.margin({ top: 5 })
}
.padding(5)
.backgroundColor(Color.White)
.borderRadius(5)
.shadow({ radius: 2, color: '#EEE' })
.onClick(() => {
router.push({
url: 'pages/product/detail',
params: { id: this.product.id }
})
})
}
}
2. 商品详情页
// 商品详情页组件
@Component
struct ProductDetail {
@State product: ProductDetail | null = null
@State selectedSku: Sku | null = null
@State quantity: number = 1
@State showSkuPicker: boolean = false
@State isFavorite: boolean = false
@State relatedProducts: Product[] = []
aboutToAppear() {
const params = router.getParams()
this.loadProductDetail(params.id)
}
build() {
Column() {
if (this.product) {
Scroll() {
Column() {
// 商品图片轮播
Swiper() {
ForEach(this.product.images, (image: string) => {
Image(image)
.width('100%')
.height(300)
.objectFit(ImageFit.Cover)
})
}
.indicator(true)
.height(300)
// 商品基本信息
Column() {
Text(this.product.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
Text(this.product.description)
.fontSize(14)
.fontColor('#666')
.margin({ bottom: 10 })
Row() {
Text(`¥${this.product.price.toFixed(2)}`)
.fontSize(22)
.fontColor('#FF5000')
.fontWeight(FontWeight.Bold)
if (this.product.originalPrice > this.product.price) {
Text(`¥${this.product.originalPrice.toFixed(2)}`)
.fontSize(14)
.fontColor('#999')
.margin({ left: 5 })
.decoration({ type: TextDecorationType.LineThrough })
}
if (this.product.discount) {
Text(`${this.product.discount}折`)
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#FF5000')
.borderRadius(3)
.padding({ left: 5, right: 5, top: 2, bottom: 2 })
.margin({ left: 5 })
}
}
.margin({ bottom: 10 })
// 商品规格选择
Row() {
Text('选择:')
.fontSize(14)
.fontColor('#666')
Text(this.selectedSku ? this.selectedSku.name : '请选择规格')
.fontSize(14)
.layoutWeight(1)
Image('arrow_right')
.width(12)
.height(12)
}
.padding(10)
.backgroundColor('#FAFAFA')
.borderRadius(5)
.onClick(() => {
this.showSkuPicker = true
})
}
.padding(15)
// 商品详情
Web({ src: this.product.detailHtml })
.height(800)
// 相关推荐
SectionTitle('猜你喜欢')
ProductGrid({ products: this.relatedProducts })
.margin({ bottom: 80 })
}
}
.layoutWeight(1)
// 底部操作栏
Row() {
Column() {
Button({ icon: this.isFavorite ? 'heart' : 'heart-outline' })
.onClick(() => this.toggleFavorite())
Text('收藏')
.fontSize(10)
}
.margin({ right: 15 })
Column() {
Button({ icon: 'cart' })
.onClick(() => this.navigateToCart())
Text('购物车')
.fontSize(10)
}
.margin({ right: 15 })
Button('加入购物车', { type: ButtonType.Normal })
.layoutWeight(1)
.backgroundColor('#FF9500')
.fontColor(Color.White)
.onClick(() => this.addToCart())
Button('立即购买', { type: ButtonType.Normal })
.layoutWeight(1)
.backgroundColor('#FF5000')
.fontColor(Color.White)
.margin({ left: 10 })
.onClick(() => this.buyNow())
}
.padding(10)
.backgroundColor(Color.White)
.border({ width: 1, color: '#EEE' })
} else {
LoadingProgress()
}
}
// SKU选择弹窗
if (this.showSkuPicker && this.product) {
SkuPicker({
skus: this.product.skus,
selectedSku: this.selectedSku,
quantity: this.quantity,
onConfirm: (sku: Sku, qty: number) => {
this.selectedSku = sku
this.quantity = qty
this.showSkuPicker = false
},
onClose: () => {
this.showSkuPicker = false
}
})
}
}
private async loadProductDetail(id: string) {
this.product = await http.get(`/api/products/${id}`)
this.selectedSku = this.product.skus[0]
this.loadRelatedProducts(id)
this.checkFavoriteStatus(id)
}
private async loadRelatedProducts(productId: string) {
this.relatedProducts = await http.get(`/api/products/related/${productId}`)
}
private async checkFavoriteStatus(productId: string) {
this.isFavorite = await http.get(`/api/favorite/status/${productId}`)
}
private async toggleFavorite() {
if (this.isFavorite) {
await http.delete(`/api/favorite/${this.product?.id}`)
} else {
await http.post('/api/favorite', { productId: this.product?.id })
}
this.isFavorite = !this.isFavorite
}
private addToCart() {
if (!this.selectedSku) {
prompt.showToast({ message: '请选择商品规格' })
return
}
http.post('/api/cart', {
productId: this.product?.id,
skuId: this.selectedSku.id,
quantity: this.quantity
})
prompt.showToast({ message: '已加入购物车' })
}
private buyNow() {
if (!this.selectedSku) {
prompt.showToast({ message: '请选择商品规格' })
return
}
router.push({
url: 'pages/order/confirm',
params: {
items: JSON.stringify([{
productId: this.product?.id,
skuId: this.selectedSku.id,
quantity: this.quantity
}])
}
})
}
private navigateToCart() {
router.push({ url: 'pages/cart' })
}
}
// SKU选择器组件
@Component
struct SkuPicker {
@Prop skus: Sku[]
@Prop selectedSku: Sku | null
@Prop quantity: number
private onConfirm: (sku: Sku, quantity: number) => void
private onClose: () => void
@State tempSelectedSku: Sku | null = this.selectedSku
@State tempQuantity: number = this.quantity
build() {
Column() {
Column() {
// 标题和关闭按钮
Row() {
Text('选择规格')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Button({ icon: 'close' })
.onClick(() => this.onClose())
}
.margin({ bottom: 15 })
// SKU选择
Text('规格')
.fontSize(14)
.fontColor('#666')
.margin({ bottom: 10 })
Wrap() {
ForEach(this.skus, (sku: Sku) => {
Button(sku.name, { type: ButtonType.Capsule })
.stateEffect(true)
.backgroundColor(this.tempSelectedSku?.id === sku.id ? '#FFF0E6' : '#F5F5F5')
.fontColor(this.tempSelectedSku?.id === sku.id ? '#FF5000' : '#333')
.margin({ right: 10, bottom: 10 })
.onClick(() => {
this.tempSelectedSku = sku
})
})
}
.margin({ bottom: 20 })
// 数量选择
Row() {
Text('数量')
.fontSize(14)
.fontColor('#666')
.layoutWeight(1)
Stepper({
value: this.tempQuantity,
min: 1,
max: this.tempSelectedSku?.stock || 10,
step: 1
})
.onChange((value: number) => {
this.tempQuantity = value
})
}
.margin({ bottom: 20 })
// 确认按钮
Button('确定')
.width('100%')
.height(40)
.backgroundColor('#FF5000')
.fontColor(Color.White)
.onClick(() => {
if (this.tempSelectedSku) {
this.onConfirm(this.tempSelectedSku, this.tempQuantity)
}
})
}
.padding(20)
.backgroundColor(Color.White)
.borderRadius(10)
.width('100%')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.End)
.backgroundColor('#80000000')
}
}
3. 购物车功能实现
// 购物车页面组件
@Component
struct CartPage {
@State cartItems: CartItem[] = []
@State selectedItems: string[] = [] // 选中的商品ID数组
@State isEditing: boolean = false // 是否编辑状态
@State isLoading: boolean = false
aboutToAppear() {
this.loadCartItems()
}
build() {
Column() {
// 购物车列表
if (this.cartItems.length > 0) {
List() {
ForEach(this.cartItems, (item: CartItem) => {
ListItem() {
CartItemCard({
item: item,
isSelected: this.selectedItems.includes(item.id),
isEditing: this.isEditing,
onSelect: (selected: boolean) => this.toggleItemSelect(item.id, selected),
onQuantityChange: (quantity: number) => this.updateItemQuantity(item.id, quantity),
onRemove: () => this.removeItem(item.id)
})
}
})
}
.layoutWeight(1)
} else {
Column() {
Image('empty_cart')
.width(150)
.height(150)
Text('购物车是空的')
.fontSize(16)
.margin({ top: 20 })
Button('去逛逛')
.margin({ top: 20 })
.onClick(() => {
router.replace({ url: 'pages/home' })
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.layoutWeight(1)
}
// 底部结算栏
if (this.cartItems.length > 0) {
Row() {
// 全选
Row() {
Checkbox(this.isAllSelected())
.onChange((checked: boolean) => {
this.toggleAllSelect(checked)
})
Text('全选')
.fontSize(14)
.margin({ left: 5 })
}
.onClick(() => {
this.toggleAllSelect(!this.isAllSelected())
})
// 编辑/完成按钮
Button(this.isEditing ? '完成' : '编辑')
.margin({ left: 15 })
.onClick(() => {
this.isEditing = !this.isEditing
})
// 合计区域
Column() {
Row() {
Text('合计:')
.fontSize(14)
Text(`¥${this.getSelectedAmount().toFixed(2)}`)
.fontSize(16)
.fontColor('#FF5000')
.fontWeight(FontWeight.Bold)
}
Text(`已选${this.selectedItems.length}件`)
.fontSize(12)
.fontColor('#999')
}
.margin({ left: 15 })
.layoutWeight(1)
// 结算按钮
Button('结算')
.width(100)
.backgroundColor('#FF5000')
.fontColor(Color.White)
.enabled(this.selectedItems.length > 0)
.onClick(() => {
this.checkout()
})
}
.padding(10)
.backgroundColor(Color.White)
.border({ width: 1, color: '#EEE' })
}
}
}
private async loadCartItems() {
this.isLoading = true
try {
this.cartItems = await http.get('/api/cart')
} finally {
this.isLoading = false
}
}
private toggleItemSelect(itemId: string, selected: boolean) {
if (selected) {
this.selectedItems = [...this.selectedItems, itemId]
} else {
this.selectedItems = this.selectedItems.filter(id => id !== itemId)
}
}
private toggleAllSelect(selectAll: boolean) {
if (selectAll) {
this.selectedItems = this.cartItems.map(item => item.id)
} else {
this.selectedItems = []
}
}
private isAllSelected(): boolean {
return this.selectedItems.length === this.cartItems.length && this.cartItems.length > 0
}
private getSelectedAmount(): number {
return this.cartItems
.filter(item => this.selectedItems.includes(item.id))
.reduce((sum, item) => sum + (item.price * item.quantity), 0)
}
private async updateItemQuantity(itemId: string, quantity: number) {
await http.put(`/api/cart/${itemId}`, { quantity })
this.loadCartItems()
}
private async removeItem(itemId: string) {
await http.delete(`/api/cart/${itemId}`)
this.loadCartItems()
this.selectedItems = this.selectedItems.filter(id => id !== itemId)
}
private checkout() {
router.push({
url: 'pages/order/confirm',
params: {
items: JSON.stringify(
this.cartItems
.filter(item => this.selectedItems.includes(item.id))
.map(item => ({
productId: item.productId,
skuId: item.skuId,
quantity: item.quantity
}))
)
}
})
}
}
// 购物车商品项组件
@Component
struct CartItemCard {
@Prop item: CartItem
@Prop isSelected: boolean
@Prop isEditing: boolean
private onSelect: (selected: boolean) => void
private onQuantityChange: (quantity: number) => void
private onRemove: () => void
build() {
Row() {
// 选择框
Checkbox(this.isSelected)
.onChange((checked: boolean) => {
this.onSelect(checked)
})
// 商品图片
Image(this.item.image)
.width(80)
.height(80)
.margin({ left: 10 })
// 商品信息
Column() {
Text(this.item.name)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ bottom: 5 })
Text(this.item.skuName)
.fontSize(12)
.fontColor('#999')
.margin({ bottom: 5 })
Row() {
Text(`¥${this.item.price.toFixed(2)}`)
.fontSize(16)
.fontColor('#FF5000')
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
if (this.isEditing) {
Button({ icon: 'trash' })
.onClick(() => {
this.onRemove()
})
} else {
Stepper({
value: this.item.quantity,
min: 1,
max: 99,
step: 1
})
.onChange((value: number) => {
this.onQuantityChange(value)
})
}
}
}
.margin({ left: 10 })
.layoutWeight(1)
}
.padding(10)
.backgroundColor(Color.White)
.margin({ bottom: 5 })
}
}
购物类应用优化建议
-
性能优化:
- 实现商品列表的虚拟滚动和图片懒加载
- 使用缓存策略减少重复网络请求
- 优化SKU选择器的渲染性能
-
用户体验:
- 添加商品加入购物车的动画效果
- 实现搜索历史记录和智能提示
- 优化商品详情页的加载过渡效果
-
电商功能增强:
- 集成多种支付方式(微信、支付宝等)
- 实现优惠券和促销活动系统
- 添加商品评价和晒单功能
-
数据安全:
- 敏感信息加密传输
- 实现安全的支付流程
- 用户隐私数据保护
结语
UNIapp结合ArkTS为购物类应用开发提供了强大的技术支撑,通过上述代码示例,我们展示了电商应用的核心功能实现。开发者可以在这些基础功能上进一步扩展和优化,打造出体验优秀、功能丰富的购物应用。
随着移动电商的持续发展和用户需求的不断变化,购物类应用将面临更多创新机会。UNIapp的跨平台特性和ArkTS的高效开发模式,将帮助开发者快速响应市场变化,为用户提供更优质的购物体验。