R Shiny响应式系统核心揭秘:actionButton点击事件是如何被捕捉并计数的?

第一章:R Shiny响应式系统概览

R Shiny 是一个强大的 R 语言框架,用于构建交互式 Web 应用程序。它将数据分析能力与前端交互无缝结合,使数据科学家能够快速部署可视化仪表板和统计工具。Shiny 的核心优势在于其响应式编程模型,该模型自动追踪输入与输出之间的依赖关系,并在输入变化时高效更新相关组件。

响应式架构的基本组成

Shiny 应用由两个主要部分构成:用户界面(UI)和服务器逻辑(Server)。UI 定义页面布局和控件,而服务器处理数据逻辑并生成动态内容。二者通过 shinyApp() 函数组合成完整应用。
  • UI 层:使用 fluidPage()sidebarLayout() 等函数构建网页结构
  • Server 层:包含响应式表达式(如 reactive({}))和输出渲染函数(如 renderPlot()
  • 响应式依赖:系统自动管理输入(input$xxx)与输出(output$yyy)间的依赖链

一个最简示例

# 加载 shiny 包
library(shiny)

# 定义 UI
ui <- fluidPage(
  sliderInput("bins", "Histogram Bins:", min = 1, max = 50, value = 30),
  plotOutput("distPlot")
)

# 定义服务器逻辑
server <- function(input, output) {
  output$distPlot <- renderPlot({
    x <- faithful$eruptions
    hist(x, breaks = input$bins, col = 'darkgray', border = 'white')
  })
}

# 启动应用
shinyApp(ui = ui, server = server)
上述代码创建了一个滑块控件,用户拖动时会实时更新直方图的分组数量。renderPlot() 内部引用了 input$bins,Shiny 自动建立依赖关系,并在滑块值变化时重新执行绘图逻辑。

核心响应式对象类型

类型用途说明
input接收用户界面控件的当前值
output存储需在 UI 中展示的渲染结果
reactive({})封装可复用的计算逻辑,仅在依赖变化时重新求值
graph LR A[User Input] --> B{Reactive Dependency} B --> C[Reactive Expression] C --> D[Render Output] D --> E[Update UI]

第二章:actionButton机制的底层原理

2.1 响应式编程模型中的事件驱动基础

响应式编程的核心在于对数据流和变化传播的处理,而事件驱动架构为其提供了底层支撑。系统通过监听事件源,触发相应的数据变换与副作用操作,实现高度解耦的异步通信。
事件流的构建与订阅
在响应式编程中,事件通常被组织为可观察的数据流(Observable Stream)。以下是一个使用 RxJS 创建事件流的示例:

const { fromEvent } = rxjs;

// 监听按钮点击事件
const button = document.getElementById('submit');
const click$ = fromEvent(button, 'click');

// 订阅事件流
click$.subscribe(() => {
  console.log('按钮被点击');
});
上述代码中,fromEvent 将 DOM 事件封装为 Observable,click$ 表示一个事件流,subscribe 启动监听。这种模式使得事件处理更加声明式和可组合。
事件驱动的优势
  • 松耦合:发布者与订阅者无需直接引用
  • 异步友好:天然支持非阻塞操作
  • 可扩展性强:易于添加新的事件处理器

2.2 actionButton在Shiny会话生命周期中的角色

`actionButton` 是 Shiny 应用中触发响应逻辑的关键输入控件,其核心作用是在用户交互时标记“事件发生”,从而激活服务器端的条件性计算。
事件驱动机制
`actionButton` 不保存状态值,而是通过计数器记录点击次数。每次点击都会使返回值递增1,从而触发 `observeEvent` 或 `eventReactives`。

actionButton("run", "运行分析")
observeEvent(input$run, {
  # 点击按钮时执行
  data <- longRunningCalc()
  output$result <- renderTable(data)
})
上述代码中,`input$run` 初始为0,每点击一次自动加1,驱动观察器重新执行。
与会话生命周期的集成
当新用户会话建立时,`actionButton` 随UI初始化注册;在会话活跃期间响应交互;会话终止后,其状态被清除,确保隔离性。
  • 初始化阶段:绑定输入ID到前端元素
  • 运行阶段:监听DOM点击并更新服务器端值
  • 销毁阶段:随会话释放资源

2.3 输入对象(input)如何捕获前端DOM事件

在现代前端框架中,`input` 元素通过绑定事件监听器来捕获用户交互行为。最常见的方法是使用 `addEventListener` 监听如 `input`、`change` 或 `keydown` 等 DOM 事件。
事件绑定机制
当用户在输入框中输入内容时,`input` 事件会实时触发,适合实现双向数据绑定。

const inputElement = document.querySelector('#myInput');
inputElement.addEventListener('input', (e) => {
  console.log('当前值:', e.target.value); // 实时获取输入值
});
上述代码中,`e.target.value` 获取触发事件的输入框当前内容,`input` 事件在每次字符变化时立即响应,适用于搜索框、表单验证等场景。
常用事件对比
  • input:内容变更即触发,包括键盘输入、粘贴、拖拽等;
  • change:失去焦点且值改变后触发;
  • keydown:按键按下时触发,可用于控制输入行为。

2.4 observeEvent与eventReactive的触发机制解析

在Shiny应用中,observeEventeventReactive用于响应特定事件触发逻辑执行,但二者在执行机制上有本质区别。
触发行为差异
  • observeEvent:副作用驱动,仅在指定事件发生时执行内部代码块;
  • eventReactive:惰性求值,返回一个reactive表达式,仅当被其他观察者读取且事件触发后才重新计算。
典型代码示例

observeEvent(input$submit, {
  # 按钮点击立即执行
  print("提交被触发")
})

data <- eventReactive(input$submit, {
  # 仅当调用data()且按钮点击后才执行
  reactiveValuesToList(input)
})
上述代码中,observeEvent直接响应UI事件,而eventReactive封装可复用的响应式值,适用于数据处理链路。

2.5 利用调试工具窥探点击事件的传输路径

在前端开发中,理解点击事件的完整传输路径对排查交互问题至关重要。通过浏览器开发者工具的“Event Listeners”面板,可实时查看元素绑定的事件及其调用栈。
事件监听的捕获与冒泡阶段
DOM 事件遵循捕获→目标→冒泡三个阶段。使用 addEventListener 时,可通过第三个参数控制阶段:
element.addEventListener('click', handler, true);
// true 表示在捕获阶段触发,false(默认)为冒泡阶段
该机制允许我们在事件传播的不同层级插入调试逻辑。
利用断点追踪执行流
在 Chrome DevTools 中,可在事件监听器中设置断点,逐步跟踪 event.targetevent.currentTarget 的变化,结合调用堆栈分析事件委托是否按预期工作。
属性说明
event.target实际触发事件的元素
event.currentTarget当前绑定监听器的元素

第三章:构建可计数的点击响应逻辑

3.1 使用reactiveVal实现简单的点击计数器

在Shiny应用中,`reactiveVal` 是管理局部响应式值的轻量级工具。它适用于需要在函数作用域内维护可变状态的场景,比如实现一个点击计数器。
创建响应式变量
使用 `reactiveVal()` 初始化一个响应式值,返回一个可读写的函数对象:
counter <- reactiveVal(0)
该代码创建初始值为0的响应式变量。调用 `counter()` 获取当前值,`counter(new_value)` 更新值。
绑定事件与更新UI
通过 `observeEvent` 监听按钮点击并递增计数:
observeEvent(input$btn, {
  counter(counter() + 1)
})
每次点击触发时,读取当前计数值并加1后重新赋值。前端使用 `renderText({ counter() })` 实时反映变化,实现数据自动同步。

3.2 基于observeEvent的副作用处理与状态更新

在响应式系统中,observeEvent 是处理副作用的核心机制,它监听特定状态变化并触发相应的操作。
事件监听与副作用解耦
通过 observeEvent,可将状态变更与业务逻辑分离,提升模块可维护性。例如:
// 监听用户登录事件,执行日志记录和通知
observeEvent("userLoggedIn", func(user User) {
    log.Info("用户登录:", user.ID)
    sendWelcomeNotification(user)
})
上述代码注册了一个事件处理器,在用户登录时自动执行日志写入与通知发送,实现关注点分离。
状态同步策略
多个组件间的状态一致性依赖事件驱动更新。常见处理模式包括:
  • 异步广播:确保事件被所有订阅者接收
  • 事务封装:在同一个上下文中更新状态并发布事件
  • 错误回滚:副作用失败时触发补偿操作

3.3 避免重复响应:理解once参数与去抖策略

在事件驱动系统中,重复响应常导致资源浪费或状态错乱。使用 `once` 参数可确保监听器仅执行一次,适用于初始化或一次性任务。
once 参数的使用
eventEmitter.once('data', () => {
  console.log('This runs only once');
});
该代码注册一个仅触发一次的监听器,执行后自动注销,防止重复调用。
去抖(Debounce)策略
去抖通过延迟执行,忽略高频触发中的中间状态,常用于搜索输入、窗口调整等场景。
  • 每次触发重置计时器
  • 仅在最后一次调用后延迟执行
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
上述函数返回一个去抖包装器,`timer` 保存定时器引用,`delay` 控制延迟毫秒数,确保函数在连续调用中仅最终执行一次。

第四章:进阶应用场景与性能优化

4.1 多按钮协作与事件隔离设计模式

在复杂用户界面中,多个按钮常需协同工作,但直接绑定事件易导致状态冲突。通过事件隔离设计,可解耦组件间的依赖关系。
事件委托与状态管理
使用事件委托将按钮事件集中处理,结合状态机管理按钮行为:

// 事件隔离控制器
class ButtonController {
  constructor() {
    this.states = new Map();
    this.listeners = [];
  }

  register(buttonId, handler) {
    this.states.set(buttonId, 'idle');
    this.listeners.push({ id: buttonId, handler });
  }

  trigger(sourceId, event) {
    // 隔离执行,避免直接调用
    this.listeners.forEach(({ id, handler }) => {
      if (id !== sourceId && this.canRespond(id, event)) {
        handler(event, this.getState(id));
      }
    });
  }

  canRespond(buttonId, event) {
    return this.states.get(buttonId) !== 'disabled';
  }
}
上述代码通过 ButtonController 集中管理按钮状态与响应逻辑,trigger 方法确保事件源不直接影响其他按钮,实现行为隔离。
协作策略配置
  • 互斥模式:同一时间仅一个按钮生效
  • 链式触发:前一个按钮完成触发下一个
  • 批量禁用:根据全局状态批量控制可见性

4.2 在模块化应用中共享和传递点击状态

在现代前端架构中,模块间的状态同步至关重要。当用户与某一组件交互时,其他相关模块需及时感知并响应这一行为。
状态提升与事件总线
通过将点击状态提升至共同父级或使用事件总线机制,可实现跨模块通信。例如,在Vue中可通过$emit与$on触发和监听自定义事件:
this.$emit('click:update', { active: true, source: 'button-module' });
该代码表示从子模块发出点击状态更新事件,携带激活状态与来源信息,父级或其他监听者据此更新视图或执行逻辑。
集中式状态管理优势
对于复杂应用,推荐使用Pinia或Redux等工具统一管理状态。点击行为被封装为动作(action),状态变更则通过 mutations 或 reducers 处理,确保数据流清晰可控。
  • 状态变更可追踪,便于调试
  • 多模块响应同一事件更高效
  • 避免深层嵌套的回调地狱

4.3 利用isolate控制无效重绘提升性能

在Flutter中,频繁的UI重绘会导致性能瓶颈。通过使用`isolate`,可将耗时计算移出主线程,避免阻塞UI渲染。
Isolate的基本结构
import 'dart:isolate';

void startIsolate(SendPort sendPort) {
  final data = expensiveCalculation();
  sendPort.send(data);
}

// 主线程中启动
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(startIsolate, receivePort.sendPort);
上述代码通过SendPortReceivePort实现双向通信,确保主线程不被计算任务阻塞。
性能优化对比
场景帧率(FPS)卡顿次数
无Isolate4218
使用Isolate583
将数据处理逻辑隔离执行后,UI流畅度显著提升,有效减少因重绘导致的丢帧问题。

4.4 持久化点击数据:结合缓存或数据库存储

在高并发场景下,直接将点击数据写入数据库会造成性能瓶颈。因此,通常采用“缓存+异步持久化”的策略,先将点击事件暂存于 Redis 等内存数据库中,再批量写入 MySQL 或 ClickHouse 等持久化存储。
数据同步机制
通过定时任务或消息队列消费缓存中的点击数据,实现与数据库的最终一致性。以下为基于 Go 的 Redis 缓存写入示例:
func SaveClickToRedis(redisClient *redis.Client, itemID string) {
    ctx := context.Background()
    key := "clicks:temp:" + itemID
    // 原子递增点击计数
    err := redisClient.Incr(ctx, key).Err()
    if err != nil {
        log.Printf("Redis increment error: %v", err)
    }
}
上述代码使用 Incr 命令对指定商品的临时点击量进行原子自增,避免并发竞争。key 设计包含前缀以支持后续扫描和批量处理。
持久化策略对比
策略优点缺点
实时写库数据强一致性能差,易成瓶颈
缓存+批量落库高性能、可扩展存在短暂数据丢失风险

第五章:总结与扩展思考

性能优化的实战路径
在高并发系统中,数据库连接池配置直接影响响应延迟。以下是一个基于 Go 的连接池调优示例:
// 设置最大空闲连接数与生命周期
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(time.Hour) // 避免长时间持有陈旧连接
合理设置这些参数可减少 TCP 握手开销,提升服务稳定性。
微服务架构中的可观测性构建
现代系统依赖三大支柱:日志、指标、追踪。下表展示了常用工具组合:
类别开源方案云服务替代
日志收集Fluentd + ElasticsearchAWS CloudWatch
指标监控Prometheus + GrafanaDatadog
分布式追踪JaegerGoogle Cloud Trace
技术选型的权衡策略
选择消息队列时需评估多个维度:
  • 吞吐量需求:Kafka 适合高写入场景,RabbitMQ 更利于复杂路由
  • 一致性要求:金融系统优先考虑持久化与事务支持
  • 运维成本:托管服务(如 Amazon SQS)降低自建集群负担
  • 生态集成:是否与现有 CI/CD 和监控体系兼容
部署拓扑示意图:
用户请求 → API 网关 → 认证服务 → 业务微服务 → 消息队列 → 数据处理引擎
真实案例显示,某电商平台通过引入缓存预热机制,在大促前自动加载热点商品数据至 Redis,使首秒响应时间从 800ms 降至 90ms。
代码下载链接: https://pan.quark.cn/s/a4b39357ea24 依据所提供的文件资料,可以归纳出以下关于“数字三角形 C++”的相关知识要点: ## 数字三角形问题概述 数字三角形问题是指在一个由数字构成的三角形中,寻找一条从顶部到底部的路径,使得该路径上数字的总和达到最大值。这个问题可以通过动态规划技术进行求解。 ### 问题定义 给定一个数字三角形,其结构如下所示: ``` 3 7 4 2 4 6 8 5 9 3 ``` 目标是从顶端出发到达最底端的一行,找到一条路径,使得该路径上数字的累计值最大。例如,在上述示例中,路径 `3 → 7 → 4 → 9` 的总和为 23,这是所有可能路径中的最大数值。 ### 动态规划算法 #### 解决思路 1. **初始设定**:假定三角形的第0行为1个元素,第1行为2个元素,依此类推。 2. **递推关系式**:对于每一个位置 `(row, col)`,可以选择下一行的两个相邻元素之一进行相加,即 `tridata[row][col] += max(tridata[row+1][col], tridata[row+1][col+1])`。 3. **求解终点**:最终的最大值位于三角形的顶端位置,即 `tridata[0][0]`。 #### 算法步骤 1. **输入阶段**:读取用户输入的三角形高度 `n` 以及各节点的数值。 2. **计算环节**:从倒数第二行开始,逐行逐列更新每个节点的值。 3. **结果输出**:输出计算得出的最大路径和。 ### 示例代码解析 ```cpp int GetMax(int tridata[20][20], int &Num) { // 从倒数第二行开始...
内容概要:本文系统研究了同步电机与构网型变流器在低惯量电力系统中的频率稳定性问题,重点基于IEEE9节点系统构建混合拓扑结构,开展电磁暂态仿真分析。研究涵盖了构网型变流器的多种先进控制策略建模与对比,包括下垂控制、虚拟同步机控制(VSM)、匹配控制及可调度虚拟振荡器控制(dVOC),深入探讨其对系统频率响应特性的影响。同时,研究集成了基于KPCA的故障检测方法、储能系统以经济效益最大化为目标的运行优化策略,结合Matlab/Simulink平台实现了完整的仿真验证体系,为高比例新能源接入下的电力系统稳定运行提供了理论支持与技术路径。; 适合人群:具备电力系统分析、自动控制理论及新能源网技术背景的科研人员与工程技术人员,特别适用于从事微电网控制、变流器建模、频率稳定与低惯量系统研究的研究生、高校教师及电力领域研发工程师。; 使用场景及目标:①用于高校与科研机构中对构网型变流器控制策略的教学演示与仿真验证;②支撑低惯量电力系统中频率稳定问题的机理分析与解决方案设计;③为实际工程中储能配置、故障快速识别与优化运行提供仿真工具链与决策依据。; 阅读建议:建议结合文中提供的Matlab/Simulink代码资源进行动手实践,重点关注不同控制策略的模型搭建细节、参数 tuning 过程及仿真结果的动态对比分析,同时关注故障检测与储能优化模块的集成实现方式,推荐通过公众号“荔枝科研社”获取完整资料包与技术支持,以全面提升仿真复现与科研创新能力。
标题SpringBoot学生成绩信息管理系统安全设计与实现AI更换标题第1章引言介绍学生成绩信息管理系统安全设计的研究背景、意义、现状及论文方法与创新点。1.1研究背景与意义阐述学生成绩信息管理系统安全设计的重要性与必要性。1.2国内外研究现状分析国内外学生成绩信息管理系统安全设计的研究进展。1.3研究方法以及创新点概述本文的研究方法及在安全设计方面的创新点。第2章相关理论总结和评述学生成绩信息管理系统安全设计的相关理论。2.1信息安全基础理论阐述信息安全的基本概念、原则及重要性。2.2Web应用安全理论介绍Web应用安全漏洞、攻击手段及防御策略。2.3SpringBoot安全机制概述SpringBoot框架提供的安全机制及配置方法。第3章系统安全需求分析详细分析学生成绩信息管理系统的安全需求。3.1用户身份认证需求分析系统对用户身份认证的需求及安全要求。3.2数据传输安全需求阐述数据传输过程中的安全需求及加密技术。3.3数据存储安全需求介绍数据存储的安全需求及数据库安全策略。第4章系统安全设计详细介绍学生成绩信息管理系统的安全设计方案。4.1系统架构安全设计设计系统的整体架构,确保架构层面的安全性。4.2身份认证与授权设计设计用户身份认证和授权机制,确保用户合法访问。4.3数据传输与存储安全设计设计数据传输和存储的安全方案,保障数据安全性。第5章系统安全实现阐述学生成绩信息管理系统安全设计的具体实现过程。5.1开发环境与工具选择介绍系统开发所使用的环境和工具。5.2安全功能模块实现详细描述各安全功能模块的实现代码和逻辑。5.3系统测试与优化对系统进行安全测试,发现修复潜在的安全漏洞。第6章研究结果呈现系统安全设计实现后的实验分析结果。6.1系统安全性能测试结果展示系统在不同安全测试场景下的性能表现。6.2安全漏洞修复情况介绍系统安全测试中发现修复的安全漏洞情况。6.3
标题SpringBoot社区电子商务系统的设计与实现AI更换标题第1章引言介绍社区电子商务系统的发展背景、研究意义及SpringBoot技术的应用价值。1.1研究背景与意义阐述社区电子商务系统的兴起背景及其在社区服务中的重要性。1.2国内外研究现状分析国内外社区电子商务系统的研究现状及发展趋势。1.3研究方法及创新点概述本文采用的研究方法及SpringBoot技术应用的创新点。第2章相关理论总结SpringBoot框架及电子商务系统相关理论,为系统设计提供理论基础。2.1SpringBoot框架概述介绍SpringBoot框架的特点、优势及在Web开发中的应用。2.2电子商务系统理论基础阐述电子商务系统的基本概念、功能模块及关键技术。2.3社区电子商务系统特点分析社区电子商务系统的独特性,如社区化、本地化等。第3章系统需求分析对社区电子商务系统进行详细的需求分析,明确系统功能和性能要求。3.1用户需求分析分析社区用户、商家及管理员等不同角色的需求。3.2功能需求分析列举系统应具备的主要功能,如商品展示、购物车、订单管理等。3.3性能需求分析提出系统在响应时间、发处理等方面的性能要求。第4章系统设计详细介绍社区电子商务系统的设计方案,包括架构设计、数据库设计等。4.1系统架构设计给出系统的整体架构,包括前端、后端及数据库等组成部分。4.2数据库设计设计系统的数据库结构,包括表结构、字段定义及关系等。4.3模块设计详细介绍各个功能模块的设计思路及实现方法。第5章系统实现与测试阐述社区电子商务系统的实现过程,进行系统测试以验证功能。5.1系统开发环境介绍系统开发所使用的软件、硬件环境及开发工具。5.2系统实现过程按照模块划分,详细介绍系统的实现步骤及关键代码。5.3系统测试与优化对系统进行功能测试、性能测试,根据测试结果进行优化。第6章结论与展望总结社区电子商务系统的设计与
内容概要:本文档系统性地整理了基于Matlab/Simulink平台的水声网络(UAN)信道建模及其他多个科研领域的仿真代码资源,重点聚焦于水声通信环境中信道特性的建模与仿真,涵盖声波传播的衰减、时延、多径效应等关键因素。资源不仅包含水声信道建模,还广泛涉及智能优化算法、机器学习、路径规划、电力系统、信号处理、无人机控制、雷达追踪、图像处理等多个前沿科研方向,提供大量可用于学术复现、算法验证与性能评估的Matlab代码实例。文档强调科研过程中“借力”成熟工具与创新思维相结合的重要性,引导研究者高效开展仿真工作。; 适合人群:具备Matlab编程基础,从事通信工程、海洋工程、电子信息、自动化、电力系统及相关交叉学科的研究生、科研人员及工程技术人员,尤其适合研究水声通信、无线传感网络、智能优化算法应用等方向的学者; 使用场景及目标:① 学习复现水声网络中的信道建模方法,深入理解水下声学传播特性及其对通信系统的影响;② 利用所提供的丰富代码资源进行学术论文复现、课题仿真验证、算法优化与性能对比;③ 借助跨领域案例拓展研究视野,提升科研效率,支撑学位论文撰写、项目申报及高水平期刊投稿; 阅读建议:建议读者结合文档提供的网盘资源,优先关注与水声通信和信道建模相关的代码示例,按照研究方向分类学习;同时可参考其他领域的仿真案例以激发创新思路,注意代码的参数配置与可移植性,结合理论模型进行仿真调试与结果分析,实现理论与实践的深度融合。
打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 IT 行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰,但是,我们要清楚:淘汰的永远只是那些初级水平的从业者,过硬技术的从业者永远都是稀缺的。 因此对于学习,我们还是要踏踏实实的。 自学 Python ,也是一样,不要一开始因为头脑发热就不停地收藏各种资料网站,购买各种书籍,下载了大量的教学视频,过了几天,学习的热情开始褪去,再过几个星期,终于完成了学习课程 —— 《从入门到放弃》。 所以,学习 Python 需要一步一个脚印,踏踏实实地学。 本教程基于 Python 3.10+ 编写,部分章节标注了 3.11/3.12/3.13 的新特性。 两个在线站点任选: - 互动版(推荐零基础):https://learn-py.org —— 浏览器里直接写代码、自动判分、做对一题解锁下一题,跟着练就能上手 - 文档版(推荐系统通读 / 速查):https://walter201230..io/Python/ —— 纯阅读形态,章节齐全可整本翻,手机也好看 2026 更新说明 2026 年是 AI 编程的普及年,在这一年,我深切感受到 AI 编程的强大,以前我们手敲代码的年代估计一去不复返,现在手敲代码还被互联网称为古法编程,短短几年,变化如此的快,我是完全没想到的。 AI 时代,我以为这个课程不会再有人看了,但没想到这个教程依然每天保持着稳定的增长,于是我用 AI 把我这个项目全面更新了一下。 更新完了全部教程内容之后,我用 AI 做了一个网站,更方便大家学习,不过为了省成本,我这个是静态网站,该有的功能都有,该有的体验也有,只是没法做到跨设备保存你学习的记录...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值