Flutter 通用按钮组件 CommonButtonWidget:多样式 + 多状态 + 交互优化

在 Flutter 开发中,按钮是交互的核心载体(提交、取消、操作、跳转)。原生ElevatedButton/OutlinedButton/TextButton存在样式配置繁琐、状态管理分散(加载、禁用、点击态)、交互反馈单一等问题。

本文封装的CommonButtonWidget 通用按钮组件,整合 "多样式(纯色 / 渐变 / 边框 / 文字)+ 多状态(加载 / 禁用 / 点击态)+ 交互优化(防抖 / 长按 / 水波纹)+ 全样式自定义" 四大核心能力,一行代码调用,覆盖 95%+ 按钮使用场景。

一、核心优势

核心能力 解决痛点 核心价值
🎨 多样式自由切换 不同样式按钮需重复封装 支持纯色 / 渐变 / 边框 / 文字 4 种基础样式,参数一键切换,无需重复写布局
🚦 多状态智能适配 加载 / 禁用 / 点击态需手动判断 内置加载中、禁用、点击态、长按态样式,状态联动自动适配
⚡ 交互体验优化 快速点击重复触发、反馈不友好 内置防抖、长按回调、水波纹反馈,符合移动端交互规范
🛠️ 样式全自定义 原生按钮样式定制繁琐 圆角、高度、内边距、文本样式、加载动画均可配置,支持图标 + 文本组合
📱 适配性强 深色模式 / 全面屏适配复杂 自动适配深色模式,支持自定义水波纹颜色,点击区域符合人机规范
🎯 极简调用 原生按钮参数多、配置复杂 一行代码调用,默认配置覆盖 80% 场景,自定义配置灵活扩展

二、核心配置速览

配置分类 核心参数 类型 默认值 核心作用
必选配置 text String -(必传) 按钮文本
onTap VoidCallback -(必传) 点击回调
样式配置 buttonType ButtonType ButtonType.solid 按钮类型(纯色 / 渐变 / 边框 / 文字)
bgColor Color Colors.blue 纯色按钮背景色
gradient Gradient? null 渐变按钮渐变(优先级高于 bgColor)
borderColor Color Colors.blue 边框 / 文字按钮边框 / 文本色
radius double 8.0 按钮圆角
height double 48.0 按钮高度(建议≥44px)
textStyle TextStyle 16 号白色粗体 文本样式
状态配置 isLoading bool false 是否加载中(自动禁用点击)
isDisabled bool false 是否禁用
loadingText String "加载中..." 加载中文本
loadingSize double 20.0 加载图标大小
交互配置 debounceDuration Duration 300ms 防抖时长
onLongPress VoidCallback? null 长按回调
splashColor Color? null 水波纹颜色
expand bool true 是否宽度占满
扩展配置 prefixIcon/suffixIcon Widget? null 前缀 / 后缀图标
iconSize double 24.0 图标大小
adaptDarkMode bool true 深色模式适配

三、完整代码(可直接复制使用)

dart

复制代码
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';

/// 按钮类型枚举
enum ButtonType {
  solid,     // 纯色按钮
  gradient,  // 渐变按钮
  outline,   // 边框按钮
  text       // 文字按钮
}

/// 通用按钮组件
class CommonButtonWidget extends StatefulWidget {
  // 必选参数
  final String text;          // 按钮文本
  final VoidCallback onTap;   // 点击回调

  // 样式配置
  final ButtonType buttonType; // 按钮类型(默认纯色)
  final Color bgColor;         // 背景色(纯色按钮)
  final Gradient? gradient;    // 渐变(渐变按钮,优先级高于bgColor)
  final Color borderColor;     // 边框色(边框/文字按钮)
  final double borderWidth;    // 边框宽度(默认1px)
  final double radius;         // 圆角(默认8px)
  final double height;         // 按钮高度(默认48px)
  final EdgeInsetsGeometry padding; // 内边距(默认水平16px)
  final TextStyle textStyle;   // 文本样式
  final Color disabledColor;   // 禁用背景色
  final Color disabledTextColor; // 禁用文本色

  // 状态配置
  final bool isLoading;        // 是否加载中(默认false)
  final bool isDisabled;       // 是否禁用(默认false)
  final String loadingText;    // 加载中文本(默认"加载中...")
  final double loadingSize;    // 加载图标大小(默认20px)
  final Color loadingColor;    // 加载图标颜色

  // 交互配置
  final Duration debounceDuration; // 防抖时长(默认300ms)
  final VoidCallback? onLongPress; // 长按回调
  final Color? splashColor;    // 水波纹颜色
  final bool enableFeedback;   // 是否开启点击反馈(默认true)
  final bool expand;           // 是否宽度占满(默认true)

  // 扩展配置
  final Widget? prefixIcon;    // 前缀图标
  final Widget? suffixIcon;    // 后缀图标
  final double iconSize;       // 图标大小(默认24px)
  final double iconTextSpacing; // 图标与文本间距(默认8px)
  final bool adaptDarkMode;    // 适配深色模式(默认true)

  const CommonButtonWidget({
    super.key,
    required this.text,
    required this.onTap,
    // 样式配置
    this.buttonType = ButtonType.solid,
    this.bgColor = Colors.blue,
    this.gradient,
    this.borderColor = Colors.blue,
    this.borderWidth = 1.0,
    this.radius = 8.0,
    this.height = 48.0,
    this.padding = const EdgeInsets.symmetric(horizontal: 16),
    this.textStyle = const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w500),
    this.disabledColor = const Color(0xFFE0E0E0),
    this.disabledTextColor = const Color(0xFF999999),
    // 状态配置
    this.isLoading = false,
    this.isDisabled = false,
    this.loadingText = "加载中...",
    this.loadingSize = 20.0,
    this.loadingColor = Colors.white,
    // 交互配置
    this.debounceDuration = const Duration(milliseconds: 300),
    this.onLongPress,
    this.splashColor,
    this.enableFeedback = true,
    this.expand = true,
    // 扩展配置
    this.prefixIcon,
    this.suffixIcon,
    this.iconSize = 24.0,
    this.iconTextSpacing = 8.0,
    this.adaptDarkMode = true,
  });

  @override
  State<CommonButtonWidget> createState() => _CommonButtonWidgetState();
}

class _CommonButtonWidgetState extends State<CommonButtonWidget> {
  bool _isClicking = false; // 防抖标记

  /// 深色模式颜色适配
  Color _adaptDarkMode(Color lightColor, Color darkColor) {
    if (!widget.adaptDarkMode) return lightColor;
    return MediaQuery.platformBrightnessOf(context) == Brightness.dark
        ? darkColor
        : lightColor;
  }

  /// 防抖点击处理(带异常捕获)
  Future<void> _handleTap() async {
    // 防抖/加载/禁用状态拦截
    if (_isClicking || widget.isLoading || widget.isDisabled) return;

    _isClicking = true;
    try {
      widget.onTap(); // 执行点击回调
    } catch (e) {
      // 全局异常捕获,避免按钮点击崩溃
      EasyLoading.showError("操作失败:${e.toString()}");
      debugPrint("按钮点击异常:$e");
    } finally {
      // 防抖延迟后重置标记
      await Future.delayed(widget.debounceDuration);
      if (mounted) {
        setState(() => _isClicking = false);
      }
    }
  }

  /// 构建按钮背景/边框装饰
  Decoration? _buildDecoration() {
    final isDisabled = widget.isDisabled || widget.isLoading;
    // 基础颜色适配(深色模式+禁用状态)
    Color bgColor = _adaptDarkMode(widget.bgColor, Colors.blueAccent);
    Color borderColor = _adaptDarkMode(widget.borderColor, Colors.blueAccent);

    // 禁用状态颜色覆盖
    if (isDisabled) {
      bgColor = _adaptDarkMode(widget.disabledColor, const Color(0xFF444444));
      borderColor = _adaptDarkMode(widget.disabledColor, const Color(0xFF555555));
    }

    switch (widget.buttonType) {
      case ButtonType.solid:
        return BoxDecoration(
          color: bgColor,
          borderRadius: BorderRadius.circular(widget.radius),
        );
      case ButtonType.gradient:
        return BoxDecoration(
          gradient: widget.gradient ?? LinearGradient(
            colors: [bgColor, bgColor.withOpacity(0.8)],
            begin: Alignment.centerLeft,
            end: Alignment.centerRight,
          ),
          borderRadius: BorderRadius.circular(widget.radius),
        );
      case ButtonType.outline:
        return BoxDecoration(
          border: Border.all(
            color: borderColor,
            width: isDisabled ? widget.borderWidth * 0.5 : widget.borderWidth,
          ),
          borderRadius: BorderRadius.circular(widget.radius),
          color: Colors.transparent,
        );
      case ButtonType.text:
        return null; // 文字按钮无装饰
    }
  }

  /// 构建按钮文本样式(适配状态+深色模式)
  TextStyle _buildTextStyle() {
    final isDisabled = widget.isDisabled || widget.isLoading;
    Color textColor = widget.textStyle.color ?? Colors.white;

    // 边框/文字按钮默认文本色适配
    if (widget.buttonType == ButtonType.outline || widget.buttonType == ButtonType.text) {
      textColor = _adaptDarkMode(widget.borderColor, Colors.blueAccent);
    }

    // 禁用状态文本色覆盖
    if (isDisabled) {
      textColor = _adaptDarkMode(widget.disabledTextColor, const Color(0xFF777777));
    }

    // 最终深色模式适配
    textColor = _adaptDarkMode(textColor, widget.textStyle.color ?? Colors.white70);

    return widget.textStyle.copyWith(
      color: textColor,
      fontSize: widget.textStyle.fontSize ?? 16,
      fontWeight: widget.textStyle.fontWeight ?? FontWeight.w500,
      decoration: isDisabled ? TextDecoration.none : widget.textStyle.decoration,
    );
  }

  /// 构建按钮内容(图标+文本+加载动画组合)
  Widget _buildButtonContent() {
    final isLoading = widget.isLoading;
    final displayText = isLoading ? widget.loadingText : widget.text;
    final loadingColor = _adaptDarkMode(widget.loadingColor, Colors.white70);

    List<Widget> contentWidgets = [];

    // 加载中图标(优先级最高)
    if (isLoading) {
      contentWidgets.add(
        SizedBox(
          width: widget.loadingSize,
          height: widget.loadingSize,
          child: CircularProgressIndicator(
            strokeWidth: 2,
            color: loadingColor,
            valueColor: AlwaysStoppedAnimation<Color>(loadingColor),
          ),
        ),
      );
      if (displayText.isNotEmpty) {
        contentWidgets.add(SizedBox(width: widget.iconTextSpacing));
      }
    } else {
      // 前缀图标(非加载状态显示)
      if (widget.prefixIcon != null) {
        contentWidgets.add(
          SizedBox(
            width: widget.iconSize,
            height: widget.iconSize,
            child: widget.prefixIcon,
          ),
        );
        contentWidgets.add(SizedBox(width: widget.iconTextSpacing));
      }
    }

    // 按钮文本(支持空文本)
    if (displayText.isNotEmpty) {
      contentWidgets.add(
        Expanded(
          child: Text(
            displayText,
            style: _buildTextStyle(),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            textAlign: TextAlign.center,
          ),
        ),
      );
    }

    // 后缀图标(非加载状态显示)
    if (!isLoading && widget.suffixIcon != null) {
      contentWidgets.add(SizedBox(width: widget.iconTextSpacing));
      contentWidgets.add(
        SizedBox(
          width: widget.iconSize,
          height: widget.iconSize,
          child: widget.suffixIcon,
        ),
      );
    }

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisSize: MainAxisSize.min,
      children: contentWidgets,
    );
  }

  @override
  Widget build(BuildContext context) {
    final isDisabled = widget.isDisabled || widget.isLoading;
    // 水波纹颜色适配(默认使用背景色半透明)
    final splashColor = widget.splashColor ?? _adaptDarkMode(
      widget.bgColor.withOpacity(0.2),
      Colors.blueAccent.withOpacity(0.3),
    );

    // 基础按钮容器(统一布局)
    Widget buttonContainer = Container(
      width: widget.expand ? double.infinity : null,
      height: widget.height,
      padding: widget.padding,
      decoration: _buildDecoration(),
      alignment: Alignment.center,
      // 点击区域扩大(最小44x44,符合iOS人机规范)
      constraints: BoxConstraints(
        minWidth: 44,
        minHeight: 44,
        maxHeight: widget.height,
      ),
      child: _buildButtonContent(),
    );

    // 交互层封装(区分水波纹/纯点击)
    Widget button;
    if (widget.buttonType != ButtonType.text && !isDisabled) {
      // 带水波纹的点击(InkWell)
      button = InkWell(
        onTap: _handleTap,
        onLongPress: isDisabled ? null : widget.onLongPress,
        splashColor: splashColor,
        highlightColor: Colors.transparent, // 去除高亮色,仅保留水波纹
        borderRadius: BorderRadius.circular(widget.radius),
        enableFeedback: widget.enableFeedback,
        child: buttonContainer,
      );
    } else {
      // 纯点击(GestureDetector)
      button = GestureDetector(
        onTap: _handleTap,
        onLongPress: isDisabled ? null : widget.onLongPress,
        behavior: HitTestBehavior.opaque,
        enableFeedback: widget.enableFeedback && !isDisabled,
        child: buttonContainer,
      );
    }

    // 禁用状态透明度处理
    if (isDisabled) {
      button = Opacity(
        opacity: 0.6,
        child: button,
      );
    }

    return button;
  }
}

// pubspec.yaml依赖(如需使用EasyLoading)
/*
dependencies:
  flutter:
    sdk: flutter
  flutter_easyloading: ^3.0.5
*/

四、四大高频场景示例

场景 1:提交按钮(纯色 + 加载态)

适用场景:表单提交、数据保存,需显示加载状态并禁用重复点击

dart

复制代码
class SubmitButtonDemo extends StatefulWidget {
  @override
  State<SubmitButtonDemo> createState() => _SubmitButtonDemoState();
}

class _SubmitButtonDemoState extends State<SubmitButtonDemo> {
  bool _isSubmitting = false;

  // 模拟表单提交逻辑
  Future<void> _submitForm() async {
    setState(() => _isSubmitting = true);
    try {
      // 模拟接口请求(2秒)
      await Future.delayed(const Duration(seconds: 2));
      EasyLoading.showSuccess("提交成功!");
    } catch (e) {
      EasyLoading.showError("提交失败:$e");
    } finally {
      if (mounted) {
        setState(() => _isSubmitting = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("提交按钮示例")),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 48),
        child: CommonButtonWidget(
          text: "提交表单",
          onTap: _submitForm,
          buttonType: ButtonType.solid,
          bgColor: Colors.green,       // 成功色
          radius: 24,                  // 大圆角
          height: 52,                  // 加高按钮
          isLoading: _isSubmitting,    // 加载状态联动
          loadingText: "提交中...",     // 加载文本
          loadingColor: Colors.white,  // 加载图标颜色
          disabledColor: Colors.grey[300]!, // 禁用背景色
          debounceDuration: const Duration(milliseconds: 500), // 加长防抖
        ),
      ),
    );
  }
}

场景 2:渐变按钮(图标 + 文本)

适用场景:支付、主要操作按钮,需视觉突出并带图标增强识别

dart

复制代码
class GradientButtonDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("渐变按钮示例")),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 48),
        child: CommonButtonWidget(
          text: "立即支付",
          onTap: () => EasyLoading.showToast("支付功能已触发"),
          buttonType: ButtonType.gradient,
          // 橙红渐变
          gradient: const LinearGradient(
            colors: [Colors.orange, Colors.redAccent],
            begin: Alignment.centerLeft,
            end: Alignment.centerRight,
          ),
          radius: 8,
          height: 48,
          prefixIcon: const Icon(Icons.payment, color: Colors.white), // 支付图标
          iconSize: 20,                // 小图标
          iconTextSpacing: 10,         // 加大图标间距
          textStyle: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
          ),
          splashColor: Colors.white.withOpacity(0.2), // 白色水波纹
        ),
      ),
    );
  }
}

场景 3:边框按钮(取消 / 确认组合)

适用场景:弹窗操作、列表操作,需区分主次按钮

dart

复制代码
class OutlineButtonDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("边框按钮示例")),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
        child: Row(
          children: [
            // 取消按钮(边框样式)
            Expanded(
              child: CommonButtonWidget(
                text: "取消",
                onTap: () => Navigator.pop(context),
                buttonType: ButtonType.outline,
                borderColor: Colors.grey[500]!,
                borderWidth: 1.0,
                radius: 4,
                height: 44,
                textStyle: const TextStyle(color: Colors.grey[700]),
              ),
            ),
            const SizedBox(width: 16),
            // 确认按钮(纯色样式)
            Expanded(
              child: CommonButtonWidget(
                text: "确认",
                onTap: () => EasyLoading.showToast("确认操作已触发"),
                buttonType: ButtonType.solid,
                bgColor: Colors.blue,
                radius: 4,
                height: 44,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

场景 4:文字按钮(辅助操作)

适用场景:找回密码、查看更多、辅助说明,需轻量样式

dart

复制代码
class TextButtonDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("文字按钮示例")),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 48),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const Text("忘记密码?", style: TextStyle(fontSize: 16)),
            const SizedBox(height: 8),
            CommonButtonWidget(
              text: "点击找回密码",
              onTap: () => EasyLoading.showToast("跳转到找回密码页面"),
              buttonType: ButtonType.text,
              borderColor: Colors.blue,       // 文本色继承borderColor
              radius: 0,                     // 无圆角
              height: 36,                    // 矮按钮
              textStyle: const TextStyle(
                color: Colors.blue,
                fontSize: 14,
                decoration: TextDecoration.underline, // 下划线
              ),
              suffixIcon: const Icon(        // 右侧箭头
                Icons.arrow_forward_ios,
                color: Colors.blue,
                size: 16,
              ),
              iconSize: 16,
              expand: false,                 // 宽度自适应
            ),
          ],
        ),
      ),
    );
  }
}

五、核心封装技巧

1. 多样式统一封装

通过ButtonType枚举切换 4 种按钮样式,核心逻辑复用:

  • 纯色按钮:BoxDecoration设置背景色
  • 渐变按钮:优先使用gradient,无渐变则降级为纯色
  • 边框按钮:透明背景 + 边框
  • 文字按钮:无装饰,仅文本样式避免为每种按钮单独封装组件,减少重复代码。

2. 防抖逻辑内置

  • 通过_isClicking标记实现防抖,避免快速重复点击
  • 防抖时长可配置(默认 300ms),适配不同场景
  • finally块确保标记重置,避免按钮永久禁用
  • 异常捕获:包裹onTap回调,避免点击逻辑崩溃导致按钮卡死

3. 状态联动适配

  • isLoading/isDisabled自动禁用点击,无需外部判断
  • 禁用状态自动调整颜色、透明度、交互反馈
  • 加载状态自动显示加载动画,隐藏图标,替换文本
  • 状态变更时 UI 自动刷新,无需手动调用setState

4. 内容灵活组合

  • 支持前缀 / 后缀图标、加载动画与文本的自由组合
  • 加载状态优先级最高,自动隐藏图标
  • 文本支持单行省略,适配长文本场景
  • 图标大小、间距可配置,满足不同布局需求

5. 交互体验优化

  • 水波纹优化:仅非文字按钮显示,自定义颜色,去除高亮色
  • 点击反馈:enableFeedback控制震动反馈,禁用状态自动关闭
  • 点击区域扩大:最小 44x44,符合 iOS 人机交互规范
  • 长按回调:支持长按操作,禁用状态自动失效

六、避坑指南

1. 防抖时长适配

  • 建议值:200-500ms(过短无法防抖,过长影响响应)
  • 表单提交 / 支付等关键操作:500ms
  • 普通操作 / 页面跳转:200-300ms
  • 禁用防抖:设置debounceDuration: Duration.zero

2. 加载状态交互

  • isLoading为 true 时,自动禁用点击,无需额外设置isDisabled
  • 加载文本建议简短(如 "加载中..."),避免文本溢出
  • 加载图标大小建议 16-24px,过大会导致布局变形

3. 深色模式兼容

  • 自定义颜色需通过_adaptDarkMode方法适配,避免深色模式下颜色冲突
  • 水波纹颜色默认适配深色模式,无需单独配置
  • 禁用状态颜色需同时适配浅色 / 深色模式

4. 样式优先级

  • 渐变按钮中gradient优先级高于bgColor,设置渐变后bgColor仅作为降级
  • 文本颜色优先级:禁用状态 > 按钮类型默认 > 自定义textStyle
  • 边框宽度在禁用状态下自动减半,增强视觉区分

5. 点击区域规范

  • 按钮高度建议≥44px(符合移动端交互规范)
  • expand: false时,按钮宽度自适应,需确保最小点击区域
  • constraints确保最小 44x44 点击区域,适配小按钮场景

6. 水波纹注意事项

  • 文字按钮默认关闭水波纹,如需开启需自定义InkWell
  • 水波纹颜色需与背景色对比明显,增强反馈
  • InkWell需有背景色父容器,否则水波纹可能不显示

七、扩展能力(按需定制)

1. 添加点击动画

dart

复制代码
// 在_buildButtonContent外层添加缩放动画
Widget _buildButtonContent() {
  return AnimatedScale(
    scale: _isClicking ? 0.98 : 1.0,
    duration: const Duration(milliseconds: 100),
    child: Row(/* 原有内容 */),
  );
}

2. 支持自定义加载组件

dart

复制代码
// 添加配置参数
final Widget? customLoadingWidget;

// 在_buildButtonContent中替换加载图标
if (isLoading) {
  contentWidgets.add(
    widget.customLoadingWidget ?? 
    SizedBox(/* 原有加载图标 */)
  );
}

3. 支持圆角为圆形

dart

复制代码
// 使用BorderRadius.circular(widget.radius == double.infinity ? widget.height/2 : widget.radius)
// 调用时设置radius: double.infinity即可实现圆形按钮
CommonButtonWidget(
  text: "圆形按钮",
  onTap: () {},
  radius: double.infinity,
  height: 48,
  expand: false,
)

八、总结

CommonButtonWidget 组件解决了原生按钮样式定制繁琐、状态管理复杂、交互体验差的问题,通过统一封装实现了多样式、多状态、高自定义的按钮组件。

组件具备以下特性:

  • 🎨 4 种基础样式,一键切换
  • 🚦 加载 / 禁用 / 点击态自动适配
  • ⚡ 内置防抖、长按、水波纹优化
  • 🛠️ 全样式自定义,满足品牌需求
  • 📱 适配深色模式、全面屏、人机规范

一行代码即可调用,覆盖表单提交、支付、弹窗操作、辅助说明等 95%+ 按钮场景,大幅提升开发效率和用户体验。

欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。

相关推荐
豆苗学前端8 小时前
Vue 2 vs Vue 3 响应式原理深度对比(源码理解层面,吊打面试官)
前端·javascript·面试
TimelessHaze8 小时前
算法复杂度分析与优化:从理论到实战
前端·javascript·算法
叫我詹躲躲8 小时前
为什么永远不要让前端直接连接数据库
javascript·mysql
晚霞的不甘8 小时前
实战前瞻:构建高可用、强实时的 Flutter + OpenHarmony 智慧医疗健康平台
前端·javascript·flutter
小兔崽子去哪了8 小时前
文件上传专题
java·javascript
Aevget8 小时前
DevExtreme JS & ASP.NET Core v25.2预览 - DataGrid/TreeList全新升级
开发语言·javascript·asp.net·界面控件·ui开发·devextreme
芳草萋萋鹦鹉洲哦8 小时前
【elementUI】form表单rules没生效
前端·javascript·elementui
余生H8 小时前
反向代理与 Forwarded 相关 Header 深度解析
javascript·nginx·http
呆子罗8 小时前
原生JS请求API
开发语言·javascript·ecmascript