amis-editor 注册自定义组件
建议先将amis文档从头到尾,仔细看一遍。
参考:amis - 低代码前端框架
amis 的渲染过程是将 json 转成对应的 React 组件。先通过 json 的 type 找到对应的 Component,然后把其他属性作为 props 传递过去完成渲染。
import * as React from 'react';
import {Renderer} from 'amis-core';
@Renderer({ // amis-core/src/factory.tsx里的Renderer方法,主要作用识别json格式的type交给对应react组件来处理(现在可以识别{"type": "page", "title": "自定义组件示例"} )。type: 'page'// ... 其他信息隐藏了
})
export class PageRenderer extends React.Component {// ... 其他信息隐藏了render() {const { title, body, render /*用来渲染孩子节点,如果当前是叶子节点则可以忽略。*/ } = this.props;return (<div className="page"><h1>{title}</h1><div className="body-container">{render('body', body,{// 这里的信息会作为 props 传递给子组件,一般情况下都不需要这个}) /*渲染孩子节点*/}</div></div>);}
}
// 如果不支持 Decorators 语法也可以使用如下写法
export Renderer({type: 'page'
})(class PageRenderer extends React.Component {render() {// ...同上}
})
React注册自定义组件:
1.比如:注册一个 React 组件,当节点的 type 是 my-renderer 时,交给当前组件来完成渲染。
import * as React from 'react';
import {Renderer} from 'amis';
@Renderer({type: 'my-renderer',autoVar: true // amis 1.8 之后新增的功能,自动解析出参数里的变量
})
class CustomRenderer extends React.Component {render() {const {tip} = this.props;return <div>这是自定义组件:{tip}</div>;}
}
有了以上这段代码后,就可以这样使用了:
{"type": "page","title": "自定义组件示例","body": {"type": "my-renderer","tip": "简单示例"}
}
如果这个组件还能通过 children 属性添加子节点,则需从props中获取body, render处理(参考上面page组件)。
render(region, node, props) 方法,这个方法就是专门用来渲染子节点的。来看下参数说明:
* region 区域名称,你有可能有多个区域可以作为容器,请不要重复。
* node 子节点。
* props 可选,可以通过此对象跟子节点通信等。
属性支持变量
因为配置了 autoVar: true,使得所有组件参数将自动支持变量,在组件内拿到的将是解析后的值(ps: 1.8.0 及以上版本新增配置,之前版本需要调用 amis 里的 resolveVariableAndFilter 方法)
2.表单项FormItem的扩展(amis-core/src/renderes/Item)
以上是普通渲染器的注册方式,如果是表单项,为了更简单的扩充,请使用 FormItem 注解,而不是 Renderer。 原因是如果用 FormItem 是不用关心:label 怎么摆,表单验证器怎么实现,如何适配表单的 3 种展现方式(水平、上下和内联模式),而只用关心:有了值后如何回显,响应用户交互设置新值。
import * as React from 'react';
import {FormItem} from 'amis';
@FormItem({type: 'custom'
})
class MyFormItem extends React.Component {render() {const {value, onChange} = this.props;return (<div><p>这个是个自定义组件</p><p>当前值:{value}</p><aclassName="btn btn-default"onClick={() => onChange(Math.round(Math.random() * 10000))}>随机修改</a></div>);}
}
有了以上这段代码后,就可以这样使用了:
{"type": "page","title": "自定义组件示例","body": {"type": "form","body": [{"type": "custom","label": "随机值","name": "random"}]}
}
注意: 使用 FormItem 默认是严格模式,即只有必要的属性变化才会重新渲染,有可能满足不了你的需求,如果忽略性能问题,可以传入 strictMode: false 来关闭。
表单项开发主要关心两件事。
1.呈现当前值。如以上例子,通过 this.props.value 判定如果勾选了则显示已勾选,否则显示请勾选。
2.接收用户交互,通过 this.props.onChange 修改表单项值。如以上例子,当用户点击按钮时,切换当前选中的值。
至于其他功能如:label/description 的展示、表单验证功能、表单布局(常规、左右或者内联)等等,只要是通过 FormItem 注册进去的都无需自己实现。
需要注意,获取或者修改的是什么值跟配置中 type 并列的 name 属性有关,也就是说直接关联某个变量,自定义中直接通过 props 下发了某个指定变量的值和修改的方法。如果你想获取其他数据,或者设置其他数据可以看下以下说明:
* 获取其他数据 可以通过 this.props.data 查看,作用域中所有的数据都在这了。
* 设置其他数据 可以通过 this.props.onBulkChange, 比如: this.props.onBulkChange({a: 1, b: 2}) 等于同时设置了两个值。当做数据填充的时候,这个方法很有用。
3.其它高级定制
——自定义验证器
如果 amis 自带的验证能满足需求了,则不需要关心。组件可以有自己的验证逻辑。
@FormItem({ type: 'custom-checkbox' })
export default class CustomCheckbox extends React.Component {validate() {// 通过 this.props.value 可以知道当前值。return isValid ? '' : '不合法,说明不合法原因。';}// ... 其他省略了
}
上面的例子只是简单说明,另外可以做异步验证,validate 方法可以返回一个 promise。
——OptionsControl (amis-core/src/renderes/Options)
如果你的表单组件性质和 amis 的 Select、Checkboxes、List 差不多,用户配置配置 source 可通过 API 拉取选项,你可以用 OptionsControl 取代 FormItem 这个注解。
用法是一样,功能方面主要多了以下功能。
* 可以配置 options,options 支持配置 visibleOn hiddenOn 等表达式
* 可以配置 source 换成动态拉取 options 的功能,source 中有变量依赖会自动重新拉取。
* 下发了这些 props,可以更方便选项。
* options 不管是用户配置的静态 options 还是配置 source 拉取的,下发到组件已经是最终的选项了。
* selectedOptions 数组类型,当前用户选中的选项。
* loading 当前选项是否在加载
* onToggle 切换一个选项的值
* onToggleAll 切换所有选项的值,类似于全选。
4.组件间通信
关于组件间通信,amis 中有个机制就是,把需要被引用的组件设置一个 name 值,然后其他组件就可以通过这个 name 与其通信,比如这个例子。其实内部是依赖于内部的一个 Scoped Context。你的组件希望可以被别的组件引用,你需要把自己注册进去,默认自定义的非表单类组件并没有把自己注册进去,可以参考以下代码做添加:
import * as React from 'react';
import {Renderer, ScopedContext} from 'amis';
@Renderer({ type: 'my-renderer'})
export class CustomRenderer extends React.Component {static contextType = ScopedContext;constructor() {const scoped = this.context;scoped.registerComponent(this);}componentWillUnmount() {const scoped = this.context;scoped.unRegisterComponent(this);}// 其他部分省略了。
}
把自己注册进去了,其他组件就能引用到了。同时,如果你想找别的组件,也同样是通过 scoped 这个 context,如: scoped.getComponentByName("xxxName") 这样就能拿到目标组件的实例了(前提是目标组件已经配置了 name 为 xxxName)。
5.自定义组件接入事件动作
需求场景主要是想要自定义组件的内部事件暴露出去,能够通过对事件的监听来执行所需动作,并希望自定义组件自身的动作能够被其他组件调用。接入方法是通过`props.dispatchEvent`派发自身的各种事件,使其具备更灵活的交互设计能力;
通过重写`doAction`方法实现其他组件对其专属动作的调用,需要注意的是,此处依赖内部的 `Scoped Context`来实现自身的注册
amis/src/renderers中不同的组件可重写自己的doAction方法(实现自己的组件专属动作)
可以直接调某一组件的doAction方法:comp.doAction()触发组件特有动作。 const values = await form.doAction( { type: 'submit' }, form.props.data, true );
也可以通过onEvent配置组件特有动作(CmptAction)去触发对应组件的特有动作
自定义的渲染器 props 会下发一个非常有用的 env 对象。这个 env 有以下功能方法:
* env.fetcher 可以用来做 ajax 请求如: this.props.env.fetcher('xxxAPi', this.props.data).then((result) => console.log(result))
* env.confirm 确认框,返回一个 promise 等待用户确认如: this.props.env.confirm('你确定要这么做?').then((confirmed) => console.log(confirmed))
* env.alert 用 Modal 实现的弹框,个人觉得更美观。
* env.notify toast 某个消息 如: this.props.env.notify("error", "出错了")
* env.jumpTo 页面跳转。
大部分组件都是直接继承 RendererProps,里面包含渲染组件所需的常用属性. 例如:export interface PageProps extends RendererProps
amis-editor注册自定义组件
比如antd按钮组件:

方法一:这里'amis-widget'的registerAmisEditorPlugin, registerRendererByType分别注册plugin插件和renderer渲染器。
src/plugins/AntdButton.tsx:
import type {BaseEventContext, RendererPluginEvent} from 'amis-editor-core';
import {getSchemaTpl} from 'amis-editor-core';
import {getEventControlConfig} from 'amis-editor/lib/renderer/event-control/helper';
import {Button, ButtonProps} from 'antd';
import React from 'react';export class AntdButtonPlugin {rendererName = 'antd-button';$schema = '/schemas/UnkownSchema.json';name = '按钮';description = 'Ant Design按钮预设模板';tags = ['Ant Design'];icon = 'fa fa-square';scaffold = {type: 'antd-button',content: 'Antd 按钮',block: false,danger: false,disabled: false,ghost: false,shape: 'default',size: 'middle',buttonType: 'primary'};previewSchema = {...this.scaffold};panelTitle = '按钮';events: RendererPluginEvent[] = [{eventName: 'onClick',eventLabel: '按钮点击',description: '按钮点击时触发',defaultShow: true}];panelBodyCreator = (context: BaseEventContext) => {const id = context.id;const manager = (window as any).store.editorManager;return getSchemaTpl('tabs', [{title: '基础',body: [{type: 'input-text',name: 'content',label: '按钮内容',value: 'Antd 按钮'},{type: 'switch',name: 'block',label: '将按钮宽度调整为其父宽度的选项',value: false},{type: 'switch',name: 'danger',label: '危险按钮',value: false},{type: 'switch',name: 'disabled',label: '禁用按钮',value: false},{type: 'switch',name: 'ghost',label: '幽灵属性',value: false},{type: 'input-text',name: 'href',label: '点击跳转的地址',value: undefined},{type: 'select',name: 'shape',label: '按钮形状',value: 'default',options: [{label: '默认',value: 'default'},{label: '圆形',value: 'circle'},{label: '圆弧',value: 'round'}]},{type: 'select',name: 'size',label: '按钮大小',value: 'middle',options: [{label: 'large',value: 'large'},{label: 'middle',value: 'middle'},{label: 'small',value: 'small'}]},{type: 'select',name: 'buttonType',label: '按钮类型',value: 'primary',options: [{label: '主要按钮',value: 'primary'},{label: '虚线按钮',value: 'dashed'},{label: '链接按钮',value: 'link'},{label: '文本按钮',value: 'text'},{label: '默认按钮',value: 'default'}]}]},{title: '事件',className: 'p-none',body: [getSchemaTpl('eventControl', {name: 'onEvent',...getEventControlConfig(manager, context)})]}]);};
}/**onClick={onClick? e => new Function(`return ${onClick}`)()(e): function onClick(e) {console.log('click');}}*/export function AntdButton({content,block,danger,disabled,ghost,href,shape,size,buttonType,onClick
}: ButtonProps & {buttonType: ButtonProps['type']; onClick: string}) {const type = buttonType;return (<Buttondanger={danger || false}disabled={disabled || false}type={type || 'primary'}block={block || false}ghost={ghost || false}href={href || undefined}shape={shape || 'default'}size={size || 'middle'}>{content || 'Antd 按钮'}</Button>);
}
src/plugins/index.ts中进行plugin注册:
//@ts-ignore
import {registerAmisEditorPlugin, registerRendererByType} from 'amis-widget';// import {registerEditorPlugin} from 'amis-editor';
// import {AntdCalendarPlugin, AntdCalendar} from './AntdCalendar';
// registerEditorPlugin(AntdCalendarPlugin)import './AntdCalendar';import {AntdButtonPlugin, AntdButton} from './AntdButton';
import {AntdDropdownPlugin, AntdDropdown} from './AntdDropdown';
import {ProCRUDPlugin, ProCRUD} from './ProCRUD';
import {ChartPiePlugin, ChartPie} from './ChartPie';
import {ChartScatterPlugin, ChartScatter} from './ChartScatter';
import {ChartMapPlugin, ChartMap} from './ChartMap';enum Usage {renderer = 'renderer',formitem = 'formitem',options = 'options'
}
enum Framework {react = 'react',vue2 = 'vue2',vue3 = 'vue3',jquery = 'jquery'
}const plugins = [{type: 'antd-button',plugin: AntdButtonPlugin,component: AntdButton},{type: 'antd-dropdown',plugin: AntdDropdownPlugin,component: AntdDropdown},{type: 'pro-crud',plugin: ProCRUDPlugin,component: ProCRUD},{type: 'chart-pie',plugin: ChartPiePlugin,component: ChartPie},{type: 'chart-scatter',plugin: ChartScatterPlugin,component: ChartScatter},{type: 'chart-map',plugin: ChartMapPlugin,component: ChartMap},
];export default () => {plugins.forEach(({type, plugin, component}) => {registerAmisEditorPlugin(plugin);registerRendererByType(component, {type,usage: Usage.renderer,weight: 99,framework: Framework.react});});
};
方法二:采用amis-editor的registerEditorPlugin注册plugin插件。 amis的@Renderer 注册renderer渲染器

src/plugins/AntdCalendar.tsx:
import {Calendar, CalendarProps} from 'antd';
import React from 'react';
import {Renderer, RendererProps} from 'amis';
import {BasePlugin, registerEditorPlugin} from 'amis-editor';export class AntdCalendarPlugin extends BasePlugin{rendererName = 'antd-calendar';$schema = '/schemas/UnkownSchema.json';name = '日历';description = 'Ant Design日历预设模板';tags = ['Ant Design'];icon = 'fa fa-calendar';scaffold = {type: 'antd-calendar',fullscreen: false};previewSchema = {...this.scaffold};panelTitle = '日历';panelControls = [{type: 'switch',name: 'fullscreen',label: '是否全屏',value: false}];
}// @Renderer({
// type: 'antd-calendar',
// name: 'antd-calendar',
// autoVar: true
// })
// export class AntdCalendar extends React.Component<RendererProps> {
// render() {
// const {fullscreen} = this.props;
// return <Calendar fullscreen={fullscreen || false} />;
// }
// }export function AntdCalendar({fullscreen}: RendererProps) {return <Calendar fullscreen={fullscreen || false} />;
}
Renderer({type: 'antd-calendar',name: 'antd-calendar',autoVar: true
})(AntdCalendar);registerEditorPlugin(AntdCalendarPlugin);
相关文章:
amis-editor 注册自定义组件
建议先将amis文档从头到尾,仔细看一遍。 参考:amis - 低代码前端框架 amis 的渲染过程是将 json 转成对应的 React 组件。先通过 json 的 type 找到对应的 Component,然后把其他属性作为 props 传递过去完成渲染。 import * as React from …...
(上位机APP开发)调用华为云命令API接口给设备下发命令
一、功能说明 通过调用华为云IOT提供的命令下发API接口,实现下面界面上相同的功能。调用API接口给设备下发命令。 二、JavaScript代码 function sendUnlockCommand() {var requestUrl = "https://9bcf4cfd30.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/60…...
排序算法系列一:选择排序、插入排序 与 希尔排序
目录 零、说在前面 一、理论部分 1.1:选择排序 1.1.1:算法解读: 1.1.2:时间复杂度 1.1.3:优缺点: 1.1.4:代码: 1.2:插入排序 1.2.1:算法解读&#x…...
【快速排序】| 详解快速排序 力扣912
🎗️ 主页:小夜时雨 🎗️专栏:快速排序 🎗️如何活着,是我找寻的方向 目录 1. 题目解析2. 代码 1. 题目解析 题目链接: https://leetcode.cn/problems/sort-an-array/ 我们上道题讲过快速排序的核心代码&a…...
游戏推荐: 植物大战僵尸杂交版
下载地址网上一搜就有. 安装就能玩. 2是显血. 4显示植物血, 5是加速. 都是左手主键盘的按钮, 再按是取消. 比较刺激: ps: 设置里面还能打开自动收集阳光和金币....
微调和rag的区别?
微调和RAG(Retrieval-Augmented Generation)在多个维度上存在显著的区别。以下是它们之间的主要差异: 1. **知识维度**: - RAG对知识的更新时间和经济成本更低。它不需要训练,只需要更新数据库即可。 - RAG对知识的掌控…...
CVPR讲座总结(二)-探索图像生成基础模型的最新进展探索多模态代理的最新进展:从视频理解到可操作代理
引言 在CVPR24上的教程中,微软高级研究员Linjie Li为我们带来了多模态代理的深入探索。这些代理通过整合多模态专家和大语言模型(LLM)来增强感知、理解和生成能力。本文总结了Linjie Li的讲座内容,重点介绍了多模态记忆、可操作代…...
为什么要禁用透明大页面
在安装CDH(Clouderas Distribution Including Apache Hadoop)环境时,禁用透明大页面(Transparent HugePages,THP)是一个推荐的系统优化步骤。以下是禁用透明大页面的一些原因: 1. **性能影响**…...
Element 页面滚动表头置顶
在开发后台管理系统时,表格是最常用的一个组件,为了看数据方便,时常需要固定表头。 如果页面基本只有一个表格区域,我们可以根据屏幕的高度动态的计算出一个值,给表格设定一个固定高度,这样表头就可以固定…...
对于CDA一级考试该咋准备??!
一、了解考试内容和结构 CDA一级考试主要涉及的内容包括:数据分析概述与职业操守、数据结构、数据库基础与数据模型、数据可视化分析与报表制作、Power BI应用、业务数据分析与报告编写等。 CDA Level Ⅰ 认证考试大纲:https://edu.cda.cn/group/4/thread/174335 …...
如何使用PHP和Selenium快速构建自己的网络爬虫系统
近年来,随着互联网的普及,网络爬虫逐渐成为了信息采集的主要手段之一,然而,常规的爬虫技术不稳定、难以维护,市面上的纯web网页爬虫也只能在静态页面上进行操作。而php结合selenium可达到动态爬虫的效果,具…...
intellij idea安装R包ggplot2报错问题求解
1、intellij idea安装R包ggplot2问题 在我上次解决图形显示问题后,发现安装ggplot2包时出现了问题,这在之前高版本中并没有出现问题, install.packages(ggplot2) ERROR: lazy loading failed for package lifecycle * removing C:/Users/V…...
【C++】初识C++(一)
一.什么是C C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度 的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object o…...
【智能算法】目标检测算法
目录 一、目标检测算法分类 二、 常见目标检测算法及matlab代码实现 2.1 R-CNN 2.1.1 定义 2.1.2 matlab代码实现 2.2 Fast R-CNN 2.2.1 定义 2.2.2 matlab代码实现 2.3 Faster R-CNN 2.3.1 定义 2.3.2 matlab代码实现 2.4 YOLO 2.4.1 定义 2.4.2 matlab代码实现…...
python 中 json.load json.loadd json.dump json.dumps 详解
在Python中,json 模块提供了用于处理JSON数据的函数。json.load(), json.loads(), json.dump(), 和 json.dumps() 是这个模块中用于序列化和反序列化JSON数据的主要函数。下面是它们之间的区别详解: json.load() 作用:从一个文件对象&#x…...
【UE 网络】专用服务器和多个客户端加入游戏会话的过程,以及GameMode、PlayerController、Pawn的创建流程
目录 0 引言1 多人游戏会话1.1 Why?为什么要有这个1.2 How?怎么使用? 2 加入游戏会话的流程总结 🙋♂️ 作者:海码007📜 专栏:UE虚幻引擎专栏💥 标题:【UE 网络】在网络…...
磁盘分区工具(fdisk 和 parted)区别及操作笔记
fdisk 和 parted 都是 Linux 系统中用于磁盘分区的工具。 两者主要区别: 支持的分区表类型: fdisk 主要支持 MBR分区表,MBR分区表支持的硬盘单个分区最大容量为2TB,最多可以有4个主分区。parted 支持 MBR分区表 和 GPT分区表&…...
VisualStudio2019受支持的.NET Core
1.VS Studio2019受支持的.NET Core? 适用于 Visual Studio 的 .NET SDK 下载 (microsoft.com) Visual Studio 2019 默认并不直接支持 .NET 6 及以上版本。要使用 .NET 6 或更高版本,你需要在 Visual Studio 2019 中采取额外步骤,比如安装相应…...
Java——IO流(二)-(1/7):字符流-FileReader、FileWriter、字符输出流的注意事项(构造器及常用方法、小结)
目录 文件字符输入流-读字符数据进来 介绍 构造器及常用方法 实例演示 文件字符输出流-写字符数据出去 介绍、构造器及常用方法 实例演示 字符输出流使用时的注意事项 小结 文件字符输入流-读字符数据进来 介绍 FileReader(文件字符输入流) 作…...
Spring循环依赖问题——从源码画流程图
文章目录 关键代码相关知识为什么要使用二级缓存为什么要使用三级缓存只使用两个缓存的问题不能解决构造器循环依赖为什么多例bean不能解决循环依赖问题初始化后代理对象赋值给原始对象解决循环依赖SpringBoot开启循环依赖 循环依赖 在线流程图 关键代码 从缓存中查询getSingl…...
360周鸿祎:智能体技术破圈,引领产业全面重构与独角兽机遇
【导语:在2026中关村论坛年会全球独角兽企业大会上,360集团创始人周鸿祎围绕“龙虾”等新一代智能体技术,阐述其带来的产业变革机遇,涉及互联网、软件等多领域重构,有望催生大量独角兽企业。】智能体技术“破圈”&…...
如何永久保存微信聊天记录?免费开源工具WeChatMsg完整指南
如何永久保存微信聊天记录?免费开源工具WeChatMsg完整指南 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/W…...
如何用免费AI助手提升3倍编码效率?DeepSeek-Coder-V2全解析
如何用免费AI助手提升3倍编码效率?DeepSeek-Coder-V2全解析 【免费下载链接】DeepSeek-Coder-V2 项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Coder-V2 在AI编程工具层出不穷的今天,开发者面临着一个关键选择:是为商…...
保姆级教程:用YOLOv8+PyQt5打造你的番茄成熟度检测桌面应用(附完整源码与数据集)
从零构建番茄成熟度检测桌面应用:YOLOv8与PyQt5深度整合实战 在农业智能化浪潮中,计算机视觉技术正逐步改变传统农业生产方式。以番茄种植为例,成熟度判断直接影响采摘效率和经济效益。本文将带您完整实现一个结合YOLOv8目标检测与PyQt5图形界…...
Vite - vite.config.js 的一些配置(base、resolve、server)
一、base 1、基本介绍 base 用于设置开发或生产环境服务的公共基础路径 类型:string默认值:/2、演示 部署在根路径 base: /// 例如,https://example.com/<!-- 此时生成的 HTML 中的资源引用会变为如下 --><script src"/assets/…...
SDMatte数据库课程设计案例:电商商品图库智能管理系统
SDMatte数据库课程设计案例:电商商品图库智能管理系统 1. 项目背景与需求分析 电商平台每天需要处理大量商品图片,传统人工修图方式存在效率低、成本高、风格不统一等问题。某服装电商平台希望开发一套智能图库管理系统,能够自动完成商品图…...
Artichoke 未来展望:这个创新 Ruby 实现的路线图和愿景 [特殊字符]
Artichoke 未来展望:这个创新 Ruby 实现的路线图和愿景 🚀 【免费下载链接】artichoke 💎 Artichoke is a Ruby made with Rust 项目地址: https://gitcode.com/gh_mirrors/ar/artichoke Artichoke 是一个用 Rust 编写的创新 Ruby 实现…...
Pixel Dream Workshop 在电商领域的应用:一键生成商品场景图
Pixel Dream Workshop 在电商领域的应用:一键生成商品场景图 1. 电商商品图的痛点与机遇 电商行业有个公开的秘密:商品图片的制作成本往往比想象中高得多。我们曾合作过的一家服装电商,每月仅模特拍摄费用就超过20万元,这还不包…...
Rockchip Android 12编译踩坑记:手把手教你修改BoardConfig.mk生成userdata.img
Rockchip Android 12编译实战:从BoardConfig.mk修改到userdata.img生成的避坑指南 第一次在Rockchip平台上编译Android 12系统时,我遇到了一个令人抓狂的问题——编译过程看似顺利,但生成的固件烧写到设备后,系统始终无法正常启动…...
从“跟网”到“构网”:新能源并网变流器的稳定性为何一个怕强一个怕弱?用大白话讲清失稳机理
新能源并网变流器的"性格差异":为什么构网型怕强电网,跟网型怕弱电网? 想象一下,你正在指挥两支风格迥异的交响乐团——一支严格遵循指挥家的每个动作(跟网型变流器),另一支则自带节奏…...
