使用 Web3.js 与 EIP-6963 构建中级 dApp:多钱包发现、账户余额查询与以太转账实战
使用 Web3.js 与 EIP-6963 构建中级 dApp多钱包发现、账户余额查询与以太转账实战【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js本教程基于 web3.js 仓库的官方中级 dApp 开发指南带领读者从零搭建一个 React TypeScript dApp通过 EIP-6963 标准自动发现浏览器中安装的多个钱包扩展不再依赖全局window.ethereum让用户选择一个钱包完成账户授权、余额展示并从一个账户向另一个账户转账 ether。读完本文你将掌握 Web3.js 的 EIP-6963 辅助函数Web3.requestEIP6963Providers/Web3.onNewProviderDiscovered、Provider Store 的 React 集成模式以及用web3.eth.sendTransaction发起真实转账的完整链路。背景为什么需要 EIP-6963传统 dApp 通过全局window.ethereum对象与钱包交互这在用户只安装一个钱包扩展时没有问题但当用户同时安装多个钱包扩展时各扩展会互相争抢window.ethereum导致 dApp 无法可靠地枚举和选择用户实际想用的钱包。EIP-6963Multi Injected Provider Discovery规定了一套基于浏览器自定义事件的双向通告机制钱包扩展监听eip6963:requestProvider事件并向页面广播eip6963:announceProvider事件dApp 据此发现所有已安装的钱包。在 web3.js 仓库中这一标准的底层实现在 packages/web3/src/web3_eip6963.ts核心包括两个事件名常量eip6963announceProvider eip6963:announceProvider与eip6963requestProvider eip6963:requestProviderEIP6963ProviderInfo钱包的元信息uuid、name、icon、rdnsEIP6963ProviderDetail由info和实际的 EIP-1193provider组成provider可直接注入Web3实例requestEIP6963Providers()向window派发eip6963:requestProvider收集钱包的announceProvider响应返回Mapuuid, EIP6963ProviderDetailonNewProviderDiscovered(callback)订阅内置的web3:providersMapUpdated事件在钱包列表更新时收到最新 Map。这两个函数被挂载为Web3的静态方法见 packages/web3/src/web3.ts因此可以直接通过Web3.requestEIP6963Providers()与Web3.onNewProviderDiscovered()调用。其行为已被仓库单元测试覆盖见 packages/web3/test/unit/web3eip6963.test.ts例如在非浏览器环境会抛出window object not available错误在浏览器环境则按web3:providersMapUpdated事件分发回调。更简洁的 API 用法示例可参考仓库中的 EIP-6963 指南。教程概览本教程共分六个步骤检查前置条件初始化 React 项目并安装依赖配置并启动 Hardhat 本地节点改造 React 应用并创建 Provider Store使用 Provider 与 Web3.js 交互创建转账表单。:::info 本中级教程建立在此前教程的基础上例如 发送交易 与 使用 React 连接 MetaMask。如果你刚接触 Web3.js建议先完成这两个教程。 :::第一步前置条件本教程假定你已具备基本的命令行使用经验并熟悉 React 与 Node.js。开始之前请确认 Node.js 及其包管理器 npm 已安装$: node -v # your version may be different, but its best to use the current stable version v20.14.0 $: npm -v 10.8.2同时请确保至少安装并配置好一个兼容 EIP-6963 的钱包浏览器扩展例如EnkryptExodusMetaMaskTrust Wallet本教程以 MetaMask 为例进行演示。第二步初始化 React 项目并安装依赖创建一个新的 React 项目TypeScript 模板并进入项目目录npx create-react-app web3-intermediate-dapp --template typescript cd web3-intermediate-dapp安装 Web3.jsnpm i web3本教程使用本地 Hardhat 网络来给钱包账户充值测试 ETH因此需要把 Hardhat 作为开发依赖安装npm i -D hardhat第三步配置并启动 Hardhat 节点由于 Hardhat 配置文件中会写入钱包的助记词secret recovery phrase务必不要把hardhat.config.js提交到 GitHub 等版本控制系统。打开create-react-app生成的.gitignore文件加入一行# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # add this line hardhat.config.js # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log*新建hardhat.config.js写入如下配置module.exports { networks: { hardhat: { accounts: { mnemonic: SECRET RECOVERY PHRASE, }, chainId: 1337, }, }, };将SECRET RECOVERY PHRASE替换为你钱包的助记词。这样 Hardhat 节点会用你的钱包账户派生测试账户且每个账户初始拥有 10000 ETH。启动 Hardhat 开发网络npx hardhat node命令执行后会输出连接地址与测试账户列表Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/ Accounts Account #0: ACCOUNT 0 ADDRESS (10000 ETH) ... Account #19: ACCOUNT 19 ADDRESS (10000 ETH):::note 如果 Hardhat 网络正确配置了钱包助记词ACCOUNT 0 ADDRESS应与钱包第一个账户的地址一致。 :::Hardhat 网络需要保持在启动它的终端中持续运行。请在项目目录中另开一个终端执行本教程后续命令。第四步改造 React 应用并创建 Provider Store首先删除脚手架自带的文件src/App.csssrc/App.test.tsxsrc/logo.svg然后创建src/useProviders.ts加入以下代码import { useSyncExternalStore } from react; import { type EIP6963ProviderDetail, type EIP6963ProviderResponse, type EIP6963ProvidersMapUpdateEvent, Web3, web3ProvidersMapUpdated, } from web3; // initial empty list of providers let providerList: EIP6963ProviderDetail[] []; /** * External store for subscribing to EIP-6963 providers */ const providerStore { // get current list of providers getSnapshot: () providerList, // subscribe to EIP-6963 provider events subscribe: (callback: () void) { // update the list of providers function setProviders(response: EIP6963ProviderResponse) { providerList []; response.forEach((provider: EIP6963ProviderDetail) { providerList.push(provider); }); // notify subscribers that the list of providers has been updated callback(); } // Web3.js helper function to request EIP-6963 providers Web3.requestEIP6963Providers().then(setProviders); // handler for newly discovered providers function updateProviders(providerEvent: EIP6963ProvidersMapUpdateEvent) { setProviders(providerEvent.detail); } // register handler for newly discovered providers with Web3.js helper function Web3.onNewProviderDiscovered(updateProviders); // return a function that unsubscribes from the created event listener return () window.removeEventListener(web3ProvidersMapUpdated as any, updateProviders); }, }; // export the provider store as a React hook export const useProviders () useSyncExternalStore(providerStore.subscribe, providerStore.getSnapshot);这个文件只导出一个成员——一个订阅了 EIP-6963 Provider 列表的 ReactuseSyncExternalStoreHook。Provider Store 完全依赖 Web3.js 导出的类型与辅助函数工作Web3.requestEIP6963Providers()首次请求已安装的钱包Web3.onNewProviderDiscovered(updateProviders)监听后续新发现的钱包如用户在运行中安装了新扩展返回的清理函数移除web3:providersMapUpdated监听避免组件卸载后内存泄漏。任何 React 组件都可以通过这个 Hook 拿到动态更新的可用 Provider 列表。注意这里requestEIP6963Providers内部在非浏览器环境会直接 reject源码见 packages/web3/src/web3_eip6963.ts因此该 Hook 只在浏览器端生效。接着把src/App.tsx内容替换为import type { EIP6963ProviderDetail } from web3; import { useProviders } from ./useProviders; function App() { // get the dynamic list of providers const providers useProviders(); return ( {providers.map((provider: EIP6963ProviderDetail) { // list available providers return ( div key{provider.info.uuid} {provider.info.name} [{provider.info.rdns}] /div ); })} / ); } export default App;App组件通过 Provider Store 列出所有可用钱包。每个 Provider 展示的信息字段名称、rdns 等均由 EIP-6963 规范定义。运行npm start在浏览器新标签页中启动 dApp并保持该标签页打开后续改动会自动热更新。如果一切正常页面应列出所有已安装的 EIP-6963 钱包。第五步使用 Provider 与 Web3.js 交互将src/App.tsx内容替换为import { useEffect, useState } from react; import { type EIP6963ProviderDetail, Web3 } from web3; import { useProviders } from ./useProviders; function App() { // get the dynamic list of providers const providers useProviders(); // application state const [web3, setWeb3] useStateWeb3 | undefined(undefined); const [accounts, setAccounts] useStatestring[]([]); const [balances, setBalances] useStateMapstring, number(new Map()); // click-handler for provider buttons function setProvider(provider: EIP6963ProviderDetail) { const web3: Web3 new Web3(provider.provider); setWeb3(web3); web3.eth.requestAccounts().then(setAccounts); provider.provider.on(accountsChanged, setAccounts); provider.provider.on(chainChanged, () window.location.reload()); } // update account balances useEffect(() { async function updateBalances(web3: Web3) { const balances new Mapstring, number(); for (const account of accounts) { const balance await web3.eth.getBalance(account); balances.set(account, parseFloat(web3.utils.fromWei(balance, ether))); } setBalances(balances); } if (web3 undefined) { return; } // set balances for list of accounts updateBalances(web3); // update balances when a new block is created const subscription web3.eth.subscribe(newBlockHeaders).then(subscription { subscription.on(data, () updateBalances(web3)); return subscription; }); return () { subscription.then(subscription subscription.unsubscribe()); }; }, [accounts, web3]); return ( {web3 undefined ? // no provider set, display list of available providers providers.map((provider: EIP6963ProviderDetail) { // for each provider, display a button to connect to that provider return ( div key{provider.info.uuid} button onClick{() setProvider(provider)} style{{ display: inline-flex, alignItems: center }} img src{provider.info.icon} alt{provider.info.name} width35 / span{provider.info.name}/span /button /div ); }) : accounts.map((address: string, ndx: number) { // provider set, list accounts and balances return ( div key{address} divAccount: {address}/div divBalance: {${balances.get(address)}}/div {ndx ! accounts.length - 1 ? br / : null} /div ); })} / ); } export default App;此时App组件为每个钱包渲染一个带图标和名称的按钮。点击按钮会触发setProvider创建 Web3 实例new Web3(provider.provider)将 EIP-6963 提供的 EIP-1193 Provider 注入 Web3.js。从源码看Web3构造器会通过Web3Context把 provider 注册进请求管理器见 packages/web3/src/web3.ts此后web3.eth的所有 RPC 调用都会走该钱包请求账户web3.eth.requestAccounts()底层调用eth_requestAccountsRPC其实现位于 packages/web3-eth/src/web3_eth.ts返回用户授权的账户地址数组监听账户切换注册accountsChanged处理器钱包切换账户时同步更新列表监听链切换注册chainChanged处理器链变化时直接重载页面避免状态不一致。useEffect负责维护余额映射遍历账户调用web3.eth.getBalance(account)并用web3.utils.fromWei(balance, ether)将 wei 转换为 ether 数值fromWei/toWei的实现位于 packages/web3-utils/src/converters.ts支持ether、gwei、wei等以太坊单位。为了让余额实时更新代码订阅了newBlockHeaders订阅——每当新区块产生就重新拉取余额组件卸载时清理函数会调用subscription.unsubscribe()释放订阅。要让钱包使用 Hardhat 开发网络还需要把网络信息配置进钱包。不同钱包的配置入口略有差异例如 MetaMask 是手动添加自定义网络 RPC但都需要填写Hardhat 启动时显示的 URLhttp://127.0.0.1:8545/链 ID1337与hardhat.config.js中chainId一致配置完成后回到npm start打开的浏览器标签页确保钱包连接到 Hardhat 网络然后点击钱包按钮。如果一切正常页面会列出钱包账户且每个账户余额为 10000 ETH。第六步创建转账表单新建src/TransferForm.tsx加入以下代码import { type ChangeEvent, type FormEvent, useEffect, useState } from react; import { type Address, Web3 } from web3; function TransferForm({ address, web3 }: { address: Address; web3: Web3 }) { // form state const [isFormValid, setIsFormValid] useStateboolean(false); const [transferTo, setTransferTo] useStatestring(); const [transferAmount, setTransferAmount] useStatestring(); // https://www.geeksforgeeks.org/ethereum-address-validation-using-regular-expressions/ function isValidAddress(address: string): boolean { return /^(0x)?[0-9a-fA-F]{40}$/.test(address); } // form validator useEffect(() { const amount parseFloat(transferAmount); setIsFormValid(isValidAddress(transferTo) !isNaN(amount) amount 0); }, [transferTo, transferAmount]); // form change handler function transferFormChange(e: ChangeEventHTMLInputElement): void { const { name, value } e.target; if (name to) { setTransferTo(value); } else if (name amount) { setTransferAmount(value); } } // submit form handler function transfer(e: FormEventHTMLFormElement): void { // prevent default form submission behavior e.preventDefault(); if (web3 undefined) { return; } // parse form data const formData: FormData new FormData(e.currentTarget); // validate to field const to: FormDataEntryValue | null formData.get(to); if (to null || !isValidAddress(to as string)) { return; } // check if amount field is empty const amount: FormDataEntryValue | null formData.get(amount); if (amount null) { return; } // validate amount field const value: number parseFloat(amount as string); if (isNaN(value) || value 0) { return; } // reset form setTransferTo(); setTransferAmount(); // send transaction web3.eth.sendTransaction({ from: address, to: to as string, value: web3.utils.toWei(value, ether), }); } return ( form onSubmit{transfer} label Transfer to:{ } input value{transferTo} onChange{transferFormChange} nameto typetext / /label span style{{ paddingRight: 5px }}/span label Transfer amount in ether:{ } input value{transferAmount undefined ? : transferAmount.toString()} onChange{transferFormChange} nameamount typenumber / /label span style{{ paddingRight: 5px }}/span button typesubmit disabled{!isFormValid} Transfer /button /form ); } export default TransferForm;TransferForm组件接收两个属性address转出方地址Address类型来自 Web3.js 类型系统web3执行转账的Web3实例。组件在表单校验与提交两个层面做了防护实时校验useEffect监听transferTo与transferAmount只有当收款地址匹配^(0x)?[0-9a-fA-F]{40}$可选0x前缀的 40 位十六进制且金额是大于 0 的有效数字时isFormValid才为true否则提交按钮被禁用提交时二次校验transfer处理函数再次校验to与amount防止绕过 UI 直接提交非法数据校验通过后清空表单。真正发起转账的是web3.eth.sendTransaction({ from, to, value })。这里的value通过web3.utils.toWei(value, ether)把用户输入的 ether 转换为 wei 精度字符串——这是以太坊交易金额的标准单位换算toWei实现在 packages/web3-utils/src/converters.ts默认按ethUnitMap中的单位换算传入非法单位会抛出InvalidUnitError。sendTransaction内部会先经web3-eth的 RPC 封装见 packages/web3-eth/src/rpc_method_wrappers.ts执行签名、广播并最终返回交易回执浏览器钱包会弹出确认弹窗让用户批准这笔交易。最后将src/App.tsx内容替换为完整版本在账户列表下方渲染转账表单import { useEffect, useState } from react; import { type EIP6963ProviderDetail, Web3 } from web3; import TransferForm from ./TransferForm; import { useProviders } from ./useProviders; function App() { // get the dynamic list of providers const providers useProviders(); // application state const [web3, setWeb3] useStateWeb3 | undefined(undefined); const [accounts, setAccounts] useStatestring[]([]); const [balances, setBalances] useStateMapstring, number(new Map()); // click-handler for provider buttons function setProvider(provider: EIP6963ProviderDetail) { const web3: Web3 new Web3(provider.provider); setWeb3(web3); web3.eth.requestAccounts().then(setAccounts); provider.provider.on(accountsChanged, setAccounts); provider.provider.on(chainChanged, () window.location.reload()); } // update account balances useEffect(() { async function updateBalances(web3: Web3) { const balances new Mapstring, number(); for (const account of accounts) { const balance await web3.eth.getBalance(account); balances.set(account, parseFloat(web3.utils.fromWei(balance, ether))); } setBalances(balances); } if (web3 undefined) { return; } // set balances for list of accounts updateBalances(web3); // update balances when a new block is created const subscription web3.eth.subscribe(newBlockHeaders).then(subscription { subscription.on(data, () updateBalances(web3)); return subscription; }); return () { subscription.then(subscription subscription.unsubscribe()); }; }, [accounts, web3]); return ( {web3 undefined ? // no provider set, display list of available providers providers.map((provider: EIP6963ProviderDetail) { // for each provider, display a button to connect to that provider return ( div key{provider.info.uuid} button onClick{() setProvider(provider)} style{{ display: inline-flex, alignItems: center }} img src{provider.info.icon} alt{provider.info.name} width35 / span{provider.info.name}/span /button /div ); }) : accounts.map((address: string, ndx: number) { // provider set, list accounts and balances return ( div key{address} divAccount: {address}/div divBalance: {${balances.get(address)}}/div TransferForm address{address} web3{web3}/TransferForm {ndx ! accounts.length - 1 ? br / : null} /div ); })} / ); } export default App;相对上一步App.tsx唯一的变化是引入了TransferForm组件并为账户列表中的每个账户渲染一个转账表单。回到npm start打开的浏览器标签页每个账户的地址和余额下方会出现转账表单。填写收款地址与金额并点击 Transfer——钱包会弹出确认请求批准后交易上链转出账户的余额随之减少由于订阅了newBlockHeaders余额会在新区块产生后自动刷新。原理深挖Web3.js 的 EIP-6963 辅助函数如何工作本教程的 Provider Store 之所以能“动态发现”钱包依赖的是 packages/web3/src/web3_eip6963.ts 中两个关键函数的实现requestEIP6963Providers()的流程对应 web3_eip6963.ts在window上注册eip6963:announceProvider监听器向window派发eip6963:requestProvider事件每个已安装的钱包扩展收到请求后会向页面广播announceProvider监听器把{ uuid, provider }写入全局eip6963ProvidersMap一个Mapstring, EIP6963ProviderDetail同时派发一个 Web3.js 自定义事件web3:providersMapUpdated常量web3ProvidersMapUpdated并 resolve 出当前的 Map。onNewProviderDiscovered(callback)对应 web3_eip6963.ts则只是简单地在该web3:providersMapUpdated事件上注册监听保证钱包列表后续发生变化例如用户新装了扩展时dApp 也能即时拿到最新 Map。这与useProvidersHook 中setProviders(providerEvent.detail)的用法一一对应。仓库单元测试 packages/web3/test/unit/web3eip6963.test.ts 验证了这些行为非浏览器环境下两个函数都会报错或 reject模拟浏览器环境时requestEIP6963Providers()能正确把announceProvider事件中的 Provider 组装成MaponNewProviderDiscovered也会在web3:providersMapUpdated上注册回调。另一个值得注意的细节是new Web3(provider.provider)传入的是 EIP-1193 Provider 对象而非 URL 字符串。Web3构造器会通过isSupportedProvider校验并把它交给Web3Context的请求管理器统一调度见 packages/web3/src/web3.ts这也是 Web3.js 能与任意符合 EIP-1193 的钱包MetaMask、Enkrypt、Exodus、Trust Wallet 等无缝协作的根基。结论本教程演示了用 Web3.js 构建一个完整 dApp 的流程从 EIP-6963 多钱包发现Provider Store useSyncExternalStore到选择钱包、requestAccounts授权、余额查询与newBlockHeaders订阅刷新再到通过sendTransaction完成 ether 转账。整个过程中Web3.js 的Web3.requestEIP6963Providers、Web3.onNewProviderDiscovered辅助函数、fromWei/toWei单位换算以及统一的 EIP-1193 Provider 抽象让 dApp 可以透明地支持多个钱包扩展不再依赖全局window.ethereum。进一步深入学习可参考仓库中的相关文档与源码EIP-6963 规范与 API 用法EIP-6963: Multi Injected Provider Discovery 指南交易基础发送交易教程React 钱包连接入门使用 React 连接 MetaMaskEIP-6963 实现源码packages/web3/src/web3_eip6963.ts单元测试packages/web3/test/unit/web3eip6963.test.ts【免费下载链接】web3.jsCollection of comprehensive TypeScript libraries for Interaction with the Ethereum JSON RPC API and utility functions.项目地址: https://gitcode.com/gh_mirrors/we/web3.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

电脑总卡?用AtlasOS的4款驱动优化工具,完整配置指南帮你把系统延迟降下来

电脑总卡?用AtlasOS的4款驱动优化工具,完整配置指南帮你把系统延迟降下来

电脑总卡?用AtlasOS的4款驱动优化工具,完整配置指南帮你把系统延迟降下来 【免费下载链接】Atlas 🚀 An open and lightweight modification to Windows, designed to optimize performance, privacy and usability. 项目地址: https://git…

2026/9/20 18:56:01 阅读更多 →
数据挖掘驱动案件串并:从特征工程到图分析排嫌疑人

数据挖掘驱动案件串并:从特征工程到图分析排嫌疑人

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 18:56:01 阅读更多 →
Cube Databricks JDBC 驱动深度解析:从变更日志看认证、导出桶与 SQL 下推的演进

Cube Databricks JDBC 驱动深度解析:从变更日志看认证、导出桶与 SQL 下推的演进

Cube Databricks JDBC 驱动深度解析:从变更日志看认证、导出桶与 SQL 下推的演进 【免费下载链接】cube 📊 Cube Core is open-source semantic layer for AI, BI and embedded analytics 项目地址: https://gitcode.com/gh_mirrors/cu/cube 本指…

2026/9/20 18:55:01 阅读更多 →

最新新闻

如何让10块钱的鼠标比苹果触控板还好用:Mac Mouse Fix完整新手指南

如何让10块钱的鼠标比苹果触控板还好用:Mac Mouse Fix完整新手指南

如何让10块钱的鼠标比苹果触控板还好用:Mac Mouse Fix完整新手指南 【免费下载链接】mac-mouse-fix Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad! 项目地址: https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix Mac Mouse Fix 是…

2026/9/20 20:17:53 阅读更多 →
运营管理期末复习:如何用逆向拆解大纲高效拿分

运营管理期末复习:如何用逆向拆解大纲高效拿分

简介:《运营管理》期末考试复习大纲是一份面向高校经管类专业学生的备考资料,内容依据课程考核要求整理,系统覆盖总论、运营战略管理、产品决策、生产系统设计与分析、生产能力决策、生产计划、生产控制、现代生产方式等章节。文件为1个PDF文…

2026/9/20 20:17:53 阅读更多 →
DBX 官网 Cloudflare Worker 部署指南:贡献者证书 GitHub 认证与匿名 Issue 提交

DBX 官网 Cloudflare Worker 部署指南:贡献者证书 GitHub 认证与匿名 Issue 提交

DBX 官网 Cloudflare Worker 部署指南:贡献者证书 GitHub 认证与匿名 Issue 提交 【免费下载链接】dbx 15MB,轻量级跨平台数据库客户端、数据库管理工具。支持 MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server 等。15MB, li…

2026/9/20 20:17:53 阅读更多 →
可视化智能仓储管理系统:RFID+JSP+MySQL实现货位可视化与出入库闭环

可视化智能仓储管理系统:RFID+JSP+MySQL实现货位可视化与出入库闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:17:53 阅读更多 →
OpenViking:Agent上下文存储的声明式操作系统

OpenViking:Agent上下文存储的声明式操作系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 20:17:53 阅读更多 →
RIOT OS 引脚定位工具:借助 soft_uart 逐引脚 bit-bang 引脚名定位 GPIO 映射

RIOT OS 引脚定位工具:借助 soft_uart 逐引脚 bit-bang 引脚名定位 GPIO 映射

物联网嵌入式操作系统实时系统 【免费下载链接】RIOT RIOT - The friendly OS for IoT 项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT 点击查看 免费下载 本指南深入讲解 RIOT OS 测试应用 tests/periph/uart_locate_pins(源码位于 tests/p…

2026/9/20 20:16:53 阅读更多 →

日新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →