普通视图

发现新文章,点击刷新页面。
昨天以前首页

赋能工业 / 商业 / 公共机构:开源 MyEMS,让能源管理 “人人可及”

2025年10月22日 15:31

在 “双碳” 目标推进与能源成本持续攀升的背景下,能源管理已从 “可选动作” 变为工业、商业、公共机构的 “必答题”。然而,传统能源管理系统往往面临 “高成本门槛”“定制化难”“技术依赖强” 三大困境 —— 工业企业需应对多产线能耗数据孤岛,商业场所受限于运营成本难以部署复杂系统,公共机构则因预算有限难以获取专业技术支持。开源能源管理系统 MyEMS 的出现,正以 “零授权成本”“全流程可控”“社区化协作” 的特性,打破能源管理的技术与成本壁垒,让高效能源管理真正 “人人可及”。

一、破局痛点:三大领域的能源管理困境

从车间流水线到商场中央空调,从学校教学楼到医院配电系统,不同场景的能源消耗逻辑差异显著,但核心痛点高度趋同:

  • 工业领域: 多设备、多产线的能耗数据分散在 PLC、传感器等不同终端,传统系统需高额接口费用才能整合数据,且难以根据生产计划动态调整节能策略,导致 “能耗监测滞后、节能方案僵化”;部分中小型制造企业因年授权费超 10 万元,被迫放弃系统化管理,陷入 “浪费难察觉、优化无方向” 的被动局面。
  • 商业领域: 商场、写字楼的空调、照明、电梯能耗占比超 70%,但传统系统多依赖固定阈值控制(如统一设定 26℃空调温度),无法根据人流变化、日照强度动态调节,造成 “高峰时段能耗过载、低谷时段能源空耗”;同时,商业运营方对系统响应速度要求高,传统厂商的定制化周期常达 3-6 个月,难以匹配商业场景的灵活需求。
  • 公共机构: 学校、医院、政务大厅等场景的能源管理预算有限,且多需对接财政监管系统,传统商业系统不仅授权费用高,还存在 “数据不透明、对接难度大” 问题 —— 某县级医院曾因系统无法与地方能耗监管平台兼容,导致半年能耗数据无法上报,影响绿色机构评级。

二、精准赋能:MyEMS 的场景化解决方案

作为专注于能源管理的开源系统,MyEMS 依托 “数据采集 - 分析优化 - 智能控制 - 报表输出” 的全流程功能,为三大领域提供 “低成本适配、高灵活定制” 的解决方案:

  • 工业场景:聚焦 “产耗协同”

MyEMS 支持对接 Modbus、OPC UA 等工业通用协议,无需额外付费即可整合产线设备、电表、水表等终端数据,实时生成 “设备能耗看板”“产线能耗对比图”。针对某中型汽车零部件厂的需求,技术团队基于 MyEMS 开源代码,仅用 2 周就开发出 “生产负荷 - 能耗联动模块”—— 当产线处于半负荷状态时,系统自动下调非核心设备功率,半年内实现能耗降低 13%,节省成本超 20 万元。

  • 商业场景:主打 “动态节能”

在商场场景中,MyEMS 可接入人流统计摄像头、光照传感器数据,构建 “人流密度 - 空调负荷 - 照明亮度” 联动模型。例如,当商场某区域人流低于阈值时,系统自动关闭 1/3 照明回路,并将空调温度上调 1-2℃;通过分析历史数据,还能预测周末、节假日的能耗高峰,提前制定预调节策略。某连锁超市引入 MyEMS 后,门店月度能耗平均下降 8%,空调电费节省尤为显著。

  • 公共机构:侧重 “合规与低成本”

针对公共机构的预算限制,MyEMS 提供 “零授权费 + 轻量化部署” 方案 —— 无需购置昂贵服务器,可依托现有办公电脑搭建基础管理平台;同时,系统内置国家《公共机构能源资源计量器具配备和管理要求》等合规模板,自动生成符合监管要求的能耗报表。某地级市教育局通过 MyEMS 整合辖区 50 所学校的能耗数据,不仅实现数据实时上报,还通过 “错峰用电提醒”“老旧灯具替换建议”,推动年度总能耗降低 9%,且整体部署成本不足万元。

三、开源之力:让 “人人可及” 落地生根

MyEMS 之所以能打破能源管理的壁垒,核心在于 “开源” 模式赋予的三大优势:

  • 成本可及:零门槛入门

与传统系统年均 5-20 万元的授权费不同,MyEMS 的核心代码完全开源,企业、机构可免费下载使用,仅需承担少量技术开发或运维成本。对于技术能力有限的中小组织,社区还提供 “基础部署指南”“常见问题手册”,甚至有志愿者团队提供免费技术咨询,彻底消除 “因成本望而却步” 的问题。

  • 技术可及:全流程可控

开源意味着代码透明 —— 用户可根据自身需求修改功能模块,无需依赖厂商定制。例如,某化工企业为满足防爆区域的特殊要求,基于 MyEMS 代码优化了数据传输加密协议;某高校则在系统中增加 “学生宿舍用电安全预警” 功能,当检测到违规电器接入时自动断电。这种 “自主可控” 的特性,让不同场景的个性化需求得以快速满足。

  • 生态可及:社区化共成长

MyEMS 拥有活跃的开发者社区,涵盖能源管理专家、IT 工程师、企业用户等群体。社区定期举办线上分享会,交流 “工业节能最佳实践”“商业楼宇能耗优化技巧”;用户遇到技术问题时,通常 24 小时内就能获得社区响应。这种 “共享共建” 的生态,让中小机构也能获取顶尖的能源管理经验,避免 “单打独斗” 的技术困境。

四、实践见证:从 “能用” 到 “好用” 的跨越

在江苏某机械制造企业,MyEMS 的落地经历颇具代表性:此前,该企业使用传统系统,每年授权费 15 万元,但仅能实现基础能耗统计;引入 MyEMS 后,技术团队用 1 个月完成代码二次开发,新增 “设备能耗异常预警” 功能 —— 当某台机床能耗突然超出历史均值 15% 时,系统立即推送提醒,帮助企业及时发现并修复了设备故障,避免了因停机造成的 10 万元损失。一年后,企业能耗降低 18%,综合成本节省超 40 万元。

类似的案例正在各地涌现:浙江某商场通过 MyEMS 实现空调能耗动态调节,夏季电费每月减少 3 万元;某县级政务中心依托 MyEMS 完成能耗数据与省级监管平台对接,一次性通过绿色公共机构认证…… 这些实践证明,开源 MyEMS 不仅让能源管理 “能用”,更能 “好用”“管用”。

五、结语:开源模式加速能源管理普惠化

能源管理的本质,是让每一度电、每一方水都用在 “刀刃上”。过去,技术壁垒与成本门槛让许多组织错失了高效管理的机会;而 MyEMS 的开源之路,正以 “去中心化”“低成本”“高灵活” 的特性,将能源管理的主动权交还给用户 —— 无论是千人规模的工厂,还是几十人的社区医院,都能借助开源力量搭建适配自身的系统。

当能源管理不再是 “少数企业的专利”,而是 “人人可及的工具”,不仅能帮助更多组织降本增效,更能汇聚起千万个 “节能单元” 的力量,为 “双碳” 目标的实现注入源源不断的动力。开源 MyEMS 的探索,或许正是能源管理行业从 “精英化” 走向 “普惠化” 的重要一步。

开源能源管理系统 MyEMS:赋能企业降本增效,加速能源数字化转型

2025年10月22日 15:26

在 “双碳” 目标与能源成本攀升的双重驱动下,企业对能源管理的需求从 “被动统计” 转向 “主动优化”。而开源能源管理系统 MyEMS(My Energy Management System)凭借零授权成本、高度可定制、社区协同迭代的优势,成为中小企业及创新团队实现能源数字化管理的 “性价比之选”。它并非简单的能耗数据记录工具,而是一套覆盖 “数据采集 - 监控分析 - 节能优化 - 报表输出” 全流程的开源解决方案,可灵活适配工业、商业、公共建筑等多场景能源管理需求。

一、MyEMS 的核心架构:轻量化设计,兼容多源数据

作为开源系统,MyEMS 的架构设计以 “低门槛部署、高兼容性” 为核心,采用模块化拆分,既便于技术团队快速上手,也能根据业务需求灵活扩展。其核心架构分为四层,各层职责清晰且协同高效:

1. 数据采集层:打通 “能源数据孤岛”

这是 MyEMS 的 “感知神经”,负责从各类能源计量设备、控制系统中抓取实时数据,支持工业与商业场景中主流的通信协议与设备类型:

  • 协议兼容: 覆盖 Modbus RTU/TCP(常见于电表、水表、燃气表)、MQTT(物联网传感器常用协议)、OPC UA(工业控制系统标准协议)、BACnet(楼宇自控系统协议)等,无需额外开发驱动,即可对接智能电表、PLC(可编程逻辑控制器)、中央空调控制器、光伏逆变器等设备;
  • 数据预处理: 对采集到的原始数据(如电压、电流、功率、能耗值)进行清洗 —— 剔除异常值(如设备故障导致的跳变数据)、补全缺失值(通过插值算法修复短时断连数据),确保数据准确性,为后续分析奠定基础;
  • 边缘部署支持: 可在边缘网关(如树莓派、工业边缘计算机)上部署采集模块,在工厂、园区等网络复杂场景中,实现本地数据暂存与预处理,避免因网络延迟导致的数据丢失。

2. 数据存储层:兼顾实时性与历史数据查询

针对能源数据 “高频实时监控 + 长期历史分析” 的双重需求,MyEMS 采用 “时序数据库 + 关系型数据库” 的混合存储方案:

  • 时序数据库(InfluxDB/TimescaleDB): 存储高频实时数据(如每 15 秒一次的设备功率数据),优势在于高写入性能与时间维度查询效率,支持快速生成 “近 24 小时能耗趋势图”“设备实时功率曲线”;
  • 关系型数据库(PostgreSQL/MySQL): 存储静态配置数据(如设备档案、用户权限、能源类型定义)与汇总数据(如每日 / 每月能耗统计结果),满足结构化数据的管理与关联查询需求;
  • 数据生命周期管理: 支持自定义数据保留策略,例如 “实时数据保留 3 个月,汇总数据保留 5 年”,避免存储资源浪费。

3. 业务逻辑层:核心功能的 “中枢大脑”

这一层是 MyEMS 的核心,通过模块化设计实现能源管理的关键业务逻辑,开发者可基于开源代码二次开发,适配个性化需求:

  • 能耗统计模块: 按 “能源类型(电 / 水 / 气 / 热)、区域(车间 / 楼层)、设备类型(生产设备 / 空调)、时间维度(时 / 日 / 月 / 年)” 多维度拆分能耗数据,自动计算单位产品能耗、人均能耗等关键指标;
  • 节能分析模块: 通过 “基准能耗对比”(如与历史同期、行业标杆对比)、“能耗异常诊断”(如某设备能耗突增 20% 时触发分析),定位节能潜力点(如低效运行的老旧设备、不合理的空调运行时间);
  • 权限管理模块: 支持 RBAC(基于角色的访问控制),可设置 “管理员(全权限)、能源专员(数据查看与报表生成)、设备操作员(仅查看所属设备数据)” 等角色,保障数据安全。

4. 应用展示层:直观化交互,适配多终端

MyEMS 提供 Web 端与移动端(基于响应式设计)两种展示方式,聚焦 “数据可视化” 与 “操作便捷性”:

  • 实时监控看板: 以仪表盘、折线图、柱状图、地图等形式,实时展示各区域 / 设备的能耗数据、设备运行状态(如 “空调运行中”“电表离线”),异常数据以红色预警标识;
  • 自定义报表: 支持用户按需求生成报表(如 “月度能耗统计报表”“节能改造效果评估报表”),导出格式包括 Excel、PDF,且可设置自动定时发送(如每月 1 日将报表发送至企业邮箱);
  • 移动端适配: 通过手机浏览器访问系统,支持 “能耗预警推送”“快速查看关键指标”,方便管理人员随时随地掌握能源状况。

二、MyEMS 的开源优势:打破商业系统壁垒,降低管理门槛

相较于动辄数十万、数百万授权费用的商业能源管理系统,MyEMS 的开源属性为用户带来三大核心价值,尤其适配中小企业与创新场景:

1. 零成本入门,降低技术投入门槛

  • 授权免费: 基于 Apache License 2.0 开源协议,企业可免费下载、使用、修改源代码,无需支付软件授权费与年度维护费,大幅降低能源管理系统的初始投入;
  • 低成本部署: 支持在 x86 服务器、虚拟机、云服务器(如阿里云、AWS)上部署,最低配置(4 核 8G 内存)即可满足中小规模场景(如 100 台以内计量设备)的需求,无需专用硬件;
  • 文档完善: 官方提供详细的部署指南(从环境搭建到数据对接)、API 文档、故障排查手册,且 GitHub 仓库(myems/myems)有清晰的代码注释,技术团队可快速上手。

2. 高度可定制,适配个性化需求

商业系统往往因 “功能固定” 无法满足企业特殊需求(如某工厂需对接自定义的生产能耗核算模型),而 MyEMS 的开源特性则解决这一痛点:

  • 功能扩展: 开发者可基于现有模块新增功能,例如为新能源场景添加 “光伏发电量预测”“储能充放电调度逻辑”,或为工业场景添加 “能耗与生产产量的联动分析”;
  • 接口开放: 支持与企业现有系统(如 ERP、MES 生产执行系统、楼宇自控系统)对接,通过 API 将能耗数据同步至 ERP 进行成本核算,或从 MES 获取生产计划数据,实现 “能耗 - 生产” 协同分析;
  • 本地化适配: 可根据企业所在地区的能源政策(如峰谷电价、碳减排核算标准)调整算法,例如添加 “峰谷电价下的能耗成本优化建议” 模块。

3. 社区协同迭代,技术持续升级

开源项目的生命力源于社区,MyEMS 的全球开发者社区(涵盖中国、美国、德国、印度等地区)确保系统功能持续迭代,问题快速响应:

  • 功能更新: 社区定期发布新版本,例如 2024 年更新的 MyEMS v3.8 版本新增 “碳足迹计算模块”(支持根据能耗数据自动换算碳排放)、“AI 能耗预测插件”(基于历史数据预测未来 7 天能耗);
  • 问题修复: 用户在 GitHub 提交 Issue(如某协议对接 bug、报表生成异常),社区开发者通常在 1-3 个工作日内响应,避免商业系统 “售后响应慢” 的问题;
  • 经验共享: 社区论坛与 Discord 群组中,用户可分享部署案例(如 “某商场用 MyEMS 实现空调能耗下降 15%”)、二次开发方案,新手可快速借鉴成熟经验。

三、MyEMS 的典型应用场景:从工业到商业,覆盖多领域需求

MyEMS 的灵活性使其可适配不同行业的能源管理需求,以下三类场景最具代表性:

1. 工业企业:聚焦 “能耗 - 生产” 协同,降低单位产品能耗

对制造企业而言,能源成本占生产成本的 10%-30%,MyEMS 可帮助其精准定位能耗浪费点,实现 “节能降本” 与 “生产效率提升” 双赢:

  • 设备能效监控: 实时采集生产设备(如机床、注塑机)的功率数据,识别 “空转能耗”(如设备未生产但处于待机状态,功率仍达额定值的 30%),通过系统告警提醒操作员及时关闭;
  • 能耗成本核算: 按 “生产线 / 车间 / 产品型号” 拆分能耗数据,计算单位产品能耗(如 “每生产 1 台汽车零部件耗电 5.2 度”),对比不同生产线的能耗差异,推动低效生产线优化;
  • 节能改造效果验证: 某机械工厂安装变频器后,通过 MyEMS 对比改造前后的风机能耗,数据显示改造后每月节电 2.8 万度,投资回收期仅 8 个月,直观验证改造价值。

2. 商业建筑:优化 “空调 / 照明” 能耗,提升运维效率

商场、办公楼、酒店等商业建筑的能源消耗中,空调与照明占比超 60%,MyEMS 可通过 “精细化调控” 降低无效能耗:

  • 空调能耗优化: 对接中央空调控制器,采集室内温度、湿度与空调运行功率数据,设置 “动态温控策略”—— 例如上班前 1 小时启动空调,下班前半小时关闭,避免 “无人时空调空转”;同时根据人流变化(如商场周末人流多、工作日人流少)调整空调负荷,实现 “按需供冷 / 供热”;
  • 照明能耗监控: 按 “楼层 / 区域” 统计照明能耗,对 “白天光线充足但灯光全开” 的区域触发告警,推动 “自然光 + 灯光” 联动控制;
  • 案例参考: 某写字楼采用 MyEMS 后,通过优化空调与照明运行策略,月度总能耗下降 12%,年节约电费超 15 万元。

3. 公共机构:合规化管理,助力 “双碳” 目标落地

学校、医院、政府办公楼等公共机构需满足能源消耗统计、碳减排报告等合规要求,MyEMS 可简化数据统计流程,降低管理成本:

  • 能耗统计合规: 自动生成符合《公共机构能源资源消费统计制度》的报表,无需人工录入数据,避免统计误差;
  • 碳足迹核算: 基于能耗数据(如用电量、天然气消耗量),按国家推荐的碳排放系数(如每度电对应 0.610 吨 CO₂)自动计算碳排放量,生成年度碳减排报告;
  • 节能目标分解: 将年度节能目标(如 “单位面积能耗下降 3%”)拆分至各部门 / 楼宇,通过 MyEMS 实时监控目标完成进度,确保目标落地。

四、MyEMS 的未来趋势:AI 与多能协同,开启智能能源管理新篇章

随着能源数字化与 “双碳” 目标的深入推进,MyEMS 正朝着 “更智能、更全面” 的方向迭代,未来将聚焦两大核心方向:

1. AI 赋能:从 “被动分析” 到 “主动优化”

当前 MyEMS 以 “数据统计与分析” 为主,未来将通过 AI 插件实现 “预测 - 优化 - 调度” 闭环:

  • 能耗预测: 基于历史能耗数据、气象数据(如温度、湿度)、生产计划(工业场景),通过机器学习模型(如 LSTM 神经网络)预测未来 1-7 天的能耗趋势,帮助企业提前制定能源采购计划(如在电价低谷期多购电);
  • 智能调度: 在新能源场景(如工厂配套光伏 + 储能系统)中,AI 算法可根据光伏发电量预测、电网峰谷电价、负荷需求,自动调度储能充放电 —— 例如光伏发电量高时,优先用光伏电,多余电量存入储能;电网峰期时,用储能放电替代电网供电,降低用电成本;
  • 异常诊断升级: 当前 MyEMS 主要通过 “阈值告警” 识别异常(如能耗超上限),未来 AI 将实现 “根源诊断”—— 例如某设备能耗突增,AI 可自动分析是 “设备故障”“操作不当” 还是 “负荷增加”,并给出解决方案建议。

2. 多能协同:覆盖 “电 / 热 / 冷 / 储” 全能源品类

当前 MyEMS 以电能管理为主,未来将扩展至热能、冷能、储能等多能源品类,实现 “多能互补” 管理:

  • 多能源数据整合: 对接供热计量表、冷水表、储能系统控制器,实现 “电 / 热 / 冷 / 储” 数据统一监控,直观展示各能源品类的消耗占比;
  • 协同优化: 例如在工业园区中,MyEMS 可协调 “光伏电 - 储能 - 余热回收” 系统 —— 光伏电优先供生产使用,余热回收系统为车间供热,储能在电网断电时作为备用电源,最大化利用清洁能源,降低对电网依赖。

结语:MyEMS,开源力量加速能源管理民主化

在商业能源管理系统 “高成本、高门槛” 的背景下,MyEMS 以开源之名,打破了技术壁垒,让中小企业、公共机构、创新团队都能低成本享受到数字化能源管理的价值。它不仅是一套系统,更是能源管理领域 “协作创新” 的载体 —— 全球开发者通过社区共同迭代,用户通过二次开发适配个性化需求,最终推动能源管理从 “少数大企业的专属” 走向 “全民可及”。

对于有能源管理需求的企业而言,MyEMS 既是 “降本增效的工具”,也是 “数字化转型的跳板”。随着 AI 与

安全漏洞修复,组合图副值轴支持同环比设置,DataEase开源BI工具v2.10.14 LTS版本发布

2025年10月20日 18:01

2025年10月17日,人人可用的开源BI工具DataEase正式发布v2.10.14 LTS版本。

这一版本修复了四个安全漏洞,并且新增了诸多功能,具体包括:图表方面,组合图副值轴支持同环比设置,指标卡支持跳转设置,支持携带查询组件的值;查询组件方面,筛选时间时支持通过控件的形式选择日期,文本下拉组件在选项值设置为多选时,也可以设置首选默认值,时间范围在年月格式下增加“年初至本月”的范围设置项,查询条件“选项值数量”的默认设置增加了提示信息;仪表板方面,添加了组件内边距模式选择功能;数据集方面,筛选时间时,支持通过控件的形式选择日期;应用方面,应用导入的时候可以选择已有的数据集创建仪表板。

X-Pack增强包的功能变动包括:新增支持SAML2认证。

安全漏洞修复

■ fix(漏洞):修复H2 RCE漏洞(CVE-2025-62420);

■ fix(漏洞):修复JDBC绕过漏洞(CVE-2025-62419);

■ fix(漏洞):修复XSS漏洞(CVE-2025-62421);

■ fix(漏洞):修复数据集存在的SQL注入漏洞(CVE-2025-62422);

感谢腾讯悟空代码安全团队、社区用户zoiltin、wude1988、httpwwwcom发现并向DataEase开源社区反馈上述漏洞。

新增功能

■ feature(图表):组合图副值轴支持同环比设置;

■ feature(图表):指标卡支持跳转设置,支持携带查询组件的值;

■ feature(查询组件):筛选时间时支持通过控件的形式选择日期;

■ feature(查询组件):文本下拉组件在选项值设置为多选时,也可以设置首选默认值(#17088);

■ feature(查询组件):时间范围在年月格式下增加“年初至本月”的范围设置项(#17101);

■ feature(查询组件):查询条件“选项值数量”的默认设置增加了提示信息(#17091);

■ feature(仪表板):添加组件内边距模式选择功能;

■ feature(数据集):筛选时间时,支持通过控件的形式选择日期;

■ feature(应用):应用导入的时候可以选择已有的数据集创建仪表板;

■ feature(X-Pack):新增支持SAML2认证。

功能优化

■ refactor(图表):汇总表支持指标联动;

■ refactor(查询组件):查询组件去除聚合字段;

■ refactor(数据大屏):修改大屏最小尺寸(#17097);

■ refactor(数据大屏):优化大屏联动图标的显示方式;

■ refactor(仪表板、数据大屏):设置空值不参与跳转;

■ refactor(仪表板、数据大屏):优化静态图片上传功能,防止伪装文件上传至服务器;

■ refactor(数据源):添加数据源校验失败日志;

■ refactor(数据源):调整API数据源的查询超时时间;

■ refactor(定时报告):优化定时报告中查询组件的默认值设置;

■ refactor(X-Pack):扩大OIDC中的Scope字段长度;

■ refactor:优化前端缓存清除机制。

Bug修复

■ fix(图表):去除地图地名映射中名称为空的选项;

■ fix(图表):修复明细表在隐藏某一列后,导出数据出现错乱的问题(#16785);

■ fix(查询组件):修复时间范围的动态时间自定义设置中,会出现默认值超出日期筛选范围的问题;

■ fix(查询组件):修复移动端查询组件异常的问题(#17074);

■ fix(仪表板):修复开启首选项后禁用默认值,但规则中依然显示默认值的问题;

■ fix(仪表板、数据大屏):修复设置多组级联,搭配外部参数使用传入多参数,只有一组参数生效的问题(#17035);

■ fix(仪表板、数据大屏):修复跳转关联多个字段的情况下,部分字段存在空值时跳转失败的问题;

■ fix(仪表板、数据大屏):修复部分联动报错的问题;

■ fix(同步管理):优化任务的启动/停止逻辑;

■ fix(数据集):修复无法在导出条件中删除条件的问题;

■ fix(数据源):修复API数据源提取字段时,字段名称显示不完整的问题;

■ fix(数据源):修复对接SQLBot列权限全字段禁用无效的问题;

■ fix(数据源):修复H2数据源报错的问题;

■ fix(数据源):修复数据源国际化中出现的问题;

■ fix(X-Pack):修复“权限配置”-“按用户配置资源”页面,在数据量大的场景下页面卡死的问题;

■ fix:修复安装时磁盘大小包含小数导致安装脚本报错的问题。

草梅 Auth 1.10.1 发布与浏览器自动化工具 | 2025 年第 42 周草梅周报

作者 草梅友仁
2025年10月19日 22:53

本文在 草梅友仁的博客 发布和更新,并在多个平台同步发布。如有更新,以博客上的版本为准。您也可以通过文末的 原文链接 查看最新版本。

前言

欢迎来到草梅周报!这是一个由草梅友仁基于 AI 整理的周报,旨在为您提供最新的博客更新、GitHub 动态、个人动态和其他周刊文章推荐等内容。


本周依旧在开发 草梅 Auth 中。

你也可以直接访问官网地址:auth.cmyr.dev/ Demo 站:auth-demo.cmyr.dev/ 文档地址:auth-docs.cmyr.dev/

本周 草梅 Auth 发布了 1.10.1 版本。

本周的主要改动是修复了人机验证相关的逻辑的一些错误,优化验证码体验。

如果想了解如何部署和使用项目,可以参考文档的内容,也欢迎补充文档缺失的内容。

如果你对草梅 Auth 感兴趣,欢迎参与开发和测试。


最近研究了下浏览器自动化,发现了个有趣的工具——browserbase/stagehand,可以使用 AI 大模型来操控浏览器。

20251019215236399.png

使用方法也很简单,可以用官方脚手架生成。

npx create-browser-app
# 按照 CLI 提示进入项目目录并添加您的 API 密钥。然后运行示例脚本。
cd my-stagehand-app # Enter the project directory
cp .env.example .env  # Add your API keys
npm start # Run the example script

也可以手动安装依赖

pnpm i @browserbasehq/stagehand playwright
# 如果没有安装 playwright 需执行下面这条命令,以安装对应的浏览器
npx playwright install

然后再编写脚本即可。

import "dotenv/config";
import { Stagehand } from "@browserbasehq/stagehand";

async function main() {
    const stagehand = new Stagehand({
        env: "BROWSERBASE",
    });

    await stagehand.init();

    console.log(`Stagehand Session Started`);
    console.log(
        `Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`
    );

    const page = stagehand.page;

    await page.goto("https://stagehand.dev");

    const extractResult = await page.extract(
        "Extract the value proposition from the page."
    );
    console.log(`Extract result:\n`, extractResult);

    const actResult = await page.act("Click the 'Evals' button.");
    console.log(`Act result:\n`, actResult);

    const observeResult = await page.observe("What can I click on this page?");
    console.log(`Observe result:\n`, observeResult);

    const agent = await stagehand.agent({
        instructions:
            "You're a helpful assistant that can control a web browser.",
    });

    const agentResult = await agent.execute(
        "What is the most accurate model to use in Stagehand?"
    );
    console.log(`Agent result:\n`, agentResult);

    await stagehand.close();
}

main().catch((err) => {
    console.error(err);
    process.exit(1);
});

可以看到脚本中是直接用自然语言来描述的,因此简化了浏览器自动化脚本的编写。

所以现在无需考虑什么 XPath 或者 selector 了,直接用自然语言描述就行。

接下来一段时间会研究下如何把发布周报的过程给自动化一下,毕竟整个发布操作的重复度其实是非常高的,很适合自动化。

GitHub Release

caomei-auth

v1.10.1 - 2025-10-18 20:08:40

摘要: 版本 1.10.1 (2025-10-18) 摘要:

本次更新主要包含以下错误修复:

  1. 验证码组件:

    • 更新以支持新的 vue-recaptcha 插件
    • 修正了组件导入路径问题
  2. 构建配置:

    • 更新了项目构建配置
  3. Nuxt 相关:

    • 修复了 vue-recaptcha-v3 的转译条件问题
    • 优化了 Google reCAPTCHA 插件的加载逻辑
    • 将 vue-recaptcha 插件添加到 Nuxt 配置并设置了相关选项

本次更新主要针对验证码功能和构建配置进行了多项修复和优化。

cmyr-template-cli

v1.41.6 - 2025-10-19 02:40:06

摘要: [1.41.6]版本更新摘要:

Bug 修复:

  • 在 package.json 文件中新增了 homepage、repository 和 bugs 三个字段

本次更新主要解决了 package.json 配置文件缺少必要字段的问题,添加了项目主页、代码仓库和问题反馈的相关链接信息,便于用户更好地了解和参与项目开发。

最新 GitHub 加星仓库

  • CaoMeiYouRen starred Second-Me - 2025-10-14 11:26:30 训练 AI 自我提升,扩展能力,连接世界 主要编程语言:Python GitHub 星标数:14424

其他博客或周刊推荐

阮一峰的网络日志

阿猫的博客

潮流周刊

二丫讲梵的学习周刊

总结

本周的更新和动态如上所示。感谢您的阅读! 您可以通过以下方式订阅草梅周报的更新:

往期回顾

本文作者:草梅友仁
本文地址:blog.cmyr.ltd/archives/20…
版权声明:本文采用 CC BY-NC-SA 4.0 协议 进行分发,转载请注明出处!

传说中的表中表:VTable 主从表功能正式上线

作者 玄魂
2025年10月17日 17:42

VTable 主从表插件


本文作者:

薯片(github.com/Violet2314) 广东财经大学


导读

VTable 主从表插件(MasterDetailPlugin)是基于 VisActor VTable 开发的企业级数据可视化组件,专门解决复杂业务场景中的层次化数据展示需求。该插件突破了传统表格的平面化限制,实现了在主表行内嵌入完整子表格的创新交互模式,为用户提供了直观、高效的数据钻取和详情查看体验

image.png核心能力

  • 支持在主表行内嵌入完整的子表格,让复杂数据结构一目了然
  • 支持静态配置和动态函数配置,满足各种业务场景
  • 支持懒加载机制,优化大数据量场景下的性能表现

典型业务场景

业务场景 主表数据 子表数据
订单管理 订单基本信息 商品清单、物流详情
项目管理 项目概览 任务列表、成员分工
财务管理 汇总数据 明细账目、凭证信息
库存管理 产品类别 SKU详情、库存动态
客户管理 客户基本信息 联系记录、交易历史

快速上手

获取NPM包

首先,你需要在项目根目录下使用以下命令安装:

 # 使用 npm 安装
npm install @visactor/vtable @visactor/vtable-plugins

# 使用 yarn 安装
yarn add @visactor/vtable @visactor/vtable-plugins

引入主从表插件

通过 NPM 包引入

import * as VTable from '@visactor/vtable';
import { MasterDetailPlugin } from '@visactor/vtable-plugins';

function generateData(count) {
  const depts = ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'];
  return Array.from({ length: count }).map(( _ , i) => ({
    id: i + 1,
    rowNo: i + 1,
    name: `姓名 ${i + 1}`,
    department: depts[i % depts.length],
    score: Math.floor(Math.random() * 100),
    amount: Math.floor(Math.random() * 10000) / 100,
    children:
      i % 4 === 0
        ? [
            { task: `子任务 A-${i + 1}`, status: 'open' },
            { task: `子任务 B-${i + 1}`, status: 'done' }
          ]
        : undefined
  }));
}

const records = generateData(11);
 const masterDetailPlugin = new VTablePlugins.MasterDetailPlugin({
id: 'master-detail-static-3',
detailTableOptions: {
  columns: [
    { field: 'task', title: '任务名', width: 220 },
    { field: 'status', title: '状态', width: 120 }
  ],
  defaultRowHeight: 30,
  defaultHeaderRowHeight: 30,
  style: { margin: 12, height: 160 },
  theme: VTable.themes.BRIGHT
}
});

const columns = [
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'rowNo', title: '#', width: 60, headerType: 'text', cellType: 'text' },
{ field: 'name', title: '姓名', width: 140, sort: true },
{ field: 'department', title: '部门', width: 140, sort: true },
{ field: 'score', title: '分数', width: 100, sort: true },
{
  field: 'amount',
  title: '金额',
  width: 120,
  sort: true,
  fieldFormat: (v) => {
    if (typeof v === 'number' && !isNaN(v)) {
      return `$${v.toFixed(2)}`;
    }
    return v === undefined || v === null ? '' : String(v);
  }
}
];

const option = {
  container: document.getElementById(CONTAINER_ID),
  columns,
  records,
  autoFillWidth: true,
  hierarchyTextStartAlignment: true,
  plugins: [masterDetailPlugin]
};

const tableInstance = new VTable.ListTable(option);

运行后效果:

image.png

参数配置

参数名称 类型 默认值 功能说明
id string master-detail-${timestamp} 插件实例的全局唯一标识符,用于区分多个插件实例
enableCheckboxCascade boolean true 是否启用主从表之间的checkbox级联功能,主表中的复选框选择会自动与相应的子表同步
detailTableOptions DetailTableOptions Function 子表配置选项,支持静态对象配置或基于数据的动态配置函数

动态配置示例

const masterDetailPlugin = new MasterDetailPlugin({
  id: 'employee-detail-plugin',
  detailTableOptions: ({ data, bodyRowIndex }) => {
    if (bodyRowIndex === 0) {
      return {
        columns: [
        //......
        ],
        theme: VTable.themes.BRIGHT,
        style: {
          margin: 20,
          height: 300
        }
      };
    }
    return {
      columns: [
      //......
      ],
      theme: VTable.themes.DARK,
      style: {
        margin: 20,
        height: 300
      }
    };
  }
});

主从表插件的主要能力

展开行和渲染子表

当用户点击表格行的展开图标时,系统会在该行下方动态创建一个完整的子表格实例。子表格具备独立的配置、数据源和交互能力,与主表形成层次化的数据展示结构。

20250924002026_rec_.gif

当用户点击展开图标时,会触发以下处理链:

用户点击展开图标
    ↓
EventManager.handleIconClick()
    ↓ 
MasterDetailPlugin.expandRow(rowIndex, colIndex)
    ↓
检查行是否已展开 (isRowExpanded)
    ↓
获取记录数据 (getRecordByRowIndex)
    ↓
MasterDetailPlugin.getChildren()
    ↓
ConfigManager.getDetailConfigForRecord()
    ↓
updateRowHeightForExpand()
    ↓
updateContainerHeight()
    ↓
SubTableManager.renderSubTable()
    ↓
recalculateAllSubTablePositions(bodyRowIndex + 1)
    ↓
drawUnderlineForRow()
    ↓
refreshRowIcon()

技术特性

  • 独立 实例: 每个子表都是完整的VTable实例,支持所有表格功能
  • 动态行高: 主表行高自动适配子表内容,支持自适应和固定高度
  • 位置同步: 主表滚动时子表位置实时跟随,保持视觉连贯性
  • 内存管理: 收起时自动销毁子表实例,展开时重新创建

生命周期管理

创建阶段: 主表行展开 → 解析配置 → 实例化子表

运行阶段: 位置同步 → 事件处理 → 数据更新

销毁阶段: 行收起 → 清理事件 → 销毁实例 → 回收内存

滚动同步机制

主表滚动时,所有子表会实时跟随滚动,保持视觉上的一体化效果。同时优化了滚动事件的触发机制,并且还有主子表的滚动分离机制,即当鼠标在子表内滚动时并且没有滚动到上下边界的时候,我滚动的是子表的内容,主表是不会滚动的,主表滚动的时候子表是不会滚动的

转存失败,建议直接上传图片文件

20250923112352_rec_.gif技术特性

  • scrollEventAlwaysTrigger: 自动设置为true,确保边界滚动也能触发事件
  • 批量更新: 一次滚动事件批量更新所有子表位置

主从表checkbox联动功能

主从表checkbox联动实现了主表与子表之间选择状态的智能同步。当主表行被选中时,其对应的子表中所有行自动选中;当子表中部分行被选中/取消选中时,主表行的选择状态会相应更新状态。使用参数enableCheckboxCascade来决定是否开启,默认是true

主表checkbox点击 → 检测子表存在 → 遍历子表所有行 → 同步选择状态 → 触发联动回调
子表checkbox点击 → 统计子表选中数量 → 计算主表状态 → 更新主表checkbox → 触发联动回调

20250923112506_rec_.gif

技术实现

  • 事件监听: 监听主表和子表的checkbox变化事件
  • 状态同步: 通过内部状态管理器维护选择状态映射关系
  • 回调通知: 选择状态变化时触发相应回调函数,便于业务处理

这种联动机制大大提升了复杂数据结构的操作效率,用户可以通过简单的点击实现批量选择操作。

懒加载功能

当父行记录的 children 字段为 true 时,表示这是一个懒加载节点,需要通过监听 VTable TREE_HIERARCHY_STATE_CHANGE 事件来实现异步数据获取

当用户点击包含懒加载节点的展开图标时,会触发以下调用链:

用户点击展开图标
    ↓
table.toggleHierarchyState()
    ↓
触发 TREE_HIERARCHY_STATE_CHANGE 事件
    ↓
用户事件处理器接收事件参数
    ↓
tableInstance.setLoadingHierarchyState(col, row) 显示loading图标
    ↓
异步数据获取
    ↓
plugin.setRecordChildren(detailData, col, row) 设置数据并展开
    ↓
渲染子表并完成展开
方法 说明 参数
plugin.setLoadingHierarchyState(col, row) 显示loading图标 col: 列索引, row: 行索引
plugin.setRecordChildren(children, col, row) 设置子数据并展开 children: 子数据数组, col: 列索引, row: 行索引

20250923235728_rec_.gif

用户配置示例

const masterData = [
  { id: 1, name: '订单001', children: true },    // 懒加载标识
  { id: 2, name: '订单002', children: [...] }   // 静态数据
];

const plugin = new MasterDetailPlugin({
  detailTableOptions: {
    // 子表配置
  }
});

const tableInstance = new VTable.ListTable(options);

// 监听主从表层次状态变化事件
const { MASTER_DETAIL_HIERARCHY_STATE_CHANGE } = VTable.ListTable.EVENT_TYPE;
tableInstance.on(MASTER_DETAIL_HIERARCHY_STATE_CHANGE, async (args) => {
  if (args.hierarchyState === VTable.TYPES.HierarchyState.expand && 
      args.originData?.children === true) {
    
    // 显示loading状态
    plugin.setLoadingHierarchyState(args.col, args.row);
    
    try {
      // 异步数据获取
      const detailData = await fetchDataFromAPI(args.originData.id);
      
      // 设置子数据并自动展开
      plugin.setRecordChildren(detailData, args.col, args.row);
    } catch (error) {
      console.error('Failed to load detail data:', error);
    }
  }
});

智能缓存机制

  • 一次加载: 数据加载成功后,children: true 自动转换为实际数据数组
  • 永久缓存: 再次点击时识别为静态数据,直接展开无需重新加载

这种设计实现了透明的按需加载,用户只需配置 onLazyLoad 回调,其余状态管理完全由插件自动处理。

小结

VTable 主从表插件是一个正在成长的开源数据展示插件,我们致力于为Web应用提供优秀的层次化数据展示解决方案。

它将复杂的主从数据关系展示能力带到了Web端,同时充分发挥了现代Web技术的交互优势。无论您是要构建订单管理系统、项目管理平台、财务报表系统,还是任何需要主从表功能的Web应用,VTable主从表插件都能为你提供一个良好的选择

在线demo和教程

demo: visactor.com/vtable/demo… 教程: visactor.com/vtable/guid…

欢迎交流

最后,我们诚挚的欢迎所有对数据可视化感兴趣的朋友参与进来,参与 VisActor 的开源建设:

VTableVTable 官网VTable Github(欢迎 Star)

VisActor 官方网站:www.visactor.io/www.viactor.com

Discord:discord.gg/3wPyxVyH6m

飞书群(外网):打开链接扫码

转存失败,建议直接上传图片文件

微信公众号:打开链接扫码

转存失败,建议直接上传图片文件

github:github.com/VisActor

🎨 SCSS 高级用法完全指南:从入门到精通

2025年10月15日 11:19

🚀 想让 CSS 写得更爽?本文手把手教你 SCSS 的各种实用技巧,让你的样式代码又好写又好管理!

📚 目录


为了实时查看,我这边使用工程化来练习:

企业微信截图_17604966056743.png

1. 变量与作用域

1.1 局部变量与全局变量

// 全局变量
$primary-color: #3498db;

.container {
  // 局部变量
  $padding: 20px;
  padding: $padding;

  .item {
    // 可以访问父级局部变量
    margin: $padding / 2;
    color: $primary-color;
  }
}

// $padding 在这里不可用

1.2 !global 标志

.element {
  $local-var: 10px;

  @if true {
    // 使用 !global 将局部变量提升为全局
    $local-var: 20px !global;
  }
}

// 现在可以在外部访问
.another {
  padding: $local-var; // 20px
}

1.3 !default 标志

// 设置默认值,如果变量已存在则不覆盖
$base-font-size: 16px !default;
$primary-color: #333 !default;

// 这在创建主题或库时非常有用

1.4 Map 变量

// 定义颜色系统
$colors: (
  primary: #3498db,
  secondary: #2ecc71,
  danger: #e74c3c,
  warning: #f39c12,
  info: #9b59b6,
);

// 使用 map-get 获取值
.button {
  background: map-get($colors, primary);

  &.danger {
    background: map-get($colors, danger);
  }
}

// 深层嵌套的 Map
$theme: (
  colors: (
    light: (
      bg: #ffffff,
      text: #333333,
    ),
    dark: (
      bg: #1a1a1a,
      text: #ffffff,
    ),
  ),
  spacing: (
    small: 8px,
    medium: 16px,
    large: 24px,
  ),
);

// 获取深层值
.dark-mode {
  background: map-get(map-get(map-get($theme, colors), dark), bg);
}

2. 嵌套与父选择器

2.1 父选择器 & 的高级用法

// BEM 命名法
.card {
  padding: 20px;

  &__header {
    font-size: 18px;
  }

  &__body {
    margin: 10px 0;
  }

  &--featured {
    border: 2px solid gold;
  }

  // 伪类
  &:hover {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  }

  // 父选择器在后面
  .dark-theme & {
    background: #333;
  }
}

2.2 嵌套属性

.button {
  // 嵌套属性值
  font: {
    family: 'Helvetica', sans-serif;
    size: 14px;
    weight: bold;
  }

  border: {
    top: 1px solid #ccc;
    bottom: 2px solid #999;
    radius: 4px;
  }

  transition: {
    property: all;
    duration: 0.3s;
    timing-function: ease-in-out;
  }
}

2.3 @at-root 跳出嵌套

.parent {
  color: blue;

  @at-root .child {
    // 这会在根级别生成 .child 而不是 .parent .child
    color: red;
  }

  @at-root {
    .sibling-1 {
      color: green;
    }
    .sibling-2 {
      color: yellow;
    }
  }
}

3. Mixins 高级技巧

3.1 带参数的 Mixin

// 基础 Mixin
@mixin flex-center($direction: row) {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: $direction;
}

// 使用
.container {
  @include flex-center(column);
}

3.2 可变参数 (...)

// 接收任意数量的参数
@mixin box-shadow($shadows...) {
  -webkit-box-shadow: $shadows;
  -moz-box-shadow: $shadows;
  box-shadow: $shadows;
}

// 使用
.card {
  @include box-shadow(0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.05));
}

// 传递多个值
@mixin transition($properties...) {
  transition: $properties;
}

.button {
  @include transition(background 0.3s ease, transform 0.2s ease-out);
}

3.3 @content 指令

// 响应式 Mixin
@mixin respond-to($breakpoint) {
  @if $breakpoint == 'mobile' {
    @media (max-width: 767px) {
      @content;
    }
  } @else if $breakpoint == 'tablet' {
    @media (min-width: 768px) and (max-width: 1023px) {
      @content;
    }
  } @else if $breakpoint == 'desktop' {
    @media (min-width: 1024px) {
      @content;
    }
  }
}

// 使用
.sidebar {
  width: 300px;

  @include respond-to('mobile') {
    width: 100%;
    display: none;
  }

  @include respond-to('tablet') {
    width: 200px;
  }
}

3.4 高级响应式 Mixin

$breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
  xxl: 1400px,
);

@mixin media-breakpoint-up($name) {
  $min: map-get($breakpoints, $name);
  @if $min {
    @media (min-width: $min) {
      @content;
    }
  } @else {
    @content;
  }
}

@mixin media-breakpoint-down($name) {
  $max: map-get($breakpoints, $name) - 1px;
  @if $max {
    @media (max-width: $max) {
      @content;
    }
  }
}

// 使用
.container {
  padding: 15px;

  @include media-breakpoint-up(md) {
    padding: 30px;
  }

  @include media-breakpoint-up(lg) {
    padding: 45px;
  }
}

3.5 主题切换 Mixin

@mixin theme($theme-name) {
  @if $theme-name == 'light' {
    background: #ffffff;
    color: #333333;
  } @else if $theme-name == 'dark' {
    background: #1a1a1a;
    color: #ffffff;
  }
}


// 更灵活的主题系统
$themes: (
  light: (
    bg: #ffffff,
    text: #333333,
    primary: #3498db,
  ),
  dark: (
    bg: #1a1a1a,
    text: #ffffff,
    primary: #5dade2,
  ),
);

@mixin themed() {
  @each $theme, $map in $themes {
    .theme-#{$theme} & {
      $theme-map: $map !global;
      @content;
      $theme-map: null !global;
    }
  }
}

@function t($key) {
  @return map-get($theme-map, $key);
}

// 使用
.card {
  @include themed() {
    background: t(bg);
    color: t(text);
    border-color: t(primary);
  }
}

4. 函数的妙用

4.1 自定义函数

// 计算 rem
@function rem($pixels, $base: 16px) {
  @return ($pixels / $base) * 1rem;
}

.title {
  font-size: rem(24px); // 1.5rem
  margin-bottom: rem(16px); // 1rem
}

4.2 颜色操作函数

// 创建颜色变体
@function tint($color, $percentage) {
  @return mix(white, $color, $percentage);
}

@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}

$primary: #3498db;

.button {
  background: $primary;

  &:hover {
    background: shade($primary, 20%);
  }

  &.light {
    background: tint($primary, 30%);
  }
}

4.3 字符串操作

@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);

  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index +
            str-length($search)), $search, $replace);
  }

  @return $string;
}

// 使用
$font-family: str-replace('Arial, sans-serif', 'Arial', 'Helvetica');

4.4 深度获取 Map 值

@function deep-map-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);
  }
  @return $map;
}

$config: (
  theme: (
    colors: (
      primary: (
        base: #3498db,
        light: #5dade2,
      ),
    ),
  ),
);
    
.element {
  color: deep-map-get($config, theme, colors, primary, base);
}

5. 继承与占位符

5.1 基础继承

.message {
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.success-message {
  @extend .message;
  border-color: #2ecc71;
  background: #d5f4e6;
}

.error-message {
  @extend .message;
  border-color: #e74c3c;
  background: #fadbd8;
}

5.2 占位符选择器 %

// 占位符不会单独生成 CSS
%flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

%text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-title {
  @extend %text-truncate;
  font-size: 18px;
}

.modal {
  @extend %flex-center;
  min-height: 100vh;
}

5.3 多重继承

%bordered {
  border: 1px solid #ddd;
}

%rounded {
  border-radius: 8px;
}

%shadowed {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.card {
  @extend %bordered;
  @extend %rounded;
  @extend %shadowed;
  padding: 20px;
}

6. 控制指令

6.1 @if / @else

@mixin theme-color($theme) {
  @if $theme == 'light' {
    background: white;
    color: black;
  } @else if $theme == 'dark' {
    background: black;
    color: white;
  } @else {
    background: gray;
    color: white;
  }
}

.app {
  @include theme-color('dark');
}

6.2 @for 循环

// 生成网格系统
@for $i from 1 through 12 {
  .col-#{$i} {
    width: percentage($i / 12);
  }
}

// 生成间距工具类
$spacing: (5, 10, 15, 20, 25, 30);

@for $i from 1 through length($spacing) {
  $space: nth($spacing, $i);

  .m-#{$space} {
    margin: #{$space}px;
  }
  .p-#{$space} {
    padding: #{$space}px;
  }
  .mt-#{$space} {
    margin-top: #{$space}px;
  }
  .pt-#{$space} {
    padding-top: #{$space}px;
  }
  .mb-#{$space} {
    margin-bottom: #{$space}px;
  }
  .pb-#{$space} {
    padding-bottom: #{$space}px;
  }
}

6.3 @each 循环

// 遍历列表
$colors: primary, secondary, success, danger, warning, info;

@each $color in $colors {
  .btn-#{$color} {
    background: var(--#{$color}-color);
  }
}

// 遍历 Map
$social-colors: (
  facebook: #3b5998,
  twitter: #1da1f2,
  instagram: #e4405f,
  linkedin: #0077b5,
  youtube: #ff0000,
);

@each $name, $color in $social-colors {
  .btn-#{$name} {
    background-color: $color;

    &:hover {
      background-color: darken($color, 10%);
    }
  }
}

// 多重值遍历
$sizes: (small, 12px, 500, medium, 14px, 600, large, 16px, 700);

@each $size, $font-size, $font-weight in $sizes {
  .text-#{$size} {
    font-size: $font-size;
    font-weight: $font-weight;
  }
}

6.4 @while 循环

// 生成渐进式字体大小
$i: 6;
@while $i > 0 {
  h#{$i} {
    font-size: 2em - ($i * 0.2);
  }
  $i: $i - 1;
}

7. 模块化系统

7.1 @use 和 @forward

// _variables.scss
$primary-color: #3498db;
$secondary-color: #2ecc71;

// _mixins.scss
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

// _functions.scss
@function rem($px) {
  @return ($px / 16px) * 1rem;
}

// main.scss - 新的模块系统
@use 'variables' as vars;
@use 'mixins' as mix;
@use 'functions' as fn;

.container {
  @include mix.flex-center;
  color: vars.$primary-color;
  padding: fn.rem(20px);
}

7.2 命名空间

// _config.scss
$primary: #3498db;

@mixin button {
  padding: 10px 20px;
  border-radius: 4px;
}

// styles.scss
@use 'config' as cfg;

.btn {
  @include cfg.button;
  background: cfg.$primary;
}

// 或者移除命名空间前缀
@use 'config' as *;

.btn {
  @include button;
  background: $primary;
}

7.3 @forward 创建索引文件

// styles/_index.scss
@forward 'variables';
@forward 'mixins';
@forward 'functions';

// main.scss
@use 'styles';

.element {
  color: styles.$primary-color;
  @include styles.flex-center;
}

8. 内置函数库

8.1 颜色函数

$base-color: #3498db;

.color-demo {
  // 颜色调整
  color: adjust-hue($base-color, 45deg);

  // 亮度
  background: lighten($base-color, 20%);
  border-color: darken($base-color, 15%);

  // 饱和度
  &.vibrant {
    background: saturate($base-color, 30%);
  }

  &.muted {
    background: desaturate($base-color, 20%);
  }

  // 透明度
  box-shadow: 0 2px 8px rgba($base-color, 0.3);
  border: 1px solid transparentize($base-color, 0.5);

  // 混合颜色
  &.mixed {
    background: mix(#3498db, #e74c3c, 50%);
  }

  // 补色
  &.complement {
    background: complement($base-color);
  }
}

8.2 数学函数

.math-demo {
  // 基础运算
  width: percentage(5 / 12); // 41.66667%
  padding: round(13.6px); // 14px
  margin: ceil(10.1px); // 11px
  height: floor(19.9px); // 19px

  // 最大最小值
  font-size: max(14px, 1rem);
  width: min(100%, 1200px);

  // 绝对值
  top: abs(-20px); // 20px

  // 随机数
  opacity: random(100) / 100;
}

8.3 列表函数

$list: 10px 20px 30px 40px;

.list-demo {
  // 获取长度
  $length: length($list); // 4

  // 获取元素
  padding-top: nth($list, 1); // 10px
  padding-right: nth($list, 2); // 20px

  // 索引
  $index: index($list, 20px); // 2

  // 追加
  $new-list: append($list, 50px);

  // 合并
  $merged: join($list, (60px 70px));
}

8.4 Map 函数

$theme: (
  primary: #3498db,
  secondary: #2ecc71,
  danger: #e74c3c,
);

.map-demo {
  // 获取值
  color: map-get($theme, primary);

  // 合并 Map
  $extended: map-merge(
    $theme,
    (
      success: #27ae60,
    )
  );

  // 检查键是否存在
  @if map-has-key($theme, primary) {
    background: map-get($theme, primary);
  }

  // 获取所有键
  $keys: map-keys($theme); // primary, secondary, danger

  // 获取所有值
  $values: map-values($theme);
}

8.5 字符串函数

$text: 'Hello World';

.string-demo {
  // 转大写
  content: to-upper-case($text); // "HELLO WORLD"

  // 转小写
  content: to-lower-case($text); // "hello world"

  // 字符串长度
  $length: str-length($text); // 11

  // 查找索引
  $index: str-index($text, 'World'); // 7

  // 切片
  content: str-slice($text, 1, 5); // "Hello"

  // 插入
  content: str-insert($text, ' Beautiful', 6); // "Hello Beautiful World"

  // 去引号
  font-family: unquote('"Arial"'); // Arial
}

9. 实战技巧

9.1 响应式字体大小

@function strip-unit($value) {
  @return $value / ($value * 0 + 1);
}

@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
  $u1: unit($min-vw);
  $u2: unit($max-vw);
  $u3: unit($min-font-size);
  $u4: unit($max-font-size);

  @if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
    & {
      font-size: $min-font-size;

      @media screen and (min-width: $min-vw) {
        font-size: calc(
          #{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} *
            ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)})
        );
      }

      @media screen and (min-width: $max-vw) {
        font-size: $max-font-size;
      }
    }
  }
}

h1 {
  @include fluid-type(320px, 1200px, 24px, 48px);
}

9.2 深色模式切换

$themes: (
  light: (
    bg: #ffffff,
    text: #333333,
    border: #e0e0e0,
    primary: #3498db,
  ),
  dark: (
    bg: #1a1a1a,
    text: #f0f0f0,
    border: #404040,
    primary: #5dade2,
  ),
);

@mixin themed-component {
  @each $theme-name, $theme-colors in $themes {
    [data-theme='#{$theme-name}'] & {
      $theme-map: $theme-colors !global;
      @content;
      $theme-map: null !global;
    }
  }
}

@function theme-color($key) {
  @return map-get($theme-map, $key);
}

.card {
  @include themed-component {
    background: theme-color(bg);
    color: theme-color(text);
    border: 1px solid theme-color(border);
  }

  &__button {
    @include themed-component {
      background: themed-component {
      background: theme-color(primary);
      color: theme-color(bg);
    }
  }
}

9.3 原子化 CSS 生成器

$spacing-map: (
  0: 0,
  1: 0.25rem,
  2: 0.5rem,
  3: 0.75rem,
  4: 1rem,
  5: 1.25rem,
  6: 1.5rem,
  8: 2rem,
  10: 2.5rem,
  12: 3rem,
  16: 4rem,
  20: 5rem,
);

$directions: (
  '': '',
  't': '-top',
  'r': '-right',
  'b': '-bottom',
  'l': '-left',
  'x': (
    '-left',
    '-right',
  ),
  'y': (
    '-top',
    '-bottom',
  ),
);

@each $size-key, $size-value in $spacing-map {
  @each $dir-key, $dir-value in $directions {
    // Margin
    .m#{$dir-key}-#{$size-key} {
      @if type-of($dir-value) == 'list' {
        @each $d in $dir-value {
          margin#{$d}: $size-value;
        }
      } @else {
        margin#{$dir-value}: $size-value;
      }
    }

    // Padding
    .p#{$dir-key}-#{$size-key} {
      @if type-of($dir-value) == 'list' {
        @each $d in $dir-value {
          padding#{$d}: $size-value;
        }
      } @else {
        padding#{$dir-value}: $size-value;
      }
    }
  }
}

9.4 三角形生成器

@mixin triangle($direction, $size, $color) {
  width: 0;
  height: 0;
  border: $size solid transparent;

  @if $direction == 'up' {
    border-bottom-color: $color;
  } @else if $direction == 'down' {
    border-top-color: $color;
  } @else if $direction == 'left' {
    border-right-color: $color;
  } @else if $direction == 'right' {
    border-left-color: $color;
  }
}

.tooltip {
  position: relative;

  &::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    @include triangle(down, 8px, #333);
  }
}

9.5 网格系统生成器

$grid-columns: 12;
$grid-gutter-width: 30px;
$container-max-widths: (
  sm: 540px,
  md: 720px,
  lg: 960px,
  xl: 1140px,
  xxl: 1320px,
);

@mixin make-container($padding-x: $grid-gutter-width / 2) {
  width: 100%;
  padding-right: $padding-x;
  padding-left: $padding-x;
  margin-right: auto;
  margin-left: auto;
}

@mixin make-row($gutter: $grid-gutter-width) {
  display: flex;
  flex-wrap: wrap;
  margin-right: -$gutter / 2;
  margin-left: -$gutter / 2;
}

@mixin make-col($size, $columns: $grid-columns) {
  flex: 0 0 auto;
  width: percentage($size / $columns);
  padding-right: $grid-gutter-width / 2;
  padding-left: $grid-gutter-width / 2;
}

.container {
  @include make-container;

  @each $breakpoint, $width in $container-max-widths {
    @include media-breakpoint-up($breakpoint) {
      max-width: $width;
    }
  }
}
.row {
  @include make-row;
}

@for $i from 1 through $grid-columns {
  .col-#{$i} {
    @include make-col($i);
  }
}

9.6 长阴影效果

@function long-shadow($length, $color, $opacity) {
  $shadow: '';

  @for $i from 0 through $length {
    $shadow: $shadow +
      '#{$i}px #{$i}px rgba(#{red($color)}, #{green($color)}, #{blue($color)}, #{$opacity})';

    @if $i < $length {
      $shadow: $shadow + ', ';
    }
  }

  @return unquote($shadow);
}

.text-shadow {
  text-shadow: long-shadow(50, #000, 0.05);
}

9.7 动画关键帧生成器

@mixin keyframes($name) {
  @keyframes #{$name} {
    @content;
  }
}

@include keyframes(fadeIn) {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-fade {
  animation: fadeIn 0.5s ease-out;
}

9.8 清除浮动

@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

.container {
  @include clearfix;
}

🎯 总结

SCSS 的高级特性让我们能够:

  1. 提高代码复用性 - 通过 mixin、函数和继承
  2. 增强可维护性 - 使用变量、模块化和命名空间
  3. 提升开发效率 - 利用循环、条件判断自动生成样式
  4. 保持代码整洁 - 嵌套、占位符和模块系统
  5. 创建强大的工具库 - 自定义函数和 mixin 集合

最佳实践建议

  1. 变量命名要语义化

    // Good
    $primary-color: #3498db;
    $spacing-unit: 8px;
    
    // Bad
    $blue: #3498db;
    $var1: 8px;
    
  2. 避免嵌套层级过深(建议不超过 3-4 层)

    // Good
    .card {
      &__header {
      }
      &__body {
      }
    }
    
    // Bad - 嵌套太深
    .card {
      .wrapper {
        .inner {
          .content {
            .text {
            }
          }
        }
      }
    }
    
  3. 优先使用 @use 而不是 @import

// Modern
@use 'variables';
@use 'mixins';

// Legacy
@import 'variables';
@import 'mixins';
  1. 使用占位符代替类继承

    // Good
    %btn-base {
    }
    .btn {
      @extend %btn-base;
    }
    
    // Less optimal
    .btn-base {
    }
    .btn {
      @extend .btn-base;
    }
    
  2. 合理组织文件结构 styles/ ├── abstracts/ │ ├── _variables.scss │ ├── _functions.scss │ └── _mixins.scss ├── base/ │ ├── _reset.scss │ └── _typography.scss ├── components/ │ ├── _buttons.scss │ └── _cards.scss ├── layout/ │ ├── _header.scss │ └── _footer.scss └── main.scss


📚 参考资源


如果这篇文章对你有帮助,欢迎点赞收藏! 👍

有任何问题或补充,欢迎在评论区讨论~ 💬

HelloGitHub 第 114 期

2025年9月28日 07:49
本期共有 39 个项目,包含 C 项目 (2),C# 项目 (2),C++ 项目 (3),Go 项目 (4),Java 项目 (2),JavaScript 项目 (5),Kotlin 项目 (1),Python 项目 (5),Rust 项目 (3),Swift 项目 (2),人工智能 (4),其它 (5),开源书籍 (1)
❌
❌