Files
Adminive/README.md
T
2026-08-06 17:41:21 +08:00

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");

用户点击可排序表头时,前端发送 orderByorderDir,后端只接受声明为 .sortable() 的字段。

用户也可以拖拽调整行顺序:

object<T>(/* fields */)
    .user_reorderable();

该配置会生成 draggablesaveOrderApi,后端提供 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)

函数只扫描包含列表指定的目录和文件,然后相对于根目录应用排除列表。