画廊添加

This commit is contained in:
2026-08-08 11:57:08 +08:00
parent 0ba5c56b6e
commit c8b82f640e
14 changed files with 282 additions and 23 deletions
+20
View File
@@ -96,6 +96,26 @@ const auto page = composition_view("device_page", compose::vertical(
`slot` 引用已经生成的 Form/Table/Status 等组件,后端仍然决定语义组合,AMIS Adapter 只负责把组合翻译成具体 AMIS JSON。前端可以进一步决定 CSS、断点、列宽等纯视觉细节。
### 示例组件画廊
完整示例首页现在包含“组件画廊”页签。画廊不是前端写死的组件清单,而是后端使用 Adminive 自身 Descriptor、View Schema、Composition View 和 Collection Service 生成,用于直接检查当前支持能力的实际 AMIS 效果。
字段画廊覆盖 `automatic``text``multiline_text``number``boolean``select``date``color``std::optional`、只读 static、条件联动和嵌套对象;同一表单同时覆盖 `vertical``horizontal``grid``flow``group``card``tabs`。表格画廊提供真实的新增、编辑、删除、刷新、列控制、搜索、筛选、排序和拖拽顺序接口。第三个页签直接展示 Composition 层的 `heading``horizontal``grid``flow``group``card``tabs``slot`
画廊资源同时提供正常 Adminive HTTP 接口:
```text
/admin/gallery/config/descriptor
/admin/gallery/config/view
/admin/gallery/config/data
/admin/gallery/config/amis
/admin/gallery/items
/admin/gallery/items/{id}
/admin/gallery/items/order
```
`flow` 在 AMIS Adapter 中翻译成可换行 Flex,每个 `items` 元素都是显式 `container` renderer。这样 Flow 的子 Card/Form Control 不会以无 `type` 的包装对象进入 AMIS renderer tree。
## 后端分层
`backend/library` 是库级协议层,只有头文件,不依赖也不链接任何具体 JSON、HTTP Server、枚举反射或结构体反射实现。它依赖项目内的 `third_party/Structive`,由 Structive 负责 intrinsic property capability、Schema 和 synchronizationAdminive 自身包含 descriptor、JSON 适配协议、AMIS schema、状态协议、managed adapter 和抽象 `Resource_Service`
+18
View File
@@ -472,12 +472,21 @@ Json make_amis_view_node(const View_Node& node, const Json& descriptor_json, std
Json items = json_array<Json>();
for(const auto& child : node.children) {
Json item = json_object<Json>();
json_set(item, "type", "container");
json_set(item, "body", make_amis_view_body<Json, T>(child, descriptor_json, permission, prefix));
Json item_style = json_object<Json>();
json_set(item_style, "flex", "1 1 280px");
json_set(item, "style", std::move(item_style));
json_append(items, std::move(item));
}
Json result = json_object<Json>();
json_set(result, "type", "flex");
json_set(result, "direction", "row");
json_set(result, "items", std::move(items));
Json style = json_object<Json>();
json_set(style, "flexWrap", "wrap");
json_set(style, "gap", "12px");
json_set(result, "style", std::move(style));
apply_view_node_options(result, node, prefix);
return result;
}
@@ -606,12 +615,21 @@ Json make_amis_composition_node(const Composition_Node& node, const std::map<std
Json items = json_array<Json>();
for(const auto& child : node.children) {
Json item = json_object<Json>();
json_set(item, "type", "container");
json_set(item, "body", make_amis_composition_body<Json>(child, slots));
Json item_style = json_object<Json>();
json_set(item_style, "flex", "1 1 280px");
json_set(item, "style", std::move(item_style));
json_append(items, std::move(item));
}
Json result = json_object<Json>();
json_set(result, "type", "flex");
json_set(result, "direction", "row");
json_set(result, "items", std::move(items));
Json style = json_object<Json>();
json_set(style, "flexWrap", "wrap");
json_set(style, "gap", "12px");
json_set(result, "style", std::move(style));
apply_composition_node_options(result, node);
return result;
}
+1 -1
View File
@@ -40,7 +40,7 @@ add_library(Adminive_Default INTERFACE)
set_target_properties(Adminive_Default PROPERTIES EXPORT_NAME Default)
add_library(Adminive::Default ALIAS Adminive_Default)
target_link_libraries(Adminive_Default INTERFACE Adminive::Httplib Adminive::Nlohmann Adminive::MagicEnum)
add_library(Adminive_Example STATIC "${CMAKE_CURRENT_LIST_DIR}/src/example.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/config_store.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/device_tables.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/server_app.cpp")
add_library(Adminive_Example STATIC "${CMAKE_CURRENT_LIST_DIR}/src/example.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/config_store.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/device_tables.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/gallery.cpp" "${CMAKE_CURRENT_LIST_DIR}/src/server_app.cpp")
target_include_directories(Adminive_Example PUBLIC "${CMAKE_CURRENT_LIST_DIR}/src")
target_link_libraries(Adminive_Example PUBLIC Adminive::Default)
add_executable(Adminive_Server "${CMAKE_CURRENT_LIST_DIR}/src/main.cpp")
+39
View File
@@ -5,6 +5,8 @@
#include "adminive/adapters/nlohmann_json.hpp"
#include <chrono>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
namespace adminive::example {
@@ -114,6 +116,43 @@ struct Application_Config {
Serial_Table_Config serial_table{};
Network_Table_Config network_table{};
};
struct Gallery_Config {
struct Even_Validator {
static constexpr int multiple_of = 2;
static constexpr std::string_view message = "值必须是偶数";
void operator()(const int& value) const {
if(value % 2 != 0) {
throw std::invalid_argument("value must be even");
}
}
};
std::string automatic_text{"自动推导为文本输入"};
std::string text_value{"单行文本"};
std::string multiline_value{"第一行\n第二行"};
Range_Value<int, 0, 100> number_value{42};
Validated_Value<int, Even_Validator> even_number{4};
bool boolean_value{true};
Log_Level select_value{Log_Level::info};
std::string date_value{"2026-08-08"};
std::string color_value{"#2563eb"};
std::optional<std::string> optional_text{"这个字段可以清空"};
bool show_conditional{true};
std::string conditional_text{"这个字段由上面的开关控制显示"};
std::string read_only_text{"只读字段使用 static 展示,不生成编辑控件"};
Endpoint_Config nested{};
};
enum class Gallery_Category {
primary,
secondary,
experimental
};
struct Gallery_Row {
std::string name;
Gallery_Category category{Gallery_Category::primary};
Range_Value<int, 0, 100> score{50};
bool enabled{true};
std::string color{"#2563eb"};
};
struct Serial_Device_Row {
std::string port_name;
Range_Value<int, 1200, 4000000> baud_rate{115200};
@@ -86,6 +86,34 @@ struct Type_View_Descriptor<example::Network_Table_Config> {
}
};
template <>
struct Type_Descriptor<example::Gallery_Config> {
static auto get() {
using T = example::Gallery_Config;
return object<T>("gallery_config", ADMINIVE_FIELD_LABEL(T, automatic_text, "自动推导文本").editable().description("不指定 control,由字段类型自动选择输入控件"), ADMINIVE_FIELD_LABEL(T, text_value, "单行文本").editable().text_input().description("显式 text_input"), ADMINIVE_FIELD_LABEL(T, multiline_value, "多行文本").editable().multiline_text().description("textarea 多行输入"), ADMINIVE_FIELD_LABEL(T, number_value, "范围数字").editable().number_input().required().description("Range_Value 自动生成最小值和最大值"), ADMINIVE_FIELD_LABEL(T, even_number, "偶数校验").editable().number_input().required().description("Validated_Value 自动生成 step 和校验提示"), ADMINIVE_FIELD_LABEL(T, boolean_value, "布尔开关").editable().boolean_input(), ADMINIVE_FIELD_LABEL(T, select_value, "枚举选择").editable().select_input().enum_label<example::Log_Level::trace>("跟踪").enum_label<example::Log_Level::debug>("调试").enum_label<example::Log_Level::info>("信息").enum_label<example::Log_Level::warning>("警告").enum_label<example::Log_Level::error>("错误"), ADMINIVE_FIELD_LABEL(T, date_value, "日期选择").editable().date_input(), ADMINIVE_FIELD_LABEL(T, color_value, "颜色选择").editable().color_input(), ADMINIVE_FIELD_LABEL(T, optional_text, "可清空文本").editable().text_input().description("std::optional 自动允许清空"), ADMINIVE_FIELD_LABEL(T, show_conditional, "显示联动字段").editable().boolean_input(), ADMINIVE_FIELD_LABEL(T, conditional_text, "联动文本").editable().text_input().visible_on("${$self.show_conditional}"), ADMINIVE_FIELD_LABEL(T, read_only_text, "只读展示").description("没有 editable,因此编辑表单中使用 static 展示"), ADMINIVE_FIELD_LABEL(T, nested, "嵌套对象").editable().description("对象字段自动展开为 fieldset")).label("Adminive 控件画廊");
}
};
template <>
struct Type_View_Descriptor<example::Gallery_Config> {
static Form_View edit() {
using T = example::Gallery_Config;
return edit_form<T>(vertical(group("文本输入", horizontal(use<&T::automatic_text>(), use<&T::text_value>()), use<&T::multiline_value>(), use<&T::optional_text>()), group("数字与选择", grid(4, use<&T::number_value>(), use<&T::even_number>(), use<&T::boolean_value>(), use<&T::select_value>())), flow(card("日期", use<&T::date_value>()), card("颜色", use<&T::color_value>())), tabs(tab("条件联动", use<&T::show_conditional>(), use<&T::conditional_text>()), tab("嵌套对象", use<&T::nested>()), tab("只读字段", use<&T::read_only_text>())))).titled("字段控件与表单布局").submit("保存画廊数据");
}
};
template <>
struct Type_Descriptor<example::Gallery_Row> {
static auto get() {
using T = example::Gallery_Row;
return object<T>("gallery_row", ADMINIVE_FIELD_LABEL(T, name, "名称").creatable().editable().required().text_input(), ADMINIVE_FIELD_LABEL(T, category, "分类").creatable().editable().required().select_input().enum_label<example::Gallery_Category::primary>("主要").enum_label<example::Gallery_Category::secondary>("次要").enum_label<example::Gallery_Category::experimental>("实验"), ADMINIVE_FIELD_LABEL(T, score, "评分").creatable().editable().required().number_input(), ADMINIVE_FIELD_LABEL(T, enabled, "启用").creatable().editable().boolean_input(), ADMINIVE_FIELD_LABEL(T, color, "颜色").creatable().editable().color_input()).label("画廊表格项");
}
};
template <>
struct Type_View_Descriptor<example::Gallery_Row> {
static Table_View table() {
using T = example::Gallery_Row;
return table_view<T>(column<&T::name>("名称").sort().search().fix(Table_Fixed::left), column<&T::category>("分类").sort().filter(), column<&T::score>("评分").sort(), column<&T::enabled>("启用").sort().filter(), column<&T::color>("颜色")).titled("表格与操作画廊").id_title("编号").create_title("新增一行").edit_title("编辑").delete_title("删除").actions_title("操作").confirm_title("保存修改").delete_confirmation("确定删除这条画廊数据吗?").default_sort("score", true).reorderable();
}
};
template <>
struct Type_Descriptor<example::Application_Config> {
static auto get() {
using T = example::Application_Config;
+41
View File
@@ -0,0 +1,41 @@
#include "gallery.hpp"
#include <map>
#include <string>
#include <utility>
namespace adminive::example {
namespace {
Json make_gallery_note(std::string title, std::string text) {
return Json{{"type", "tpl"}, {"tpl", "<strong>" + std::move(title) + "</strong><div class=\"text-muted\" style=\"margin-top:6px\">" + std::move(text) + "</div>"}};
}
}
Component_Gallery::Component_Gallery() : config_resource_(config_, "/admin/gallery/config"), rows_("/admin/gallery/items", make_rows()) {}
Json Component_Gallery::amis_schema() const {
const Json form = config_resource_.amis_schema();
const Json table = rows_.amis_schema();
const auto composition = composition_view("component_gallery", compose::tabs(compose::tab("字段控件与布局", compose::slot("form")), compose::tab("表格与操作", compose::slot("table")), compose::tab("组合能力", compose::vertical(compose::heading("Composition View 组合画廊").accent("#2563eb"), compose::group("横向与网格", compose::horizontal(compose::card("Horizontal A", compose::slot("horizontal_a")), compose::card("Horizontal B", compose::slot("horizontal_b"))), compose::grid(3, compose::card("Grid 1", compose::slot("grid_1")), compose::card("Grid 2", compose::slot("grid_2")), compose::card("Grid 3", compose::slot("grid_3")))), compose::group("Flow 自动换行", compose::flow(compose::card("Flow A", compose::slot("flow_a")), compose::card("Flow B", compose::slot("flow_b")), compose::card("Flow C", compose::slot("flow_c")), compose::card("Flow D", compose::slot("flow_d")))).collapsible_when())))).titled("Adminive 组件画廊");
std::map<std::string, Json> slots;
slots.emplace("form", form);
slots.emplace("table", table.at("body"));
slots.emplace("horizontal_a", make_gallery_note("horizontal", "两个组件按横向语义组合,具体栅格由 AMIS Adapter 翻译。"));
slots.emplace("horizontal_b", make_gallery_note("card", "Card 是组合层容器,不和字段 Presentation 混在一起。"));
slots.emplace("grid_1", make_gallery_note("grid", "后端定义逻辑列数。"));
slots.emplace("grid_2", make_gallery_note("slot", "Slot 可以嵌入 Form、Table、Status 或其他后端组件。"));
slots.emplace("grid_3", make_gallery_note("group", "Group 可以折叠并继续嵌套其他组合。"));
slots.emplace("flow_a", make_gallery_note("flow", "Flow 使用可换行 Flex,子项都是合法 container renderer。"));
slots.emplace("flow_b", make_gallery_note("tabs", "整个画廊本身就是 Composition tabs。"));
slots.emplace("flow_c", make_gallery_note("vertical", "纵向组合只表达顺序,不泄漏具体 CSS。"));
slots.emplace("flow_d", make_gallery_note("heading", "标题也是组合节点,可以独立使用。"));
return to_amis_composition_schema<Json>(composition, slots);
}
void Component_Gallery::bind(httplib::Server& server) {
config_resource_.bind(server);
rows_.bind(server);
}
std::vector<Gallery_Row> Component_Gallery::make_rows() {
return {
Gallery_Row{"Alpha", Gallery_Category::primary, Range_Value<int, 0, 100>{92}, true, "#2563eb"},
Gallery_Row{"Beta", Gallery_Category::secondary, Range_Value<int, 0, 100>{76}, true, "#16a34a"},
Gallery_Row{"Gamma", Gallery_Category::experimental, Range_Value<int, 0, 100>{58}, false, "#d97706"}
};
}
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "example_descriptors.hpp"
#include <vector>
namespace adminive::example {
class Component_Gallery {
public:
Component_Gallery();
Json amis_schema() const;
void bind(httplib::Server& server);
private:
static std::vector<Gallery_Row> make_rows();
Managed_Value<Gallery_Config> config_;
Http_Resource<Gallery_Config> config_resource_;
Http_Collection_Resource<Gallery_Row> rows_;
};
}
+4 -1
View File
@@ -35,12 +35,14 @@ int Server_App::run() {
}
Json Server_App::amis_schema() const {
Json tabs = Json::array();
const Json gallery_schema = gallery_.amis_schema();
const Json states_schema = state_resource_.amis_schema();
tabs.push_back(Json{{"title", "组件画廊"}, {"body", gallery_schema.at("body")}});
tabs.push_back(Json{{"title", "无线电状态"}, {"body", states_schema.at("body")}});
tabs.push_back(Json{{"title", "无线电服务配置"}, {"body", service_config_resource_.amis_schema()}});
tabs.push_back(Json{{"title", "告警配置"}, {"body", alert_config_resource_.amis_schema()}});
tabs.push_back(Json{{"title", "多类型设备"}, {"body", device_tables_.amis_schema()}});
return Json{{"type", "page"}, {"title", "Adminive 完整示例"}, {"subTitle", "列表中文映射、后端排序、拖拽顺序、多态类型表格和持久化配置均由后端定义"}, {"body", Json{{"type", "tabs"}, {"tabs", std::move(tabs)}}}};
return Json{{"type", "page"}, {"title", "Adminive 完整示例"}, {"subTitle", "组件画廊、字段控件、组合布局、表格操作和持久化配置均由后端定义"}, {"body", Json{{"type", "tabs"}, {"tabs", std::move(tabs)}}}};
}
std::vector<Radio_State> Server_App::make_radio_states() {
return {
@@ -54,6 +56,7 @@ void Server_App::bind_routes() {
service_config_resource_.bind(server_);
alert_config_resource_.bind(server_);
device_tables_.bind(server_);
gallery_.bind(server_);
server_.Get("/admin/amis", [this](const httplib::Request&, httplib::Response& response) {
write_http_json(response, Json{{"status", 0}, {"msg", ""}, {"data", amis_schema()}});
});
+2
View File
@@ -1,5 +1,6 @@
#pragma once
#include "device_tables.hpp"
#include "gallery.hpp"
#include <atomic>
#include <chrono>
#include <filesystem>
@@ -20,6 +21,7 @@ private:
Http_Resource<Radio_Service_Config> service_config_resource_;
Http_Resource<Alert_Config> alert_config_resource_;
Device_Table_Manager device_tables_;
Component_Gallery gallery_;
std::chrono::steady_clock::time_point started_at_{std::chrono::steady_clock::now()};
std::atomic<std::uint64_t> poll_sequence_{};
};
+77
View File
@@ -1,4 +1,5 @@
#include "example_descriptors.hpp"
#include "gallery.hpp"
#include <cassert>
#include <string>
#include <string_view>
@@ -25,6 +26,47 @@ const Json* find_named_node(const Json& value, std::string_view name) {
}
return nullptr;
}
void assert_flex_items_renderable(const Json& value) {
if(value.is_object()) {
if(value.contains("type") && value.at("type") == "flex") {
assert(value.contains("items"));
for(const auto& item : value.at("items")) {
assert(item.is_object());
assert(item.contains("type"));
}
}
for(const auto& [key, child] : value.items()) {
static_cast<void>(key);
assert_flex_items_renderable(child);
}
return;
}
if(value.is_array()) {
for(const auto& child : value) {
assert_flex_items_renderable(child);
}
}
}
const Json* find_labeled_node(const Json& value, std::string_view label) {
if(value.is_object()) {
if(value.contains("label") && value.at("label").is_string() && value.at("label").get<std::string>() == label) {
return &value;
}
for(const auto& [key, child] : value.items()) {
static_cast<void>(key);
if(const Json* result = find_labeled_node(child, label)) {
return result;
}
}
} else if(value.is_array()) {
for(const auto& child : value) {
if(const Json* result = find_labeled_node(child, label)) {
return result;
}
}
}
return nullptr;
}
const Json* find_titled_node(const Json& value, std::string_view title) {
if(value.is_object()) {
if(value.contains("title") && value.at("title").is_string() && value.at("title").get<std::string>() == title) {
@@ -217,6 +259,41 @@ int main() {
assert(resource_form.at("api").at("method") == "post");
assert(resource_form.at("api").at("url") == "/admin/config/radio/data");
assert(resource_form.at("actions").at(1).at("label") == "确认修改");
adminive::example::Component_Gallery gallery;
const auto gallery_schema = gallery.amis_schema();
assert(gallery_schema.at("type") == "panel");
assert_flex_items_renderable(gallery_schema);
const Json* automatic_control = find_named_node(gallery_schema, "automatic_text");
const Json* text_control = find_named_node(gallery_schema, "text_value");
const Json* multiline_control = find_named_node(gallery_schema, "multiline_value");
const Json* number_control = find_named_node(gallery_schema, "number_value");
const Json* even_control = find_named_node(gallery_schema, "even_number");
const Json* boolean_control = find_named_node(gallery_schema, "boolean_value");
const Json* select_control = find_named_node(gallery_schema, "select_value");
const Json* date_gallery_control = find_named_node(gallery_schema, "date_value");
const Json* color_gallery_control = find_named_node(gallery_schema, "color_value");
const Json* optional_control = find_named_node(gallery_schema, "optional_text");
const Json* readonly_control = find_named_node(gallery_schema, "read_only_text");
assert(automatic_control && automatic_control->at("type") == "input-text");
assert(text_control && text_control->at("type") == "input-text");
assert(multiline_control && multiline_control->at("type") == "textarea");
assert(number_control && number_control->at("type") == "input-number");
assert(number_control->at("min") == 0 && number_control->at("max") == 100);
assert(even_control && even_control->at("type") == "input-number" && even_control->at("step") == 2);
assert(boolean_control && boolean_control->at("type") == "switch");
assert(select_control && select_control->at("type") == "select");
assert(date_gallery_control && date_gallery_control->at("type") == "input-date");
assert(color_gallery_control && color_gallery_control->at("type") == "input-color");
assert(optional_control && optional_control->at("clearable") == true);
assert(readonly_control && readonly_control->at("type") == "static");
const Json* save_gallery_button = find_labeled_node(gallery_schema, "保存画廊数据");
const Json* create_gallery_button = find_labeled_node(gallery_schema, "新增一行");
const Json* edit_gallery_button = find_labeled_node(gallery_schema, "编辑");
const Json* delete_gallery_button = find_labeled_node(gallery_schema, "删除");
assert(save_gallery_button && save_gallery_button->at("type") == "submit");
assert(create_gallery_button && create_gallery_button->at("type") == "button");
assert(edit_gallery_button && edit_gallery_button->at("actionType") == "dialog");
assert(delete_gallery_button && delete_gallery_button->at("actionType") == "ajax");
adminive::example::Server_Status server_status;
server_status.server_time = "2026-08-06T00:00:00Z";
server_status.poll_sequence = 7;
@@ -55,6 +55,9 @@ int main() {
assert(form_amis.at("body").at(0).at("type") == "fieldset");
assert(form_amis.at("body").at(1).at("type") == "grid");
assert(form_amis.at("body").at(2).at("type") == "flex");
assert(form_amis.at("body").at(2).at("items").at(0).at("type") == "container");
assert(form_amis.at("body").at(2).at("items").at(1).at("type") == "container");
assert(form_amis.at("body").at(2).at("items").at(0).at("body").at(0).at("type") == "card");
assert(form_amis.at("body").at(3).at("type") == "tabs");
const auto table_view = adminive::describe_table_view<Config>();
adminive::validate_table_view<Config>(table_view);
@@ -73,6 +76,11 @@ int main() {
const auto composition_amis = adminive::to_amis_composition_schema<Json>(composition, {{"form", form_amis}, {"table", Json{{"type", "tpl"}, {"tpl", "table"}}}});
assert(composition_amis.at("body").at(0).at("type") == "tpl");
assert(composition_amis.at("body").at(1).at("type") == "grid");
const auto flow_composition = adminive::composition_view("flow_page", adminive::compose::flow(adminive::compose::slot("left"), adminive::compose::slot("right")));
const auto flow_amis = adminive::to_amis_composition_schema<Json>(flow_composition, {{"left", Json{{"type", "tpl"}, {"tpl", "left"}}}, {"right", Json{{"type", "tpl"}, {"tpl", "right"}}}});
assert(flow_amis.at("body").at(0).at("type") == "flex");
assert(flow_amis.at("body").at(0).at("items").at(0).at("type") == "container");
assert(flow_amis.at("body").at(0).at("items").at(1).at("type") == "container");
const auto table_amis = adminive::to_amis_table_schema<Json, Config>("/configs", table_view);
assert(table_amis.at("type") == "page");
assert(table_amis.at("body").at(0).at("columns").at(1).at("searchable") == true);
+1 -1
View File
@@ -1,5 +1,5 @@
.app-shell {
width: min(1100px, calc(100% - 32px));
width: min(1320px, calc(100% - 32px));
margin: 32px auto;
}
.app-header {
+1 -1
View File
@@ -33,7 +33,7 @@ export default function App() {
<header className="app-header">
<div>
<h1>Adminive</h1>
<p> JSON </p>
<p> JSON </p>
</div>
<button type="button" onClick={() => void load_schema()} disabled={loading}></button>
</header>
+26 -19
View File
@@ -1,26 +1,33 @@
import react from '@vitejs/plugin-react';
import {fileURLToPath, URL} from 'node:url';
import {defineConfig} from 'vite';
const output_dir = fileURLToPath(new URL('../frontend_dist', import.meta.url));
const cache_dir = fileURLToPath(new URL('./node_modules/.vite-adminive', import.meta.url));
export default defineConfig({
cacheDir: cache_dir,
plugins: [react()],
resolve: {
dedupe: ['mobx', 'mobx-react', 'mobx-react-lite', 'mobx-state-tree', 'react', 'react-dom']
},
server: {
host: '0.0.0.0',
port: 5173,
proxy: {
'/admin': {
target: 'http://127.0.0.1:9999',
changeOrigin: true
}
cacheDir: cache_dir,
plugins: [react()],
resolve: {
dedupe: ['mobx', 'mobx-react', 'mobx-react-lite', 'mobx-state-tree', 'react', 'react-dom']
},
server: {
host: '0.0.0.0',
port: 5173,
proxy: {
'/admin': {
target: 'http://127.0.0.1:9999',
changeOrigin: true
}
}
},
build: {
outDir: output_dir,
emptyOutDir: true,
rollupOptions: {
output: {
inlineDynamicImports: true
}
}
}
},
build: {
outDir: output_dir,
emptyOutDir: true
}
});
});