在电商应用中,售后申请功能是提升用户满意度、解决交易纠纷的重要环节。本文将深入分析一个基于 React Native 实现的售后申请系统,探讨其架构设计、技术实现以及鸿蒙跨端适配策略。

核心数据

该系统构建了清晰的订单和商品数据模型,为售后申请提供了完整的数据支持:

// 订单项目类型
type OrderItem = {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
};

// 订单类型
type Order = {
  id: string;
  orderNumber: string;
  date: string;
  status: 'delivered' | 'cancelled' | 'refunded';
  totalAmount: number;
  items: OrderItem[];
  shippingAddress: {
    name: string;
    phone: string;
    address: string;
  };
  paymentMethod: string;
};

这种数据模型设计的优势:

  • 类型安全:使用 TypeScript 类型确保数据结构一致性
  • 完整性:涵盖了订单和商品的核心属性,支持售后申请所需的所有信息
  • 层级清晰:订单包含商品列表和地址对象,结构层次分明
  • 扩展性:支持添加更多属性,如商品规格、订单备注等

状态管理

系统采用了 React Hooks 中的 useState 进行轻量级状态管理:

const [order] = useState<Order>({
  // 订单数据...
});

const [selectedItem, setSelectedItem] = useState<string>('i1');
const [serviceType, setServiceType] = useState<'refund' | 'exchange' | 'return'>('refund');
const [reason, setReason] = useState<string>('');
const [photos, setPhotos] = useState<string[]>([]);
const [contact, setContact] = useState<string>(order.shippingAddress.phone);

这种状态管理方式具有以下优势:

  • 模块化:将订单数据、选择状态和表单数据分离管理,提高代码可读性
  • 响应式:状态变更自动触发组件重渲染,确保 UI 与数据同步
  • 跨端兼容:React Hooks 在鸿蒙系统的 React Native 实现中通常都有良好支持
  • 灵活性:支持实时更新选择状态和表单数据,提高用户体验

系统实现了完整的售后申请功能,包括:

商品选择

系统支持从订单中选择需要申请售后的商品:

  • 显示订单中的所有商品,包括图片、名称和价格
  • 支持点击选择商品,提供清晰的选中状态反馈
  • 默认选中第一个商品,提高用户操作效率
服务类型

系统支持三种售后类型:

  • 仅退款:只退款不退货
  • 退货退款:既退货又退款
  • 换货:更换商品

用户可以根据实际情况选择合适的售后类型,系统会根据选择提供相应的操作流程。

退款原因

系统提供了多种常见的退款原因:

  • 质量问题
  • 与描述不符
  • 不喜欢/效果不好
  • 发错货/漏发
  • 包装破损
  • 其他原因

用户可以选择最符合实际情况的原因,系统会将此信息作为售后处理的参考。


该实现采用了 React Native 核心组件库,确保了在鸿蒙系统上的基本兼容性:

  • SafeAreaView:适配刘海屏等异形屏
  • ScrollView:处理内容滚动,确保长页面可浏览
  • TouchableOpacity:提供触摸反馈,增强用户体验
  • TextInput:提供文本输入功能,支持联系方式填写
  • Image:显示商品图片和上传的凭证图片
  • Alert:系统级弹窗提示,提供操作反馈

Base64 图标

系统使用 Base64 编码的图标库,这种处理方式在跨端开发中尤为重要:

  • 避免了不同平台对资源文件格式的兼容性问题
  • 减少了网络请求,提高了加载速度
  • 简化了构建流程,无需处理多平台资源文件
  • 确保图标在不同设备上的显示一致性

屏幕尺寸

系统通过 Dimensions API 获取屏幕尺寸,确保了在不同屏幕尺寸的设备上都能获得一致的布局体验,无论是 React Native 环境还是鸿蒙系统:

const { width, height } = Dimensions.get('window');

系统采用了模块化的样式定义,确保了样式的一致性和可维护性:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  // 其他样式...
});

这种方式为后续的主题定制和深色模式适配预留了扩展空间。


在鸿蒙系统上使用 React Native 时,应注意以下 API 兼容性问题:

  1. Image API:鸿蒙系统的 Image 实现可能与 React Native 有所差异,建议测试确认图片加载行为
  2. Alert API:鸿蒙系统的 Alert 实现可能与 React Native 有所差异,建议测试确认弹窗行为
  3. ScrollView API:鸿蒙系统的 ScrollView 实现可能与 React Native 有所差异,建议测试确认滚动行为

本售后申请系统实现了一个功能完整、用户友好的售后申请界面,通过合理的架构设计和代码组织,为用户提供了良好的售后申请体验。在跨端开发场景下,该实现充分考虑了 React Native 和鸿蒙系统的兼容性需求,为后续的功能扩展和性能优化预留了空间。

通过商品选择、服务类型选择、退款原因选择、图片上传等核心功能,结合 Base64 图标处理、卡片式布局等技术手段,该系统不仅功能完善,而且具有良好的可维护性和可扩展性。这些实践经验对于构建其他跨端应用组件也具有参考价值。


售后申请是电商应用的核心闭环环节,承载着退款、退货、换货三大核心诉求,其交互设计直接影响用户体验和售后处理效率。本文将从状态管理架构、交互逻辑设计、视觉规范体系三个维度深度拆解这份 React Native 售后申请页面代码,并提供完整的鸿蒙(HarmonyOS)ArkTS 跨端适配方案,为跨端电商售后模块开发提供可落地的技术参考。


1. 强类型系统

售后申请的核心是多维度选择与表单提交,代码通过 TypeScript 构建了贴合业务场景的类型体系,从底层保证数据的合法性:

// 订单商品类型:标准化商品信息结构
type OrderItem = {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
};

// 订单类型:聚焦售后场景的核心字段
type Order = {
  id: string;
  orderNumber: string;
  date: string;
  status: 'delivered' | 'cancelled' | 'refunded'; // 限定售后相关状态
  totalAmount: number;
  items: OrderItem[];
  shippingAddress: {
    name: string;
    phone: string;
    address: string;
  };
  paymentMethod: string;
};

类型设计亮点

  • 状态范围精准status 字段仅包含与售后相关的状态值(已签收/已取消/已退款),避免无效状态干扰;
  • 商品信息完整OrderItem 包含图片、价格、数量等售后申请必需的商品信息,满足展示和选择需求;
  • 地址信息结构化:收货地址封装为独立对象,便于联系方式的提取和复用;
  • 类型约束下沉:所有核心字段均有明确类型,杜绝运行时类型错误。

2. 状态管理架构

售后申请涉及商品选择、服务类型、退款原因、凭证上传、联系方式五大核心状态,代码通过 React 的 useState 实现了状态的精细化管理,保证各维度选择的独立性和关联性:

// 核心状态定义
const [selectedItem, setSelectedItem] = useState<string>('i1'); // 选中商品ID
const [serviceType, setServiceType] = useState<'refund' | 'exchange' | 'return'>('refund'); // 服务类型
const [reason, setReason] = useState<string>(''); // 退款原因
const [photos, setPhotos] = useState<string[]>([]); // 上传凭证
const [contact, setContact] = useState<string>(order.shippingAddress.phone); // 联系方式

状态设计亮点

  • 状态粒度合理:每个选择维度对应独立状态,便于单独控制和联动;
  • 默认值合理
    • 默认选中第一个商品,减少用户操作;
    • 默认服务类型为"仅退款"(高频场景);
    • 联系方式默认填充订单收货电话,提升填写效率;
  • 状态类型精准serviceType 使用联合类型限定仅能选择三种服务类型,避免非法值;
  • 数组状态管理:凭证照片使用数组管理,支持多图上传和删除。

3. 核心交互逻辑

售后申请的核心是表单完整性校验 + 操作确认 + 结果反馈的完整流程,代码实现了兼顾用户体验和数据安全性的交互逻辑:

const handleApplyService = () => {
  // 1. 表单校验:确保必填字段完整
  if (!reason.trim()) {
    Alert.alert('提示', '请选择退款原因');
    return;
  }

  // 2. 操作确认:展示完整的申请信息,避免误操作
  Alert.alert(
    '确认申请',
    `您确定要为"${order.items.find(i => i.id === selectedItem)?.name}"申请${
      serviceType === 'refund' ? '仅退款' : 
      serviceType === 'exchange' ? '换货' : '退货退款'
    }吗?`,
    [
      { text: '取消', style: 'cancel' },
      {
        text: '确定',
        onPress: () => {
          // 3. 结果反馈:明确告知申请提交结果
          Alert.alert('成功', '售后申请已提交,我们将尽快处理');
        }
      }
    ]
  );
};

流程设计亮点

  • 前置校验:提交前检查核心必填字段(退款原因),避免无效提交;
  • 动态确认文案:根据用户选择的商品和服务类型生成个性化确认文案,提升操作透明度;
  • 二次确认:对售后申请这类重要操作添加确认弹窗,降低误操作概率;
  • 结果反馈明确:操作完成后给出清晰的成功提示,增强用户操作确定性。

代码在 UI 层面针对售后申请场景做了大量体验优化,核心体现在以下维度:

(1)商品选择:

商品选择模块采用卡片式布局 + 选中态高亮的设计,让用户清晰感知当前选择:

<TouchableOpacity
  key={item.id}
  style={[
    styles.itemSelection,
    selectedItem === item.id && styles.selectedItem // 选中态样式
  ]}
  onPress={() => setSelectedItem(item.id)}
>
  <Image source={{ uri: item.image }} style={styles.itemImage} />
  <View style={styles.itemInfo}>
    <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
    <Text style={styles.itemPrice}>¥{item.price} x {item.quantity}</Text>
  </View>
  {selectedItem === item.id && <Text style={styles.checkIcon}></Text>} {/* 选中标识 */}
</TouchableOpacity>

体验优化点

  • 视觉分层:选中商品使用蓝色边框 + 浅蓝色背景,与未选中状态形成明显区分;
  • 选中标识:右侧添加对勾图标,强化选中状态感知;
  • 图片展示:商品图片缩小展示(40x40),兼顾视觉识别和空间利用;
  • 文字截断:商品名称添加 numberOfLines={1},避免长名称导致布局错乱。
(2)服务类型选择:

服务类型采用横向三等分按钮组设计,符合用户对"仅退款/退货退款/换货"的认知习惯:

<View style={styles.serviceTypeContainer}>
  <TouchableOpacity
    style={[styles.serviceTypeButton, serviceType === 'refund' && styles.selectedService]}
    onPress={() => setServiceType('refund')}
  >
    <Text style={[styles.serviceTypeText, serviceType === 'refund' && styles.selectedServiceText]}>仅退款</Text>
  </TouchableOpacity>
  {/* 退货退款/换货按钮 */}
</View>

布局亮点

  • 等分布局:每个按钮占比 0.3,保证视觉均衡;
  • 选中态对比强烈:选中按钮使用蓝色背景 + 白色文字,未选中使用浅灰背景 + 灰色文字;
  • 操作便捷:横向布局符合移动端拇指操作习惯,无需滚动即可完成选择。
(3)退款原因:

退款原因采用流式标签布局,支持多行展示,符合移动端表单选择习惯:

<View style={styles.reasonsContainer}>
  {reasons.map((reasonItem, index) => (
    <TouchableOpacity
      key={index}
      style={[styles.reasonButton, reason === reasonItem && styles.selectedReason]}
      onPress={() => setReason(reasonItem)}
    >
      <Text style={[styles.reasonText, reason === reasonItem && styles.selectedReasonText]}>{reasonItem}</Text>
    </TouchableOpacity>
  ))}
</View>

交互亮点

  • 圆角标签:使用 20px 圆角,符合移动端标签设计趋势;
  • 流式布局flexWrap: 'wrap' 自动换行,适配不同数量的原因选项;
  • 选中态明显:选中标签使用蓝色背景,与未选中的浅灰背景形成对比;
  • 点击反馈:整个标签区域可点击,提升操作便捷性。
(4)凭证上传:

凭证上传模块实现了图片预览 + 删除 + 新增的完整交互,符合用户对图片上传的操作认知:

<View style={styles.photoUploadContainer}>
  {/* 已上传图片展示 */}
  {photos.map((photo, index) => (
    <View key={index} style={styles.photoContainer}>
      <Image source={{ uri: photo }} style={styles.photo} />
      <TouchableOpacity
        style={styles.removePhotoButton}
        onPress={() => setPhotos(photos.filter((_, i) => i !== index))}
      >
        <Text style={styles.removePhotoText}>×</Text>
      </TouchableOpacity>
    </View>
  ))}
  
  {/* 新增图片按钮(最多3张) */}
  {photos.length < 3 && (
    <TouchableOpacity
      style={styles.addPhotoButton}
      onPress={() => {
        setPhotos([...photos, 'https://via.placeholder.com/80x80']);
      }}
    >
      <Text style={styles.addPhotoText}>+</Text>
    </TouchableOpacity>
  )}
</View>

体验优化点

  • 数量限制:最多上传 3 张图片,避免过度占用资源;
  • 删除按钮:每张图片右上角添加红色圆形删除按钮,操作直观;
  • 新增按钮:虚线边框 + 加号图标,符合用户对"添加"操作的视觉认知;
  • 预览尺寸:80x80 尺寸兼顾预览效果和空间利用。
(5)底部固定:

提交按钮采用底部悬浮固定设计,始终保持在视野内,提升操作便捷性:

<View style={styles.bottomButton}>
  <TouchableOpacity 
    style={styles.applyButton}
    onPress={handleApplyService}
  >
    <Text style={styles.applyButtonText}>提交申请</Text>
  </TouchableOpacity>
</View>

布局亮点

  • 位置合理:位于底部导航栏上方,既不遮挡内容又便于操作;
  • 样式突出:蓝色背景 + 白色文字,视觉优先级高;
  • 尺寸充足:垂直内边距 14px,保证点击区域足够大。

代码通过 StyleSheet.create 构建了一套完整的视觉规范,核心设计原则包括:

(1)卡片式布局

所有功能模块使用相同的卡片样式:

  • 白色背景(#ffffff
  • 12px 圆角(borderRadius: 12
  • 轻微阴影(elevation: 1
  • 16px 内边距
  • 12px 上下间距

元素类型 颜色值 语义
主色调 #3b82f6 选中状态、提交按钮、强调色
警示色 #ef4444 删除按钮、错误提示
背景色 #f5f7fa 页面背景
表单背景 #f8fafc 输入框、选择框背景
文本主色 #1e293b 标题、重要文本
文本次要色 #64748b 说明文本、未选中状态
边框色 #e2e8f0 输入框、卡片边框

文字类型 字号 字重 用途
页面标题 20px bold 头部标题
模块标题 16px 500 各卡片标题
正文 14px normal 商品名称、说明文本
辅助文本 12px normal 价格、备注文本

将 React Native 售后申请页面迁移至鸿蒙平台,核心是基于 ArkTS + ArkUI 实现状态管理、交互逻辑、视觉规范的对等还原,同时适配鸿蒙的组件特性和布局范式。

1. 核心适配

鸿蒙端适配遵循逻辑复用、语法适配、体验统一的原则,业务逻辑和视觉规范 100% 复用,仅需适配平台特有 API 和组件语法:

@Entry
@Component
struct AfterSalesApp {
  // 数据模型:完全复用 RN 端字段定义
  @State order: Order = {/* 订单数据 */};
  
  // 状态管理:对等实现 useState → @State
  @State selectedItem: string = 'i1';
  @State serviceType: 'refund' | 'exchange' | 'return' = 'refund';
  @State reason: string = '';
  @State photos: string[] = [];
  @State contact: string = '';

  // 业务逻辑:完全复用 RN 端实现
  handleApplyService() {/* 提交逻辑 */}

  // 页面构建:镜像 RN 端布局结构
  build() {
    Column() {
      // 头部区域
      // 滚动内容区
      // 底部提交按钮
      // 底部导航
    }
  }
}

React Native 特性 鸿蒙 ArkUI 对应实现 适配关键说明
useState @State 装饰器 状态初始化与更新逻辑完全复用
TouchableOpacity Button + onClick 可点击组件的交互逻辑复用
Alert.alert AlertDialog.show 弹窗 API 语法差异,交互逻辑对等
StyleSheet 链式样式 样式属性 100% 复用
Image Image 组件 图片加载语法差异,属性一致
TextInput TextInput 组件 输入框属性(value、onChange 等)复用
条件渲染 if/else 渲染 动态渲染逻辑完全复用
ScrollView Scroll 组件 滚动容器语法差异,功能一致
绝对定位 position: Position.Fixed 定位语法差异,效果一致
map 数组方法 ForEach 组件 列表渲染语法差异,逻辑一致

3. 鸿蒙代码

// 鸿蒙 ArkTS 完整实现
type OrderItem = {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
};

type Order = {
  id: string;
  orderNumber: string;
  date: string;
  status: 'delivered' | 'cancelled' | 'refunded';
  totalAmount: number;
  items: OrderItem[];
  shippingAddress: {
    name: string;
    phone: string;
    address: string;
  };
  paymentMethod: string;
};

@Entry
@Component
struct AfterSalesApp {
  @State order: Order = {
    id: 'o1',
    orderNumber: 'ORD202310150001',
    date: '2023-10-15',
    status: 'delivered',
    totalAmount: 10997,
    items: [
      { id: 'i1', name: 'iPhone 15 Pro Max', price: 9999, quantity: 1, image: 'https://via.placeholder.com/60x60' },
      { id: 'i2', name: '手机壳', price: 299, quantity: 1, image: 'https://via.placeholder.com/60x60' },
      { id: 'i3', name: '钢化膜', price: 699, quantity: 1, image: 'https://via.placeholder.com/60x60' }
    ],
    shippingAddress: {
      name: '张三',
      phone: '138****8888',
      address: '北京市朝阳区某某街道123号'
    },
    paymentMethod: '支付宝'
  };

  @State selectedItem: string = 'i1';
  @State serviceType: 'refund' | 'exchange' | 'return' = 'refund';
  @State reason: string = '';
  @State photos: string[] = [];
  @State contact: string = '138****8888';

  handleApplyService() {
    if (!this.reason.trim()) {
      AlertDialog.show({
        title: '提示',
        message: '请选择退款原因',
        confirm: { value: '确定' }
      });
      return;
    }

    const selectedProduct = this.order.items.find(i => i.id === this.selectedItem)?.name || '';
    const serviceTypeName = this.serviceType === 'refund' 
      ? '仅退款' 
      : this.serviceType === 'exchange' 
        ? '换货' 
        : '退货退款';

    AlertDialog.show({
      title: '确认申请',
      message: `您确定要为"${selectedProduct}"申请${serviceTypeName}吗?`,
      confirm: {
        value: '确定',
        action: () => {
          AlertDialog.show({
            title: '成功',
            message: '售后申请已提交,我们将尽快处理',
            confirm: { value: '确定' }
          });
        }
      },
      cancel: { value: '取消' }
    });
  }

  reasons: string[] = [
    '质量问题',
    '与描述不符',
    '不喜欢/效果不好',
    '发错货/漏发',
    '包装破损',
    '其他原因'
  ];

  build() {
    Column()
      .flex(1)
      .backgroundColor('#f5f7fa')
      .safeArea(true) {
      
      // 头部
      Column()
        .padding(16)
        .backgroundColor('#ffffff')
        .borderBottom({ width: 1, color: '#e2e8f0' }) {
        Text('申请售后')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1e293b')
          .marginBottom(4);
        
        Text(`订单号: ${this.order.orderNumber}`)
          .fontSize(14)
          .fontColor('#64748b');
      }

      // 滚动内容区
      Scroll()
        .flex(1)
        .marginTop(12) {
        Column() {
          // 选择商品
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('选择商品')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(12);
            
            ForEach(this.order.items, (item: OrderItem) => {
              Button()
                .width('100%')
                .flexDirection(FlexDirection.Row)
                .alignItems(ItemAlign.Center)
                .padding(12)
                .borderRadius(8)
                .border({ 
                  width: 1, 
                  color: this.selectedItem === item.id ? '#3b82f6' : '#e2e8f0' 
                })
                .backgroundColor(this.selectedItem === item.id ? '#eff6ff' : '#ffffff')
                .marginBottom(8)
                .onClick(() => {
                  this.selectedItem = item.id;
                }) {
                Image(item.image)
                  .width(40)
                  .height(40)
                  .borderRadius(4)
                  .marginRight(12);
                
                Column()
                  .flexGrow(1) {
                  Text(item.name)
                    .fontSize(14)
                    .fontColor('#1e293b')
                    .marginBottom(4)
                    .maxLines(1);
                  
                  Text(`¥${item.price} x ${item.quantity}`)
                    .fontSize(12)
                    .fontColor('#64748b');
                }
                
                if (this.selectedItem === item.id) {
                  Text('✓')
                    .fontSize(18)
                    .fontColor('#3b82f6')
                    .fontWeight(FontWeight.Bold);
                }
              }
            }, (item: OrderItem) => item.id)
          }

          // 服务类型
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('选择服务类型')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(12);
            
            Row()
              .justifyContent(FlexAlign.SpaceBetween) {
              Button()
                .flexGrow(0.3)
                .padding(12)
                .borderRadius(6)
                .backgroundColor(this.serviceType === 'refund' ? '#3b82f6' : '#f1f5f9')
                .onClick(() => {
                  this.serviceType = 'refund';
                }) {
                Text('仅退款')
                  .fontSize(14)
                  .fontColor(this.serviceType === 'refund' ? '#ffffff' : '#64748b');
              }
              
              Button()
                .flexGrow(0.3)
                .padding(12)
                .borderRadius(6)
                .backgroundColor(this.serviceType === 'return' ? '#3b82f6' : '#f1f5f9')
                .onClick(() => {
                  this.serviceType = 'return';
                }) {
                Text('退货退款')
                  .fontSize(14)
                  .fontColor(this.serviceType === 'return' ? '#ffffff' : '#64748b');
              }
              
              Button()
                .flexGrow(0.3)
                .padding(12)
                .borderRadius(6)
                .backgroundColor(this.serviceType === 'exchange' ? '#3b82f6' : '#f1f5f9')
                .onClick(() => {
                  this.serviceType = 'exchange';
                }) {
                Text('换货')
                  .fontSize(14)
                  .fontColor(this.serviceType === 'exchange' ? '#ffffff' : '#64748b');
              }
            }
          }

          // 退款原因
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('退款原因')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(12);
            
            Row()
              .flexWrap(FlexWrap.Wrap) {
              ForEach(this.reasons, (reasonItem: string, index: number) => {
                Button()
                  .paddingHorizontal(12)
                  .paddingVertical(8)
                  .borderRadius(20)
                  .backgroundColor(this.reason === reasonItem ? '#3b82f6' : '#f1f5f9')
                  .marginRight(8)
                  .marginBottom(8)
                  .onClick(() => {
                    this.reason = reasonItem;
                  }) {
                  Text(reasonItem)
                    .fontSize(14)
                    .fontColor(this.reason === reasonItem ? '#ffffff' : '#64748b');
                }
              }, (reasonItem: string, index: number) => index.toString())
            }
          }

          // 问题描述
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('问题描述')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(12);
            
            TextInput({
              value: this.reason,
              placeholder: '请详细描述您遇到的问题'
            })
              .border({ width: 1, color: '#e2e8f0' })
              .borderRadius(6)
              .padding(12)
              .fontSize(14)
              .backgroundColor('#f8fafc')
              .minHeight(80)
              .multiline(true)
              .onChange((value) => {
                this.reason = value;
              });
          }

          // 上传凭证
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('上传凭证(可选)')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(12);
            
            Row()
              .flexWrap(FlexWrap.Wrap) {
              ForEach(this.photos, (photo: string, index: number) => {
                Stack()
                  .marginRight(8)
                  .marginBottom(8)
                  .width(80)
                  .height(80)
                  .borderRadius(6) {
                  Image(photo)
                    .width('100%')
                    .height('100%')
                    .borderRadius(6);
                  
                  Button()
                    .width(20)
                    .height(20)
                    .borderRadius(10)
                    .backgroundColor('#ef4444')
                    .position(Position.Absolute)
                    .top(-5)
                    .right(-5)
                    .onClick(() => {
                      this.photos = this.photos.filter((_, i) => i !== index);
                    }) {
                    Text('×')
                      .fontSize(12)
                      .fontColor('#ffffff')
                      .fontWeight(FontWeight.Bold);
                  }
                }
              }, (photo: string, index: number) => index.toString())
              
              if (this.photos.length < 3) {
                Button()
                  .width(80)
                  .height(80)
                  .borderRadius(6)
                  .border({ width: 1, color: '#cbd5e1', style: BorderStyle.Dashed })
                  .backgroundColor('#ffffff')
                  .onClick(() => {
                    this.photos = [...this.photos, 'https://via.placeholder.com/80x80'];
                  }) {
                  Text('+')
                    .fontSize(24)
                    .fontColor('#94a3b8');
                }
              }
            }
          }

          // 联系方式
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('联系方式')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(12);
            
            TextInput({
              value: this.contact,
              placeholder: '请输入您的联系电话'
            })
              .border({ width: 1, color: '#e2e8f0' })
              .borderRadius(6)
              .padding(12)
              .fontSize(14)
              .backgroundColor('#f8fafc')
              .type(InputType.PhoneNumber)
              .onChange((value) => {
                this.contact = value;
              });
          }

          // 服务说明
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('服务说明')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(8);
            
            Text('• 仅退款:无需退货,直接退款')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 退货退款:需要退回商品后退款')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 换货:退回商品并发送新商品')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 请如实填写问题描述,以便我们更快处理')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20);
          }

          // 注意事项
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(80)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 1, opacity: 0.1, radius: 2 }) {
            
            Text('注意事项')
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#1e293b')
              .marginBottom(8);
            
            Text('• 申请提交后无法撤销,请谨慎操作')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 仅退款适用于未收到货或与描述严重不符')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 退货退款需保持商品完好无损')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 换货需在7天内申请')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20);
          }
        }
      }

      // 底部申请按钮
      Column()
        .position(Position.Fixed)
        .bottom(60)
        .left(16)
        .right(16)
        .padding(8)
        .backgroundColor('#ffffff') {
      
      Button()
        .width('100%')
        .backgroundColor('#3b82f6')
        .paddingVertical(14)
        .borderRadius(8)
        .onClick(() => {
          this.handleApplyService();
        }) {
        Text('提交申请')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#ffffff');
      }
    }

      // 底部导航
      Row()
        .justifyContent(FlexAlign.SpaceAround)
        .backgroundColor('#ffffff')
        .borderTop({ width: 1, color: '#e2e8f0' })
        .paddingVertical(12)
        .position(Position.Fixed)
        .bottom(0)
        .width('100%') {
      
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('🏠')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        Text('首页')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('🔍')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        Text('分类')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('🛒')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        Text('购物车')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1)
        .paddingTop(4)
        .borderTop({ width: 2, color: '#3b82f6' }) {
        Text('📦')
          .fontSize(20)
          .fontColor('#3b82f6')
          .marginBottom(4);
        Text('订单')
          .fontSize(12)
          .fontColor('#3b82f6')
          .fontWeight(FontWeight.Medium);
      }
    }
  }
}

心是多维度选择与表单完整性校验**:通过精细化状态管理实现商品、服务类型、退款原因等维度的独立选择,提交前进行必填字段校验,保证申请信息完整;
2. 视觉分层提升操作感知:通过色彩对比、边框样式、选中标识等视觉手段,让用户清晰感知当前选择状态,降低操作认知成本;
3. 交互细节决定用户体验:图片上传/删除、按钮选中态、底部固定提交按钮等细节设计,直接影响售后申请的操作效率和用户体验;
4. 跨端适配的关键是逻辑复用+体验统一:业务逻辑和视觉规范可跨端复用,仅需适配平台特有 API 和组件语法,保证跨端体验一致性;
5. 风险操作需强化确认机制:售后申请涉及用户和商家的核心利益,必须添加二次确认和明确的操作提示,降低误操作概率;
6. 表单体验优化提升转化率:默认值填充、自动换行、流式布局等优化手段,能显著提升表单填写效率和完成率。

这份售后申请页面的跨端适配实践,验证了 React Native 与鸿蒙 ArkTS 在电商核心售后场景下的高度兼容性,为跨端电商应用的售后模块开发提供了可落地的技术参考,同时也为其他表单类跨端场景提供了设计思路和实现范式。


真实演示案例代码:





// App.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, TextInput, Image } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  refund: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  exchange: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  return: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  photo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  clock: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  user: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

const { width, height } = Dimensions.get('window');

// 订单项目类型
type OrderItem = {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
};

// 订单类型
type Order = {
  id: string;
  orderNumber: string;
  date: string;
  status: 'delivered' | 'cancelled' | 'refunded';
  totalAmount: number;
  items: OrderItem[];
  shippingAddress: {
    name: string;
    phone: string;
    address: string;
  };
  paymentMethod: string;
};

// 售后申请应用组件
const AfterSalesApp: React.FC = () => {
  const [order] = useState<Order>({
    id: 'o1',
    orderNumber: 'ORD202310150001',
    date: '2023-10-15',
    status: 'delivered',
    totalAmount: 10997,
    items: [
      { id: 'i1', name: 'iPhone 15 Pro Max', price: 9999, quantity: 1, image: 'https://via.placeholder.com/60x60' },
      { id: 'i2', name: '手机壳', price: 299, quantity: 1, image: 'https://via.placeholder.com/60x60' },
      { id: 'i3', name: '钢化膜', price: 699, quantity: 1, image: 'https://via.placeholder.com/60x60' }
    ],
    shippingAddress: {
      name: '张三',
      phone: '138****8888',
      address: '北京市朝阳区某某街道123号'
    },
    paymentMethod: '支付宝'
  });

  const [selectedItem, setSelectedItem] = useState<string>('i1');
  const [serviceType, setServiceType] = useState<'refund' | 'exchange' | 'return'>('refund');
  const [reason, setReason] = useState<string>('');
  const [photos, setPhotos] = useState<string[]>([]);
  const [contact, setContact] = useState<string>(order.shippingAddress.phone);

  const handleApplyService = () => {
    if (!reason.trim()) {
      Alert.alert('提示', '请选择退款原因');
      return;
    }

    Alert.alert(
      '确认申请',
      `您确定要为"${order.items.find(i => i.id === selectedItem)?.name}"申请${serviceType === 'refund' ? '仅退款' : serviceType === 'exchange' ? '换货' : '退货退款'}吗?`,
      [
        {
          text: '取消',
          style: 'cancel'
        },
        {
          text: '确定',
          onPress: () => {
            Alert.alert('成功', '售后申请已提交,我们将尽快处理');
          }
        }
      ]
    );
  };

  const reasons = [
    '质量问题',
    '与描述不符',
    '不喜欢/效果不好',
    '发错货/漏发',
    '包装破损',
    '其他原因'
  ];

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>申请售后</Text>
        <Text style={styles.orderNumber}>订单号: {order.orderNumber}</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 选择商品 */}
        <View style={styles.sectionCard}>
          <Text style={styles.sectionTitle}>选择商品</Text>
          {order.items.map(item => (
            <TouchableOpacity
              key={item.id}
              style={[
                styles.itemSelection,
                selectedItem === item.id && styles.selectedItem
              ]}
              onPress={() => setSelectedItem(item.id)}
            >
              <Image source={{ uri: item.image }} style={styles.itemImage} />
              <View style={styles.itemInfo}>
                <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
                <Text style={styles.itemPrice}>¥{item.price} x {item.quantity}</Text>
              </View>
              {selectedItem === item.id && <Text style={styles.checkIcon}></Text>}
            </TouchableOpacity>
          ))}
        </View>

        {/* 服务类型 */}
        <View style={styles.sectionCard}>
          <Text style={styles.sectionTitle}>选择服务类型</Text>
          <View style={styles.serviceTypeContainer}>
            <TouchableOpacity
              style={[
                styles.serviceTypeButton,
                serviceType === 'refund' && styles.selectedService
              ]}
              onPress={() => setServiceType('refund')}
            >
              <Text style={[
                styles.serviceTypeText,
                serviceType === 'refund' && styles.selectedServiceText
              ]}>仅退款</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[
                styles.serviceTypeButton,
                serviceType === 'return' && styles.selectedService
              ]}
              onPress={() => setServiceType('return')}
            >
              <Text style={[
                styles.serviceTypeText,
                serviceType === 'return' && styles.selectedServiceText
              ]}>退货退款</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[
                styles.serviceTypeButton,
                serviceType === 'exchange' && styles.selectedService
              ]}
              onPress={() => setServiceType('exchange')}
            >
              <Text style={[
                styles.serviceTypeText,
                serviceType === 'exchange' && styles.selectedServiceText
              ]}>换货</Text>
            </TouchableOpacity>
          </View>
        </View>

        {/* 退款原因 */}
        <View style={styles.sectionCard}>
          <Text style={styles.sectionTitle}>退款原因</Text>
          <View style={styles.reasonsContainer}>
            {reasons.map((reasonItem, index) => (
              <TouchableOpacity
                key={index}
                style={[
                  styles.reasonButton,
                  reason === reasonItem && styles.selectedReason
                ]}
                onPress={() => setReason(reasonItem)}
              >
                <Text style={[
                  styles.reasonText,
                  reason === reasonItem && styles.selectedReasonText
                ]}>{reasonItem}</Text>
              </TouchableOpacity>
            ))}
          </View>
        </View>

        {/* 问题描述 */}
        <View style={styles.sectionCard}>
          <Text style={styles.sectionTitle}>问题描述</Text>
          <TextInput
            style={styles.descriptionInput}
            value={reason}
            onChangeText={setReason}
            placeholder="请详细描述您遇到的问题"
            multiline
            numberOfLines={4}
          />
        </View>

        {/* 上传凭证 */}
        <View style={styles.sectionCard}>
          <Text style={styles.sectionTitle}>上传凭证(可选)</Text>
          <View style={styles.photoUploadContainer}>
            {photos.map((photo, index) => (
              <View key={index} style={styles.photoContainer}>
                <Image source={{ uri: photo }} style={styles.photo} />
                <TouchableOpacity
                  style={styles.removePhotoButton}
                  onPress={() => setPhotos(photos.filter((_, i) => i !== index))}
                >
                  <Text style={styles.removePhotoText}>×</Text>
                </TouchableOpacity>
              </View>
            ))}
            
            {photos.length < 3 && (
              <TouchableOpacity
                style={styles.addPhotoButton}
                onPress={() => {
                  // 模拟添加照片
                  setPhotos([...photos, 'https://via.placeholder.com/80x80']);
                }}
              >
                <Text style={styles.addPhotoText}>+</Text>
              </TouchableOpacity>
            )}
          </View>
        </View>

        {/* 联系方式 */}
        <View style={styles.sectionCard}>
          <Text style={styles.sectionTitle}>联系方式</Text>
          <TextInput
            style={styles.contactInput}
            value={contact}
            onChangeText={setContact}
            placeholder="请输入您的联系电话"
            keyboardType="phone-pad"
          />
        </View>

        {/* 服务说明 */}
        <View style={styles.serviceInfoCard}>
          <Text style={styles.serviceInfoTitle}>服务说明</Text>
          <Text style={styles.serviceInfoText}>• 仅退款:无需退货,直接退款</Text>
          <Text style={styles.serviceInfoText}>• 退货退款:需要退回商品后退款</Text>
          <Text style={styles.serviceInfoText}>• 换货:退回商品并发送新商品</Text>
          <Text style={styles.serviceInfoText}>• 请如实填写问题描述,以便我们更快处理</Text>
        </View>

        {/* 注意事项 */}
        <View style={styles.notesCard}>
          <Text style={styles.notesTitle}>注意事项</Text>
          <Text style={styles.noteItem}>• 申请提交后无法撤销,请谨慎操作</Text>
          <Text style={styles.noteItem}>• 仅退款适用于未收到货或与描述严重不符</Text>
          <Text style={styles.noteItem}>• 退货退款需保持商品完好无损</Text>
          <Text style={styles.noteItem}>• 换货需在7天内申请</Text>
        </View>
      </ScrollView>

      {/* 底部申请按钮 */}
      <View style={styles.bottomButton}>
        <TouchableOpacity 
          style={styles.applyButton}
          onPress={handleApplyService}
        >
          <Text style={styles.applyButtonText}>提交申请</Text>
        </TouchableOpacity>
      </View>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🏠</Text>
          <Text style={styles.navText}>首页</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🔍</Text>
          <Text style={styles.navText}>分类</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🛒</Text>
          <Text style={styles.navText}>购物车</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.navItem, styles.activeNavItem]}>
          <Text style={styles.navIcon}>📦</Text>
          <Text style={styles.navText}>订单</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f7fa',
  },
  header: {
    flexDirection: 'column',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
  },
  orderNumber: {
    fontSize: 14,
    color: '#64748b',
  },
  content: {
    flex: 1,
    marginTop: 12,
  },
  sectionCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#1e293b',
    marginBottom: 12,
  },
  itemSelection: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#e2e8f0',
    marginBottom: 8,
  },
  selectedItem: {
    borderColor: '#3b82f6',
    backgroundColor: '#eff6ff',
  },
  itemImage: {
    width: 40,
    height: 40,
    borderRadius: 4,
    marginRight: 12,
  },
  itemInfo: {
    flex: 1,
  },
  itemName: {
    fontSize: 14,
    color: '#1e293b',
    marginBottom: 4,
  },
  itemPrice: {
    fontSize: 12,
    color: '#64748b',
  },
  checkIcon: {
    fontSize: 18,
    color: '#3b82f6',
    fontWeight: 'bold',
  },
  serviceTypeContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  serviceTypeButton: {
    flex: 0.3,
    padding: 12,
    borderRadius: 6,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
  },
  selectedService: {
    backgroundColor: '#3b82f6',
  },
  serviceTypeText: {
    fontSize: 14,
    color: '#64748b',
  },
  selectedServiceText: {
    color: '#ffffff',
  },
  reasonsContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  reasonButton: {
    paddingHorizontal: 12,
    paddingVertical: 8,
    borderRadius: 20,
    backgroundColor: '#f1f5f9',
    marginRight: 8,
    marginBottom: 8,
  },
  selectedReason: {
    backgroundColor: '#3b82f6',
  },
  reasonText: {
    fontSize: 14,
    color: '#64748b',
  },
  selectedReasonText: {
    color: '#ffffff',
  },
  descriptionInput: {
    borderWidth: 1,
    borderColor: '#e2e8f0',
    borderRadius: 6,
    padding: 12,
    fontSize: 14,
    backgroundColor: '#f8fafc',
    minHeight: 80,
    textAlignVertical: 'top',
  },
  photoUploadContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  photoContainer: {
    position: 'relative',
    marginRight: 8,
    marginBottom: 8,
  },
  photo: {
    width: 80,
    height: 80,
    borderRadius: 6,
  },
  removePhotoButton: {
    position: 'absolute',
    top: -5,
    right: -5,
    width: 20,
    height: 20,
    borderRadius: 10,
    backgroundColor: '#ef4444',
    alignItems: 'center',
    justifyContent: 'center',
  },
  removePhotoText: {
    color: '#ffffff',
    fontSize: 12,
    fontWeight: 'bold',
  },
  addPhotoButton: {
    width: 80,
    height: 80,
    borderRadius: 6,
    borderWidth: 1,
    borderStyle: 'dashed',
    borderColor: '#cbd5e1',
    alignItems: 'center',
    justifyContent: 'center',
  },
  addPhotoText: {
    fontSize: 24,
    color: '#94a3b8',
  },
  contactInput: {
    borderWidth: 1,
    borderColor: '#e2e8f0',
    borderRadius: 6,
    padding: 12,
    fontSize: 14,
    backgroundColor: '#f8fafc',
  },
  serviceInfoCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  serviceInfoTitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#1e293b',
    marginBottom: 8,
  },
  serviceInfoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 20,
    marginBottom: 4,
  },
  notesCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 80,
    borderRadius: 12,
    padding: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  notesTitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#1e293b',
    marginBottom: 8,
  },
  noteItem: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 20,
    marginBottom: 4,
  },
  bottomButton: {
    position: 'absolute',
    bottom: 60,
    left: 16,
    right: 16,
    padding: 8,
    backgroundColor: '#ffffff',
  },
  applyButton: {
    backgroundColor: '#3b82f6',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  applyButtonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '500',
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  activeNavItem: {
    paddingTop: 4,
    borderTopWidth: 2,
    borderTopColor: '#3b82f6',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#3b82f6',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#3b82f6',
    fontWeight: '500',
  },
});

export default AfterSalesApp;



请添加图片描述


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述
本文探讨了基于React Native的电商售后申请系统设计与实现,重点分析了其架构特点、技术方案和跨端适配策略。系统采用TypeScript构建了完整的订单数据模型,通过React Hooks实现轻量级状态管理,支持商品选择、服务类型选择、退款原因填写等核心功能。文章详细介绍了系统在数据模型设计、状态管理、UI组件实现等方面的技术细节,并特别强调了在鸿蒙系统上的兼容性考虑。该实现具有模块化、响应式和跨平台兼容等特点,为电商应用提供了完整的售后解决方案,同时为跨端开发提供了可借鉴的技术实践。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐