React Native for OpenHarmony 实战:Steam 资讯 App 服务条款实现

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_steam

服务条款是 App 与用户之间的法律协议。它规定了用户使用 App 的权利和义务,以及 App 开发者的责任和免责条款。一个完整的服务条款能保护用户权益,也能保护 App 开发者的合法权益。这篇文章来实现一个完整的服务条款页面,包括条款内容展示、版本管理和用户同意。

服务条款与隐私政策的区别

很多开发者容易混淆服务条款和隐私政策,但这两者有本质的区别。理解这个区别对于编写正确的法律文档很重要。

隐私政策 主要关注数据保护:

  • 数据收集 - 说明 App 收集哪些数据
  • 数据使用 - 说明如何使用这些数据
  • 数据保护 - 说明如何保护用户数据
  • 用户权利 - 用户对自己数据的权利

服务条款 主要关注使用规则:

  • 使用权限 - 用户可以如何使用 App
  • 禁止行为 - 用户不能做什么
  • 知识产权 - App 内容的所有权
  • 免责条款 - App 开发者的责任限制
  • 终止条款 - 什么情况下会终止用户的使用权

法律保护 - 隐私政策保护用户的数据隐私,服务条款保护 App 开发者的权益。两者都是必需的,缺一不可。

服务条款内容的定义

首先定义服务条款的内容。服务条款应该清晰地说明用户的权利和义务。

先看服务条款的结构定义:

tsx 复制代码
interface TermsOfService {
  version: string;
  effectiveDate: string;
  lastUpdated: string;
  sections: TermsSection[];
}

interface TermsSection {
  title: string;
  content: string;
  subsections?: TermsSubsection[];
}

interface TermsSubsection {
  title: string;
  content: string;
}

这个接口定义了服务条款的结构,每个属性都有明确的用途:

  • version - 条款版本号,用于追踪条款的更新
  • effectiveDate - 条款生效日期,说明从什么时候开始适用这个版本的条款
  • lastUpdated - 最后更新时间,让用户知道条款是否最新
  • sections - 条款的主要章节,比如"使用权限"、"禁止行为"等
  • subsections - 章节下的子章节,用于更详细的说明

结构化设计 - 通过定义清晰的数据结构,可以方便地管理和更新服务条款。如果要添加新的章节或修改内容,只需要改数据就行。这种设计也便于后续从服务器动态加载条款内容。

然后定义具体的服务条款内容:

tsx 复制代码
const TERMS_OF_SERVICE: TermsOfService = {
  version: '1.0',
  effectiveDate: '2024-01-01',
  lastUpdated: '2024-01-01',
  sections: [
    {
      title: '1. 使用权限',
      content: '我们授予您一个有限的、非独占的、不可转让的许可证,以便您可以使用本应用。',
      subsections: [
        {
          title: '1.1 个人使用',
          content: '您只能为个人、非商业目的使用本应用。',
        },
        {
          title: '1.2 禁止转让',
          content: '您不能将本应用的使用权转让给他人。',
        },
      ],
    },
    {
      title: '2. 禁止行为',
      content: '您同意不会进行以下行为:',
      subsections: [
        {
          title: '2.1 非法使用',
          content: '不会使用本应用进行任何非法活动。',
        },
        {
          title: '2.2 骚扰和滥用',
          content: '不会骚扰、威胁或滥用其他用户。',
        },
        {
          title: '2.3 技术攻击',
          content: '不会尝试破坏、干扰或获得对本应用的未授权访问。',
        },
      ],
    },
    {
      title: '3. 知识产权',
      content: '本应用及其所有内容的知识产权属于我们或我们的许可方。',
    },
    {
      title: '4. 免责条款',
      content: '本应用按"现状"提供,不提供任何明示或暗示的保证。',
    },
    {
      title: '5. 责任限制',
      content: '在任何情况下,我们都不对任何间接、附带或后果性损害负责。',
    },
    {
      title: '6. 条款终止',
      content: '如果您违反本条款,我们可能会终止您的使用权。',
    },
  ],
};

这个对象定义了服务条款的完整内容。每个章节都有标题和内容,一些章节还有子章节。

内容管理 - 通过定义一个数据对象,可以集中管理服务条款的内容。如果要更新条款,只需要修改这个对象就行。

服务条款页面的实现

服务条款页面需要展示完整的条款内容,并提供清晰的导航。用户应该能够轻松阅读和理解条款的每一部分。

先看服务条款页面的基本结构:

tsx 复制代码
export const TermsOfServiceScreen = () => {
  const [agreedVersion, setAgreedVersion] = useState<string | null>(null);
  const [showAgreementDialog, setShowAgreementDialog] = useState(false);
  const colors = useTheme();
  
  useEffect(() => {
    // 检查用户是否已同意当前版本的条款
    loadAgreedVersion();
  }, []);
  
  const loadAgreedVersion = async () => {
    const saved = await AsyncStorage.getItem('terms_of_service_agreed');
    setAgreedVersion(saved);
  };

这个页面的初始化逻辑包括几个关键步骤:

  • 状态管理 - 使用 agreedVersion 追踪用户同意的条款版本,使用 showAgreementDialog 控制对话框的显示
  • 主题支持 - 通过 useTheme() 获取当前主题的颜色,确保页面遵循用户的主题选择
  • 加载历史记录 - 在组件挂载时,从本地存储加载用户之前同意的条款版本

用户体验 - 通过加载历史记录,我们可以知道用户是否已同意过条款。如果用户已同意最新版本,就不需要再次显示同意对话框。这样可以避免重复的提示,提高用户体验。

然后看页面的主要内容部分:

tsx 复制代码
  return (
    <View style={[styles.container, {backgroundColor: colors.background}]}>
      <Header title="服务条款" showBack />
      <ScrollView style={styles.content}>
        {/* 条款版本信息 */}
        <View style={[styles.versionInfo, {backgroundColor: colors.surface}]}>
          <Text style={[styles.versionLabel, {color: colors.textSecondary}]}>
            版本 {TERMS_OF_SERVICE.version}
          </Text>
          <Text style={[styles.effectiveDate, {color: colors.textSecondary}]}>
            生效日期:{TERMS_OF_SERVICE.effectiveDate}
          </Text>
          <Text style={[styles.updateTime, {color: colors.textSecondary}]}>
            最后更新:{TERMS_OF_SERVICE.lastUpdated}
          </Text>
        </View>
        
        {/* 条款内容 */}
        {TERMS_OF_SERVICE.sections.map((section, index) => (
          <View key={index} style={styles.section}>
            <Text style={[styles.sectionTitle, {color: colors.text}]}>
              {section.title}
            </Text>
            <Text style={[styles.sectionContent, {color: colors.textSecondary}]}>
              {section.content}
            </Text>
            {section.subsections?.map((sub, subIndex) => (
              <View key={subIndex} style={styles.subsection}>
                <Text style={[styles.subsectionTitle, {color: colors.text}]}>
                  {sub.title}
                </Text>
                <Text style={[styles.subsectionContent, {color: colors.textSecondary}]}>
                  {sub.content}
                </Text>
              </View>
            ))}
          </View>
        ))}
      </ScrollView>
      <TabBar />
    </View>
  );
};

这个页面的关键实现细节:

  • 版本信息展示 - 在页面顶部显示条款版本、生效日期和最后更新时间,让用户知道他们查看的是最新版本
  • 动态内容渲染 - 使用 map 遍历条款章节,动态生成 UI。这样即使条款内容很长,代码也保持简洁
  • 子章节支持 - 对于有子章节的章节,也进行遍历展示,形成清晰的层级结构
  • 动态颜色应用 - 所有颜色都从 useTheme() 获取,支持主题切换

用户体验 - 通过清晰的版本信息和更新时间,用户可以知道条款是否最新。通过分层展示内容,用户可以快速找到想要的信息。使用 ScrollView 让用户可以舒适地阅读长文本内容。

条款同意确认

用户在首次使用 App 时,应该被要求同意服务条款。这可以通过一个对话框来实现。这个对话框应该清晰地说明用户需要同意什么。

先看同意确认对话框的实现:

tsx 复制代码
const showAgreementDialog = () => {
  return (
    <Modal
      visible={showAgreementDialog}
      transparent
      animationType="fade"
    >
      <View style={styles.modalOverlay}>
        <View style={[styles.modalContent, {backgroundColor: colors.surface}]}>
          <Text style={[styles.modalTitle, {color: colors.text}]}>
            同意服务条款
          </Text>
          <Text style={[styles.modalMessage, {color: colors.textSecondary}]}>
            为了使用本应用,您需要同意我们的服务条款。
          </Text>
          <View style={styles.modalButtons}>
            <TouchableOpacity
              style={[styles.button, {backgroundColor: colors.surfaceLight}]}
              onPress={() => setShowAgreementDialog(false)}
            >
              <Text style={[styles.buttonText, {color: colors.text}]}>
                拒绝
              </Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.button, {backgroundColor: colors.primary}]}
              onPress={handleAgree}
            >
              <Text style={[styles.buttonText, {color: colors.background}]}>
                同意
              </Text>
            </TouchableOpacity>
          </View>
        </View>
      </View>
    </Modal>
  );
};

这个对话框的关键点:

  • 模态显示 - 使用 Modal 组件以模态方式显示对话框,这样用户必须做出选择才能继续
  • 清晰的选择 - 提供"拒绝"和"同意"两个按钮,让用户有明确的选择
  • 视觉区分 - 同意按钮使用主色调,拒绝按钮使用较浅的颜色,这样用户可以快速识别哪个是主要操作
  • 透明背景 - 使用半透明的背景让用户关注对话框内容,同时能看到后面的内容

用户交互 - 通过清晰的对话框,用户可以明确地选择是否同意服务条款。这样既保护了用户权利,也为 App 提供了法律保护。对话框应该在用户首次打开 App 时显示,或者在条款更新时显示。

条款版本管理

当服务条款更新时,需要追踪用户是否已同意新版本的条款。这是一个重要的法律要求,确保用户了解最新的条款变化。

先看版本管理的实现:

tsx 复制代码
const handleAgree = async () => {
  try {
    // 保存用户同意的条款版本
    await AsyncStorage.setItem(
      'terms_of_service_agreed',
      TERMS_OF_SERVICE.version
    );
    setAgreedVersion(TERMS_OF_SERVICE.version);
    setShowAgreementDialog(false);
  } catch (error) {
    console.error('Error saving terms agreement:', error);
  }
};

const checkTermsUpdate = () => {
  // 如果用户同意的版本与当前版本不同,需要重新同意
  if (agreedVersion !== TERMS_OF_SERVICE.version) {
    setShowAgreementDialog(true);
  }
};

这个实现的逻辑包括三个关键步骤:

  • 保存同意记录 - 当用户同意条款时,保存同意的版本号到本地存储。这样即使 App 关闭,下次打开时也能知道用户同意过哪个版本
  • 版本对比 - 每次打开 App 时,检查用户同意的版本是否与当前版本相同。如果不同,说明条款已更新
  • 强制更新 - 如果版本不同,强制用户重新同意新版本的条款。这确保用户了解最新的条款变化

版本管理 - 通过版本号管理,可以追踪用户是否已同意最新的服务条款。当条款更新时,用户会被要求重新同意。这是一个重要的法律要求,确保 App 符合法律规定。

条款内容的本地化

服务条款应该支持多种语言,让不同地区的用户都能理解。这不仅提高了用户体验,也是法律合规的要求。

先看本地化的实现:

tsx 复制代码
const TERMS_OF_SERVICES: Record<string, TermsOfService> = {
  'zh-CN': {
    version: '1.0',
    effectiveDate: '2024-01-01',
    lastUpdated: '2024-01-01',
    sections: [
      {
        title: '1. 使用权限',
        content: '我们授予您一个有限的、非独占的、不可转让的许可证,以便您可以使用本应用。',
        // ... 中文内容
      },
    ],
  },
  'en-US': {
    version: '1.0',
    effectiveDate: '2024-01-01',
    lastUpdated: '2024-01-01',
    sections: [
      {
        title: '1. License Grant',
        content: 'We grant you a limited, non-exclusive, non-transferable license to use this application.',
        // ... 英文内容
      },
    ],
  },
};

const getTermsOfService = (language: string) => {
  return TERMS_OF_SERVICES[language] || TERMS_OF_SERVICES['zh-CN'];
};

这个实现的关键点:

  • 多语言支持 - 为每种语言定义完整的服务条款。这样用户可以用自己熟悉的语言阅读条款
  • 动态选择 - 根据用户选择的语言,返回对应的条款内容。这可以通过 AppContext 中的 language 状态来实现
  • 兜底处理 - 如果不支持某种语言,默认返回中文版本。这确保即使用户选择了不支持的语言,也能看到条款内容

国际化 - 通过支持多种语言,App 可以服务全球用户。这也是法律合规的要求,不同国家对服务条款的语言有不同的要求。

条款样式的定义

服务条款页面的样式应该清晰、易读,让用户能够轻松理解条款内容。好的排版设计可以显著提高文字的可读性。

tsx 复制代码
const styles = StyleSheet.create({
  container: {flex: 1},
  content: {flex: 1, paddingHorizontal: 16},
  versionInfo: {
    paddingVertical: 12,
    paddingHorizontal: 16,
    borderRadius: 8,
    marginVertical: 16,
  },
  versionLabel: {fontSize: 12, marginBottom: 4},
  effectiveDate: {fontSize: 12, marginBottom: 4},
  updateTime: {fontSize: 12},
  section: {marginBottom: 24},
  sectionTitle: {fontSize: 16, fontWeight: 'bold', marginBottom: 8},
  sectionContent: {fontSize: 14, lineHeight: 22, marginBottom: 12},
  subsection: {marginLeft: 16, marginBottom: 12},
  subsectionTitle: {fontSize: 14, fontWeight: '600', marginBottom: 4},
  subsectionContent: {fontSize: 13, lineHeight: 20},
  modalOverlay: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalContent: {
    borderRadius: 12,
    padding: 20,
    width: '80%',
  },
  modalTitle: {fontSize: 18, fontWeight: 'bold', marginBottom: 12},
  modalMessage: {fontSize: 14, lineHeight: 20, marginBottom: 20},
  modalButtons: {flexDirection: 'row', gap: 12},
  button: {flex: 1, paddingVertical: 10, borderRadius: 6, alignItems: 'center'},
  buttonText: {fontSize: 14, fontWeight: '600'},
});

这些样式定义了服务条款页面的布局,每个样式都有明确的用途:

  • 文字大小 - 标题用较大的字体(16px),内容用较小的字体(14px),子章节标题用中等字体(14px)
  • 行高 - 设置合适的行高让文字易读。内容行高设为 22,子章节内容行高设为 20,这样用户可以舒适地阅读
  • 缩进 - 子章节使用 marginLeft: 16 缩进,形成清晰的视觉层级,让用户能快速识别章节结构
  • 间距 - 各个元素之间有适当的间距。章节之间间距较大(24),让不同章节有明确的分隔

可读性 - 通过合理的字体大小、行高和间距,可以显著提高文字的可读性。用户应该能够轻松阅读和理解服务条款。

条款更新的通知

当服务条款更新时,应该通知用户有新版本的条款。这是一个重要的用户体验和法律合规的要求。用户应该被告知条款发生了什么变化。

tsx 复制代码
const checkForTermsUpdates = async () => {
  const savedVersion = await AsyncStorage.getItem('terms_of_service_version');
  
  if (savedVersion !== TERMS_OF_SERVICE.version) {
    // 显示更新通知
    showUpdateNotification();
    
    // 保存新版本
    await AsyncStorage.setItem('terms_of_service_version', TERMS_OF_SERVICE.version);
  }
};

const showUpdateNotification = () => {
  Alert.alert(
    '服务条款已更新',
    '我们的服务条款已更新,请查看最新版本。',
    [
      {text: '稍后', onPress: () => {}},
      {text: '查看', onPress: () => navigate('termsOfService')},
    ]
  );
};

这个实现的逻辑包括几个关键步骤:

  • 版本检查 - 每次打开 App 时检查条款版本。通过比较本地保存的版本和当前版本,可以判断是否有更新
  • 更新通知 - 如果版本不同,显示一个 Alert 通知用户。这个通知应该清晰地说明条款已更新
  • 导航 - 用户可以点击"查看"按钮查看新版本的条款。这样用户可以立即了解条款的变化
  • 版本保存 - 保存新版本号,这样下次打开 App 时就不会再显示相同的通知

用户通知 - 通过通知用户条款更新,可以确保用户了解最新的服务条款。这也是法律合规的要求。

条款与隐私政策的协调

在实际项目中,服务条款和隐私政策应该相互协调,避免冲突。比如,如果隐私政策说不收集某种数据,服务条款中就不应该说会使用这种数据。

在设置页面中,应该同时提供隐私政策和服务条款的链接:

tsx 复制代码
<View style={styles.legalSection}>
  <ListItem
    icon="📋"
    title="隐私政策"
    onPress={() => navigate('privacyPolicy')}
  />
  <ListItem
    icon="📜"
    title="服务条款"
    onPress={() => navigate('termsOfService')}
  />
</View>

这样用户可以方便地查看两个文档,了解 App 的完整法律信息。

法律完整性 - 隐私政策和服务条款应该相互补充,共同保护用户和开发者的权益。在编写这两个文档时,应该确保内容一致,避免冲突。

小结

服务条款页面展示了如何实现一个完整的法律条款管理系统:

  • 结构化内容 - 通过定义清晰的数据结构,方便管理和更新条款内容
  • 用户同意 - 通过对话框让用户明确同意服务条款
  • 版本管理 - 追踪用户是否已同意最新版本的条款
  • 多语言支持 - 为不同地区的用户提供本地化的条款内容
  • 清晰展示 - 通过合理的样式和布局,让用户能够轻松理解条款内容
  • 更新通知 - 当条款更新时,通知用户查看新版本
  • 法律合规 - 确保 App 符合法律要求,保护用户和开发者的权益

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

相关推荐
奚大野...1 小时前
uni-app手机端项目touchmove禁止页面上下拉滑动
前端·javascript·uni-app
Object~2 小时前
4.const和iota
开发语言·前端·javascript
攀登的牵牛花2 小时前
前端向架构突围系列 - 工程化(一):JavaScript 演进史与最佳实践
前端·javascript
夏天想2 小时前
为什么使用window.print打印的页面只有第一页。其他页面没有了。并且我希望打印的是一个弹窗的内容,竟然把弹窗的样式边框和打印的按钮都打印进去了
前端·javascript·html
鸣弦artha2 小时前
Flutter 框架跨平台鸿蒙开发 —— Image Widget 占位符技术
flutter·华为·harmonyos
重铸码农荣光2 小时前
TypeScript:JavaScript 的“防坑装甲”,写代码不再靠玄学!
前端·react.js·typescript
南山安2 小时前
TypeScript:更加安全规范的JavaScript
react.js·typescript·代码规范
世人万千丶3 小时前
鸿蒙跨端框架Flutter学习day 2、常用UI组件-层叠布局 Stack & Positioned
学习·flutter·ui·实时互动·harmonyos·鸿蒙
@PHARAOH3 小时前
WHAT - Vercel react-best-practices 系列(四)
前端·react.js·前端框架