6.0 KiB
Adminive Backend-Driven Example
Adminive 使用 C++20 模板、成员指针和 magic_enum 生成后端描述。前端只负责渲染 /admin/amis 返回的 amis Schema,不包含业务字段、枚举映射、列顺序、排序规则或配置联动。
数据名称与界面名称
字段成员名继续作为稳定的数据名称:
struct Radio_State {
Radio_Mode mode{Radio_Mode::receive};
};
界面标签、列表表头和枚举显示名称在描述端单独配置:
ADMINIVE_FIELD_LABEL(T, mode, "运行模式")
.list_label("模式")
.sortable()
.enum_label<Radio_Mode::receive>("接收")
.enum_label<Radio_Mode::transmit>("发送")
.enum_label<Radio_Mode::duplex>("双工")
.enum_label<Radio_Mode::maintenance>("维护");
接口仍返回:
{
"mode": "duplex"
}
表格显示为“模式:双工”。magic_enum 负责枚举值发现和稳定字符串生成,.enum_label 只覆盖界面文字。
列顺序与用户排序
字段的默认列顺序和是否允许按值排序由后端描述:
ADMINIVE_FIELD_LABEL(T, buffer_count, "缓冲区数量")
.list_label("缓冲区")
.order(10)
.sortable();
对象描述可以定义默认排序:
object<T>(/* fields */)
.default_sort("buffer_count");
用户点击可排序表头时,前端发送 orderBy 和 orderDir,后端只接受声明为 .sortable() 的字段。
用户也可以拖拽调整行顺序:
object<T>(/* fields */)
.user_reorderable();
该配置会生成 draggable 和 saveOrderApi,后端提供 POST <resource>/order 保存当前运行期顺序。列选择菜单支持拖拽调整列先后,默认由 .column_reorderable() 控制。
CRUD 中文文字
CRUD 固定文字也在对象描述中配置:
object<T>(/* fields */)
.label("无线电状态")
.id_label("编号")
.create_label("新增")
.edit_label("编辑")
.delete_label("删除")
.actions_label("操作")
.confirm_label("确认修改")
.view_status_label("查看状态 ▾")
.management_label("无线电状态列表");
树状配置与持久化
嵌套 C++ 对象直接表达树状配置:
struct Radio_Service_Config {
std::string profile_name{"Primary Radio Profile"};
Radio_Mode mode{Radio_Mode::receive};
Endpoint_Config primary_endpoint{};
Endpoint_Config backup_endpoint{};
Appearance_Config appearance{};
};
嵌套对象在界面中生成 fieldset。字符串、整数、浮点数、布尔、枚举、日期和颜色分别生成对应控件。联动条件仍由后端定义:
ADMINIVE_FIELD_LABEL(T, backup_endpoint, "备用端点")
.editable()
.visible_on("${mode == 'duplex'}");
配置文件位于:
config/adminive.json
文件保存以下持久化内容:
Application_Config
├── default_device_type
├── radio_service
├── alerts
├── serial_table
└── network_table
程序启动时读取该文件。文件不存在时,使用 C++ 默认值自动创建。配置表单点击“确认修改”后,后端先在副本上完成赋值和校验,再写入临时文件并替换正式配置文件,成功后才替换内存对象。
多态类型表格
不同表格类型通过 C++ 虚函数注册:
class Device_Table_Base {
public:
virtual ~Device_Table_Base() = default;
virtual Device_Table_Type type() const noexcept = 0;
virtual std::string_view type_name() const noexcept = 0;
virtual std::string_view type_label() const noexcept = 0;
virtual Json amis_schema() const = 0;
virtual void bind(httplib::Server& server) = 0;
};
示例实现两个子类:
Serial_Device_Table
Network_Device_Table
界面切换 device_type 后,service.schemaApi 重新向后端请求对应子类的 Schema:
serial -> 串口、波特率、校验位、启用
network -> 地址、协议、超时、TLS
因此切换类型后,表格列、编辑表单、配置表单和接口路径都会一起改变,前端不需要识别任何业务类型。
对象状态
对象状态使用独立类型,并通过成员函数指针注册:
resource.register_status<&Radio_State::status>(2000);
只有用户打开某一行的“查看状态”弹层后,该行状态才开始请求和刷新。颜色由后端状态值返回。
主要接口
GET /admin/amis
GET /admin/status
GET /admin/radio_states
POST /admin/radio_states
PUT /admin/radio_states/{id}
DELETE /admin/radio_states/{id}
GET /admin/radio_states/{id}/status
POST /admin/device_tables/serial/items/order
POST /admin/device_tables/network/items/order
GET /admin/device_tables/schema?type=serial
GET /admin/device_tables/schema?type=network
POST /admin/device_tables/default
POST /admin/config/radio/data
POST /admin/config/alerts/data
POST /admin/device_tables/serial/config/data
POST /admin/device_tables/network/config/data
构建
cmake -S . -B build -G Ninja
cmake --build build
ctest --test-dir build --output-on-failure
.\build\backend\Adminive_Server.exe 9999
源码压缩目标
压缩函数参数顺序:
add_project_zip_target(
target_name
output_file
root_directory
include_list_name
exclude_list_name
)
调用示例:
set(adminive_package_includes
"/backend/"
"/cmake/"
"/config/"
"/frontend/"
"/scripts/"
"/CMakeLists.txt"
"/README.md"
)
set(adminive_package_excludes
"/.git/"
"/.idea/"
"/build/"
"/cmake-build-*/"
"/doc/"
"/node_modules/"
"/frontend/frontend.zip"
"/frontend_dist/"
"*.tsbuildinfo"
"*.zip"
)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/PackageZip.cmake")
add_project_zip_target(package_zip "${CMAKE_CURRENT_LIST_DIR}/Adminive.zip" "${CMAKE_CURRENT_LIST_DIR}" adminive_package_includes adminive_package_excludes)
函数只扫描包含列表指定的目录和文件,然后相对于根目录应用排除列表。