当前位置: 首页 > article >正文

鸿蒙OSUniApp开发支持多语言的国际化组件#三方框架 #Uniapp

使用UniApp开发支持多语言的国际化组件

在全球化的今天,一个优秀的应用往往需要支持多种语言以满足不同地区用户的需求。本文将详细讲解如何在UniApp框架中实现一套完整的国际化解决方案,从而轻松实现多语言切换功能。

前言

去年接手了一个面向国际市场的电商项目,需要支持中文、英文和法文三种语言。项目采用UniApp框架开发,可一开始我们团队在国际化方面遇到了不少问题:业务逻辑与翻译文本耦合度高、切换语言后某些组件不更新、动态内容翻译困难等。

经过多次迭代和重构,我们最终开发出了一套灵活且易用的国际化解决方案。这套方案不仅解决了当前项目的需求,还具有很好的通用性和扩展性。今天就把这些经验分享给大家,希望能给正在做国际化的小伙伴提供一些参考。

技术选型

国际化(i18n)库的选择上,我们对比了几个主流方案:

  1. vue-i18n:Vue生态的标准国际化解决方案
  2. i18next:功能全面但体积较大
  3. 自研轻量级方案:针对UniApp定制开发

考虑到UniApp的跨端特性和性能要求,最终我们选择了vue-i18n(8.x版本),它与Vue深度集成且体积适中,社区支持也比较完善。

基础配置

1. 安装依赖

# 项目根目录执行
npm install vue-i18n@8.27.0

2. 创建多语言文件

我们在项目中创建了专门的语言文件目录结构:

/lang/en.js     # 英文/zh-CN.js  # 简体中文/fr.js     # 法文/index.js  # 统一导出

zh-CN.js为例:

export default {common: {confirm: '确认',cancel: '取消',loading: '加载中...',noData: '暂无数据',},login: {title: '用户登录',username: '用户名',password: '密码',remember: '记住密码',submit: '登录',forgotPassword: '忘记密码?',},// 更多模块...
}

3. 配置i18n实例

lang/index.js中配置i18n:

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import enUS from './en.js'
import zhCN from './zh-CN.js'
import fr from './fr.js'
import { getSystemLanguage } from '@/utils/system'Vue.use(VueI18n)// 获取系统语言或存储的语言设置
const getLanguage = () => {// 优先使用存储的语言设置const localLanguage = uni.getStorageSync('language')if (localLanguage) return localLanguage// 否则获取系统语言const systemLanguage = getSystemLanguage()// 映射系统语言到我们支持的语言const languageMap = {'en': 'en','zh-CN': 'zh-CN','fr': 'fr'}return languageMap[systemLanguage] || 'en' // 默认英文
}const i18n = new VueI18n({locale: getLanguage(),messages: {'en': enUS,'zh-CN': zhCN,'fr': fr},silentTranslationWarn: true, // 禁用翻译警告fallbackLocale: 'en' // 回退语言
})export default i18n

4. 在main.js中挂载i18n

import Vue from 'vue'
import App from './App'
import i18n from './lang'Vue.config.productionTip = false// 挂载i18n实例
Vue.prototype._i18n = i18nconst app = new Vue({i18n,...App
})app.$mount()

封装国际化组件

为了使国际化在整个应用中更加方便使用,我们封装了一个专用组件:

<!-- components/i18n-text/i18n-text.vue -->
<template><text :class="['i18n-text', customClass]" :style="customStyle">{{ finalText }}</text>
</template><script>
export default {name: 'i18n-text',props: {// i18n键名i18n: {type: String,default: ''},// 参数对象,用于替换占位符params: {type: Object,default: () => ({})},// 不使用i18n时的直接文本text: {type: String,default: ''},// 自定义类名customClass: {type: String,default: ''},// 自定义样式customStyle: {type: String,default: ''}},computed: {finalText() {// 优先使用i18n键名进行翻译if (this.i18n) {return this.$t(this.i18n, this.params)}// 否则直接使用传入的文本return this.text}}
}
</script><style>
.i18n-text {/* 可根据需要添加样式 */
}
</style>

注册为全局组件:

// components/index.js
import i18nText from './i18n-text/i18n-text.vue'export default {install(Vue) {Vue.component('i18n-text', i18nText)// 其他全局组件...}
}// main.js中引入并使用
import components from './components'
Vue.use(components)

实用功能开发

1. 语言切换工具类

// utils/language.js
import i18n from '@/lang'export const switchLanguage = (lang) => {// 切换语言i18n.locale = lang// 持久化语言设置uni.setStorageSync('language', lang)// 通知所有页面语言已变更uni.$emit('languageChanged', lang)// 刷新当前页面const pages = getCurrentPages()const currentPage = pages[pages.length - 1]if (currentPage && currentPage.$vm) {currentPage.$vm.$forceUpdate()}
}// 获取当前语言
export const getCurrentLanguage = () => {return i18n.locale
}// 检查是否为RTL语言(如阿拉伯语)
export const isRTLLanguage = () => {const rtlLanguages = ['ar', 'he'] // 从右到左书写的语言代码return rtlLanguages.includes(getCurrentLanguage())
}

2. 语言选择器组件

<!-- components/language-picker/language-picker.vue -->
<template><view class="language-picker"><view class="current-language" @tap="showOptions = true"><image :src="languageIcons[currentLanguage]" class="language-icon"></image><text>{{ languageNames[currentLanguage] }}</text><uni-icons type="bottom" size="14" color="#666"></uni-icons></view><uni-popup ref="popup" type="bottom" @change="popupChange"><view class="language-options"><view class="popup-title"><i18n-text i18n="settings.selectLanguage"></i18n-text></view><view v-for="(name, code) in languageNames" :key="code"class="language-option":class="{ active: currentLanguage === code }"@tap="changeLanguage(code)"><image :src="languageIcons[code]" class="language-icon"></image><text>{{ name }}</text><uni-icons v-if="currentLanguage === code" type="checkmarkempty" size="18" color="#007AFF"></uni-icons></view><view class="cancel-btn" @tap="showOptions = false"><i18n-text i18n="common.cancel"></i18n-text></view></view></uni-popup></view>
</template><script>
import { getCurrentLanguage, switchLanguage } from '@/utils/language'export default {name: 'language-picker',data() {return {showOptions: false,currentLanguage: getCurrentLanguage(),languageNames: {'en': 'English','zh-CN': '简体中文','fr': 'Français',},languageIcons: {'en': '/static/flags/en.png','zh-CN': '/static/flags/zh-cn.png','fr': '/static/flags/fr.png',}}},watch: {showOptions(val) {if (val) {this.$refs.popup.open()} else {this.$refs.popup.close()}}},methods: {changeLanguage(lang) {if (this.currentLanguage === lang) {this.showOptions = falsereturn}// 设置加载状态uni.showLoading({ title: '' })// 切换语言switchLanguage(lang)this.currentLanguage = langthis.showOptions = falsesetTimeout(() => {uni.hideLoading()}, 500)},popupChange(e) {this.showOptions = e.show}}
}
</script><style lang="scss">
.language-picker {.current-language {display: flex;align-items: center;padding: 6rpx 16rpx;border-radius: 8rpx;background-color: rgba(0, 0, 0, 0.05);.language-icon {width: 36rpx;height: 36rpx;margin-right: 8rpx;border-radius: 50%;}}.language-options {background-color: #fff;border-radius: 16rpx 16rpx 0 0;padding-bottom: env(safe-area-inset-bottom);.popup-title {text-align: center;padding: 30rpx 0;font-size: 32rpx;font-weight: 500;border-bottom: 1rpx solid #eee;}.language-option {display: flex;align-items: center;padding: 30rpx 40rpx;border-bottom: 1rpx solid #f5f5f5;.language-icon {width: 50rpx;height: 50rpx;margin-right: 20rpx;border-radius: 50%;}&.active {background-color: #f9f9f9;}}.cancel-btn {text-align: center;padding: 30rpx 0;color: #007AFF;font-size: 32rpx;}}
}
</style>

实战应用

1. 在页面中使用

<!-- pages/home/home.vue -->
<template><view class="home"><view class="header"><i18n-text i18n="home.title" class="title"></i18n-text><language-picker></language-picker></view><view class="content"><view class="welcome-message"><i18n-text i18n="home.welcome" :params="{ username: userInfo.nickname }"></i18n-text></view><view class="product-list"><view class="product-item" v-for="(item, index) in productList" :key="index"><image :src="item.image" mode="aspectFill"></image><view class="product-info"><!-- 产品标题可能来自接口,需要动态翻译 --><text class="product-title">{{ getProductTitle(item) }}</text><text class="product-price">{{ formatCurrency(item.price) }}</text></view></view></view></view></view>
</template><script>
export default {data() {return {userInfo: {nickname: '张三'},productList: []}},onLoad() {this.fetchProductList()// 监听语言变化刷新数据uni.$on('languageChanged', this.handleLanguageChange)},onUnload() {uni.$off('languageChanged', this.handleLanguageChange)},methods: {async fetchProductList() {// 模拟接口请求const res = await this.$api.product.getList()this.productList = res.data},handleLanguageChange() {// 语言变化时刷新数据this.fetchProductList()},// 根据当前语言获取正确的产品标题getProductTitle(item) {const lang = this.$i18n.localeconst titleKey = `title_${lang.replace('-', '_')}`// 如果接口返回了对应语言的标题,优先使用if (item[titleKey]) {return item[titleKey]}// 否则使用默认语言标题return item.title},// 根据当前语言格式化货币formatCurrency(price) {const lang = this.$i18n.localeconst currencyMap = {'zh-CN': 'CNY','en': 'USD','fr': 'EUR'}return new Intl.NumberFormat(lang, {style: 'currency',currency: currencyMap[lang] || 'USD'}).format(price)}}
}
</script>

2. 处理动态内容和API数据

在实际项目中,我们经常需要处理来自API的多语言数据,以下是一些常用策略:

// 处理API返回的多语言内容
export const processMultiLangContent = (data) => {const currentLang = getCurrentLanguage()const result = {}// 递归处理对象const processObject = (obj) => {const newObj = {}Object.keys(obj).forEach(key => {const value = obj[key]// 如果是多语言字段对象 { zh-CN: '中文', en: 'English' }if (value && typeof value === 'object' && !Array.isArray(value) && value[currentLang]) {newObj[key] = value[currentLang]} // 如果是普通对象,递归处理else if (value && typeof value === 'object' && !Array.isArray(value)) {newObj[key] = processObject(value)}// 如果是数组,处理数组中的每个对象else if (Array.isArray(value)) {newObj[key] = value.map(item => {if (typeof item === 'object') {return processObject(item)}return item})}// 其他情况直接赋值else {newObj[key] = value}})return newObj}return processObject(data)
}

进阶技巧

1. 请求拦截器添加语言参数

为了让后端能够返回对应语言的内容,我们在请求拦截器中添加语言参数:

// request.js
import { getCurrentLanguage } from '@/utils/language'// 请求拦截
export const requestInterceptor = (config) => {// 添加语言参数config.header = {...config.header,'Accept-Language': getCurrentLanguage()}return config
}

2. 处理消息提示

封装消息提示方法,自动应用翻译:

// utils/message.js
import i18n from '@/lang'export const showToast = (messageKey, params = {}) => {uni.showToast({title: i18n.t(messageKey, params),icon: 'none'})
}export const showModal = (titleKey, contentKey, params = {}) => {return new Promise((resolve, reject) => {uni.showModal({title: i18n.t(titleKey),content: i18n.t(contentKey, params),confirmText: i18n.t('common.confirm'),cancelText: i18n.t('common.cancel'),success: (res) => {if (res.confirm) {resolve(true)} else {resolve(false)}},fail: reject})})
}

常见问题及解决方案

1. 组件未响应语言变化

解决方案:使用事件总线通知组件重新渲染

// 切换语言时触发全局事件
uni.$emit('languageChanged', newLang)// 在组件中监听
created() {this.unsubscribe = uni.$on('languageChanged', this.handleLanguageChange)
},
beforeDestroy() {this.unsubscribe()
},
methods: {handleLanguageChange() {this.$forceUpdate()}
}

2. 日期格式化问题

解决方案:封装日期格式化工具函数

// utils/date.js
import { getCurrentLanguage } from './language'export const formatDate = (date, format = 'short') => {const targetDate = new Date(date)const lang = getCurrentLanguage()const options = {'short': { year: 'numeric', month: 'short', day: 'numeric' },'long': { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },'time': { hour: '2-digit', minute: '2-digit' },'full': { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', hour: '2-digit', minute: '2-digit' }}return new Intl.DateTimeFormat(lang, options[format]).format(targetDate)
}

性能优化

为了提高应用性能,我们采取了以下措施:

  1. 按需加载语言包:根据用户设置的语言只加载需要的语言包
  2. 缓存翻译结果:对频繁使用的翻译进行缓存
  3. 避免过度翻译:只翻译用户可见内容,非关键内容使用默认语言
// lang/loader.js - 动态加载语言包
export const loadLanguage = async (lang) => {let messages = {}try {// 动态导入语言包const module = await import(/* webpackChunkName: "[request]" */ `./${lang}.js`)messages = module.default} catch (e) {console.error(`Could not load language pack: ${lang}`, e)// 加载失败时使用备用语言const fallbackModule = await import(/* webpackChunkName: "en" */ './en.js')messages = fallbackModule.default}return messages
}

总结

通过本文,我们详细介绍了UniApp中实现国际化的完整方案,从基础配置到组件封装,再到实际应用和性能优化。这套方案具有以下特点:

  1. 易用性:通过组件化设计,使翻译使用变得简单
  2. 灵活性:支持静态翻译和动态内容翻译
  3. 可扩展性:轻松添加新语言支持
  4. 性能优化:按需加载和缓存机制保证性能

希望这篇文章能对大家在UniApp项目中实现国际化有所帮助。如果有任何问题或建议,欢迎在评论区留言交流!

参考资料

  1. vue-i18n官方文档
  2. UniApp全局组件开发文档
  3. Web国际化API

相关文章:

鸿蒙OSUniApp开发支持多语言的国际化组件#三方框架 #Uniapp

使用UniApp开发支持多语言的国际化组件 在全球化的今天&#xff0c;一个优秀的应用往往需要支持多种语言以满足不同地区用户的需求。本文将详细讲解如何在UniApp框架中实现一套完整的国际化解决方案&#xff0c;从而轻松实现多语言切换功能。 前言 去年接手了一个面向国际市场…...

国产数据库工具突围:SQLynx如何解决Navicat的三大痛点?深度体验报告

引言&#xff1a;Navicat的"中国困境" 当开发者面对达梦数据库的存储过程调试&#xff0c;或是在人大金仓中处理复杂查询时&#xff0c;Navicat突然变得力不从心——这不是个例。 真实痛点&#xff1a;某政务系统迁移至OceanBase后&#xff0c;开发团队发现Navicat无…...

《Adversarial Sticker: A Stealthy Attack Method in the Physical World》论文分享(侵删)

原文链接&#xff1a;Adversarial Sticker: A Stealthy Attack Method in the Physical World | IEEE Journals & Magazine | IEEE Xplore author{Xingxing Wei and Ying Guo and Jie Yu} 摘要 为了评估深度学习在物理世界中的脆弱性&#xff0c;最近的工作引入了对抗补丁…...

Python生成器:高效处理大数据的秘密武器

生成器概述 生成器是 Python 中的一种特殊迭代器&#xff0c;通过普通函数的语法实现&#xff0c;但使用 yield 语句返回数据。生成器自动实现了 __iter__() 和 __next__() 方法&#xff0c;因此可以直接用于迭代。生成器的核心特点是延迟计算&#xff08;lazy evaluation&…...

React Native/Flutter 原生模块开发

以下是关于 React Native 和 Flutter 原生模块开发的基本知识点总结: 一、核心概念对比 维度React NativeFlutter架构基础JavaScriptCore/Hermes + Bridge/TurboModulesDart VM + Skia引擎原生交互方式Native Modules + Native UI ComponentsPlatform Channels + Platform Vie…...

嵌入式STM32学习——继电器

继电器模块引脚说明 VCC&#xff08;&#xff09;&#xff1a; 供电正极。连接此引脚到电源&#xff08;通常是直流电源&#xff09;&#xff0c;以提供继电器线圈所需的电流。 GND&#xff08;-&#xff09;&#xff1a; 地。连接此引脚到电源的负极或地。 IN&#xff08;或…...

从基础到实习项目:C++后端开发学习指南

在当今技术快速迭代的背景下&#xff0c;后端开发作为软件工程的核心支柱持续发挥着关键作用。C凭借其卓越的性能表现和系统级控制能力&#xff0c;依然是构建高性能后端服务的首选语言之一。本文将系统性地解析现代C后端开发的核心技术体系&#xff0c;包括从语言特性精要到架…...

AI软件汇总与功能解析:赋能未来的智能工具库

人工智能&#xff08;AI&#xff09;技术的快速发展催生了大量功能强大的软件工具&#xff0c;覆盖自然语言处理、计算机视觉、数据分析、自动化决策等多个领域。本文将汇总当前主流的AI软件&#xff0c;并解析其核心功能与应用场景&#xff0c;为企业和开发者提供参考指南。 一…...

Xinference推理框架

概述 GitHub&#xff0c;官方文档。 核心优势 性能优化&#xff1a;通过vLLM、SGLang等引擎实现低延迟推理&#xff0c;吞吐量提升2-3倍&#xff1b;企业级支持&#xff1a;支持分布式部署、国产硬件适配及模型全生命周期管理&#xff1b;生态兼容&#xff1a;无缝对接LangC…...

前端ECS简介

ECS概念 ECS是一种软件架构模式&#xff0c;常见于游戏业务场景&#xff0c;其主要对象分类为 • Entity 实体,ECS架构中所有的业务对象都必须拥有一个唯一的Entity实体 • Component 组件,存储着数据结构,对应着某一种业务属性,一个Entity上可以动态挂载多个Component • …...

ET ProcessOuterSender类(实体) 分析

ProcessOuterSender 夸进程发送Actor消息&#xff0c;只在NetInner(Scene)使用。 字段 TIMEOUT_TIME RPC超时时间RpcId rpcIdrequestCallback 存储RPC的回调事件AService 进程之间的网络服务InnerProtocol 内部网络协议类型 目前固定KCP 方法 OnRead 方法&#xff0c;收包…...

redis中key的过期和淘汰

一、过期&#xff08;redis主动删除&#xff09; 设置了ttl过期时间的key&#xff0c;在ttl时间到的时候redis会删除过期的key。但是redis是惰性过期。惰性过期&#xff1a;redis并不会立即删除过期的key&#xff0c;而是会在获取key的时候判断key是否过期&#xff0c;如果发现…...

Dify与n8n全面对比指南:AI应用开发与工作流自动化平台选择【2025最新】

Dify与n8n全面对比指南&#xff1a;AI应用开发与工作流自动化平台选择【2025最新】 随着AI技术与自动化工具的迅速发展&#xff0c;开发者和企业面临着多种平台选择。Dify和n8n作为两个备受关注的自动化平台&#xff0c;分别专注于不同领域&#xff1a;Dify主要面向AI应用开发&…...

【深度学习之四】知识蒸馏综述提炼

知识蒸馏综述提炼 目录 知识蒸馏综述提炼 前言 参考文献 一、什么是知识蒸馏&#xff1f; 二、为什么要知识蒸馏&#xff1f; 三、一点点理论 四、知识蒸馏代码 总结 前言 知识蒸馏作为一种新兴的、通用的模型压缩和迁移学习架构&#xff0c;在最近几年展现出蓬勃的活力…...

redis解决常见的秒杀问题

title: redis解决常见的秒杀问题 date: 2025-03-07 14:24:13 tags: redis categories: redis的应用 秒杀问题 每个店铺都可以发布优惠券&#xff0c;保存到 tb_voucher 表中&#xff1b;当用户抢购时&#xff0c;生成订单并保存到 tb_voucher_order 表中。 订单表如果使用数据…...

TypeScript中文文档

最近一直想学习TypeScript&#xff0c;一直找不到一个全面的完整的TypeScript 中文文档。在网直上找了了久&#xff0c;终于找到一个全面的中文的typescript中文学习站&#xff0c;有学习ts的朋友可以年。 文档地址&#xff1a;https://typescript.uihtm.com 该TypeScript 官…...

Function Calling

在介绍Function Calling之前我们先了解一个概念,接口。 接口 两种常见接口: 人机交互接口,User Interface,简称 UI应用程序编程接口,Application Programming Interface,简称 API接口能「通」的关键,是两边都要遵守约定。 人要按照 UI 的设计来操作。UI 的设计要符合人…...

【搭建Node-RED + MQTT Broker实现AI大模型交互】

搭建Node-RED MQTT Broker实现AI大模型交互 搭建Node-RED MQTT Broker实现AI大模型交互一、系统架构二、环境准备与安装1. 安装Node.js2. 安装Mosquitto MQTT Broker3. 配置Mosquitto4. 安装Node-RED5. 配置Node-RED监听所有网络接口6. 启动Node-RED 三、Node-RED流程配置1. …...

高可靠低纹波国产4644电源芯片在工业设备的应用

摘要 随着工业自动化和智能化的飞速发展&#xff0c;工业设备对于电源芯片的性能和可靠性提出了前所未有的严格要求。电源芯片作为工业设备的核心供电组件&#xff0c;其性能直接影响到整个设备的运行效率和稳定性。本文以国科安芯的ASP4644四通道降压稳压器为例&#xff0c;通…...

面试--HTML

1.src和href的区别 总结来说&#xff1a; <font style"color:rgb(238, 39, 70);background-color:rgb(249, 241, 219);">src</font>用于替换当前元素&#xff0c;指向的资源会嵌入到文档中&#xff0c;例如脚本、图像、框架等。<font style"co…...

SparkSQL操作Mysql-准备mysql环境

我们计划在hadoop001这台设备上安装mysql服务器&#xff0c;&#xff08;当然也可以重新使用一台全新的虚拟机&#xff09;。 以下是具体步骤&#xff1a; 使用finalshell连接hadoop001.查看是否已安装MySQL。命令是: rpm -qa|grep mariadb若已安装&#xff0c;需要先做卸载MyS…...

Linux常用方法

1、查看日志后100行 tail -f -n 100 catalina.out 2、ps命令 ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照 ps -ef 显示所有进程信息&#xff0c;连同命令行&#xff0c;ps 与grep 常用组合用法&#xff0c;查找特定进程 ps aux列出目前所有的…...

[c++项目]云备份项目测试

1. 测试概述 测试时间&#xff1a;2024年3月 测试环境&#xff1a;macOS 23.4.0 测试工具&#xff1a;VSCode, CMake, GTest 2. 功能测试 2.1 文件备份功能 测试项预期结果实际结果状态单文件备份成功上传并保存成功✅多文件备份批量上传成功成功✅大文件备份分片上传成功…...

DeepBook 与 CEX 的不同

如果你曾经使用过像币安或 Coinbase 这样的中心化交易所&#xff08;CEX&#xff09;&#xff0c;你可能已经熟悉了订单簿系统 — — 这是一种撮合买卖双方进行交易的机制。而 DeepBook 是 Sui 上首个完全链上的中央限价订单簿。 那么&#xff0c;是什么让 DeepBook 如此独特&…...

Scrapy框架下地图爬虫的进度监控与优化策略

1. 引言 在互联网数据采集领域&#xff0c;地图数据爬取是一项常见但具有挑战性的任务。由于地图数据通常具有复杂的结构&#xff08;如POI点、路径信息、动态加载等&#xff09;&#xff0c;使用传统的爬虫技术可能会遇到效率低下、反爬策略限制、任务进度难以监控等问题。 …...

城市扫街人文街头纪实胶片电影感Lr调色预设,DNG/手机适配滤镜!

调色详情 城市扫街人文街头纪实胶片电影感 Lr 调色是通过 Lightroom&#xff08;Lr&#xff09;软件&#xff0c;对城市街头抓拍的人文纪实照片进行后期调色处理。旨在赋予照片如同胶片拍摄的质感以及电影般浓厚的叙事氛围&#xff0c;不放过每一个日常又珍贵的瞬间&#xff0c…...

嵌入式学习笔记 D21:双向链表的基本操作

双向链表的定义与创建双向链表的插入双向链表的查找双向链表的修改双向链表的删除双向链表的逆序MakeFile工具使用 一、双向链表的定义与创建 1.双向链表的定义&#xff1a; 双向链表是在单链表的每个结点中&#xff0c;再设置一个指向其前一个结点的指针域。 struct DOUNode…...

让AI帮我写一个word转pdf的工具

需求分析 前几天&#xff0c;一个美女找我&#xff1a; 阿瑞啊&#xff0c;能不能帮我写个工具&#xff0c;我想把word文件转为pdf格式的 我说&#xff1a;“你直接网上搜啊&#xff0c;网上工具多了去了” 美女说&#xff1a; 网上的要么是需要登录注册会员的&#xff0c;要…...

OrangePi Zero 3学习笔记(Android篇)10 - SPI和从设备

目录 1. 配置内核 2. 修改设备数 3. 修改权限 4. 验证 Zero 3的板子有2个SPI Master接口&#xff0c;其中SPI0接的是板载16MB大小的SPI Nor Flash&#xff0c;SPI1则是导出到26pin的接口上。 spi和i2c有点不同&#xff0c;spi是直接生成spi虚拟设备&#xff0c;所以在dev里…...

基于策略的强化学习方法之近端策略优化(PPO)深度解析

PPO&#xff08;Proximal Policy Optimization&#xff09;是一种基于策略梯度的强化学习算法&#xff0c;旨在通过限制策略更新幅度来提升训练稳定性。传统策略梯度方法&#xff08;如REINFORCE&#xff09;直接优化策略参数&#xff0c;但易因更新步长过大导致性能震荡或崩溃…...