diff --git a/docs/magic-dash-pro.md b/docs/magic-dash-pro.md index 7445bc0..9a13a10 100644 --- a/docs/magic-dash-pro.md +++ b/docs/magic-dash-pro.md @@ -252,3 +252,20 @@ lambda current_user: current_user.user_name > `DatabaseConfig.mysql_config` 当数据库类型为`'mysql'`时,配置**MySQL**数据库连接参数。 + + +## 5 插件 + +### 快速添加页面 + +```cmd +python utils/add_page.py --name PagaName +``` + + | 参数名 | 类型 | 默认值 | 描述 | + |--------|------|--------|------| + | --name | str | 无 | 页面名称(必填)/key | + | --title | str | --name | 页面标题 | + | --describe | str | --name | 页面描述/注释 | + | --url | str | --name | 页面URL路径,默认/core/{--name} | + | --icon | str | "antd-menu" | 页面图标 | \ No newline at end of file diff --git a/docs/magic-dash.md b/docs/magic-dash.md index 616a966..cac26dd 100644 --- a/docs/magic-dash.md +++ b/docs/magic-dash.md @@ -136,3 +136,21 @@ magic-dash > `RouterConfig.wildcard_patterns` 基于正则表达式,配置应用中涉及到的通配页面模式字典。 + + +## 5 插件 + +### 快速添加页面 + + +```cmd +python utils/add_page.py --name PagaName +``` + + | 参数名 | 类型 | 默认值 | 描述 | + |--------|------|--------|------| + | --name | str | 无 | 页面名称(必填)/key | + | --title | str | --name | 页面标题 | + | --describe | str | --name | 页面描述/注释 | + | --url | str | --name | 页面URL路径,默认/core/{--name} | + | --icon | str | "antd-menu" | 页面图标 | \ No newline at end of file diff --git a/magic_dash/templates/magic-dash-pro/callbacks/core_pages_c/__init__.py b/magic_dash/templates/magic-dash-pro/callbacks/core_pages_c/__init__.py index a723153..ab7ce02 100644 --- a/magic_dash/templates/magic-dash-pro/callbacks/core_pages_c/__init__.py +++ b/magic_dash/templates/magic-dash-pro/callbacks/core_pages_c/__init__.py @@ -24,9 +24,7 @@ app.clientside_callback( # 控制核心页面侧边栏折叠 - ClientsideFunction( - namespace="clientside_basic", function_name="handleSideCollapse" - ), + ClientsideFunction(namespace="clientside_basic", function_name="handleSideCollapse"), [ Output("core-side-menu-collapse-button-icon", "icon"), Output("core-header-side", "style"), @@ -45,17 +43,13 @@ app.clientside_callback( # 控制页首页面搜索切换功能 - ClientsideFunction( - namespace="clientside_basic", function_name="handleCorePageSearch" - ), + ClientsideFunction(namespace="clientside_basic", function_name="handleCorePageSearch"), Input("core-page-search", "value"), ) app.clientside_callback( # 控制ctrl+k快捷键聚焦页面搜索框 - ClientsideFunction( - namespace="clientside_basic", function_name="handleCorePageSearchFocus" - ), + ClientsideFunction(namespace="clientside_basic", function_name="handleCorePageSearchFocus"), # 其中更新key用于强制刷新状态 [ Output("core-page-search", "autoFocus"), @@ -182,6 +176,9 @@ def core_router( # 更新页面返回内容 page_content = login_logs.render() + ### ADD_PAGE 逻辑插入位置 + ### NEW_PAGE_TARGET + # 多标签页形式 if page_config.get("core_layout_type") == "tabs": # 基于Patch进行标签页子项远程映射更新 @@ -200,8 +197,7 @@ def core_router( "children": index.render(), "closable": False, "contextMenu": [ - {"key": key, "label": key} - for key in ["关闭其他", "刷新页面"] + {"key": key, "label": key} for key in ["关闭其他", "刷新页面"] ], } ) @@ -214,8 +210,7 @@ def core_router( "children": index.render(), "closable": False, "contextMenu": [ - {"key": key, "label": key} - for key in ["关闭其他", "刷新页面"] + {"key": key, "label": key} for key in ["关闭其他", "刷新页面"] ], }, { @@ -302,9 +297,7 @@ def core_router( app.clientside_callback( - ClientsideFunction( - namespace="clientside_basic", function_name="handleCoreTabsClose" - ), + ClientsideFunction(namespace="clientside_basic", function_name="handleCoreTabsClose"), [ Output("core-container", "items", allow_duplicate=True), Output("core-container", "activeKey", allow_duplicate=True), @@ -318,9 +311,7 @@ def core_router( ) app.clientside_callback( - ClientsideFunction( - namespace="clientside_basic", function_name="handleCoreFullscreenToggle" - ), + ClientsideFunction(namespace="clientside_basic", function_name="handleCoreFullscreenToggle"), [ Output("core-fullscreen", "isFullscreen"), Output("core-full-screen-toggle-button-icon", "icon"), diff --git a/magic_dash/templates/magic-dash-pro/components/user_manage.py b/magic_dash/templates/magic-dash-pro/components/user_manage.py index aafcb7b..0cd92b8 100644 --- a/magic_dash/templates/magic-dash-pro/components/user_manage.py +++ b/magic_dash/templates/magic-dash-pro/components/user_manage.py @@ -1,5 +1,3 @@ -import uuid -import time import dash from dash import set_props import feffery_antd_components as fac @@ -11,6 +9,11 @@ from models.users import Users from configs import AuthConfig +import uuid +import time +import secrets +import string + def render(): """渲染用户管理抽屉""" @@ -38,12 +41,20 @@ def refresh_user_manage_table_data(): "gold" if item["user_role"] == AuthConfig.admin_role else "blue" ), }, - "操作": { - "content": "删除", - "type": "link", - "danger": True, - "disabled": item["user_role"] == AuthConfig.admin_role, - }, + "操作": [ + { + "content": "删除", + "type": "link", + "danger": True, + "disabled": item["user_role"] == AuthConfig.admin_role, + }, + { + "content": "重置密码", + "type": "link", + "danger": False, + "disabled": item["user_role"] == AuthConfig.admin_role, + }, + ], } for item in all_users ] @@ -75,6 +86,26 @@ def render_user_manage_drawer(visible): renderFooter=True, okClickClose=False, ), + # 删除用户模态框 + fac.AntdModal( + id="user-manage-delete-user-modal", + title=fac.AntdSpace( + [fac.AntdIcon(icon="antd-user-delete"), "删除用户"] + ), + mask=False, + renderFooter=True, + okClickClose=False, + visible=False, + ), + fac.AntdModal( + id="user-manage-reset-password-modal", + title=fac.AntdSpace( + [fac.AntdIcon(icon="antd-key"), "重置密码"]), + mask=False, + renderFooter=True, + okClickClose=False, + visible=False, + ), fac.AntdSpace( [ fac.AntdTable( @@ -249,6 +280,7 @@ def handle_add_user(okCounts, values): user_role=values["user-manage-add-user-form-user-role"], ) + # 提示 set_props( "global-message", { @@ -258,6 +290,11 @@ def handle_add_user(okCounts, values): ) }, ) + # 关闭modal + set_props( + "user-manage-add-user-modal", + {"visible": False}, + ) # 刷新用户列表 set_props( @@ -266,6 +303,7 @@ def handle_add_user(okCounts, values): ) +# 表格栏目高级操作 @app.callback( Input("user-manage-table", "nClicksButton"), [ @@ -274,20 +312,31 @@ def handle_add_user(okCounts, values): ], prevent_initial_call=True, ) -def handle_user_delete(nClicksButton, clickedContent, recentlyButtonClickedRow): - """处理用户删除逻辑""" - +def advanced_user_operation(nClicksButton, clickedContent, recentlyButtonClickedRow): + """ + 处理表格栏高级操作逻辑 + """ + # 处理删除逻辑 if clickedContent == "删除": - # 删除用户 - Users.delete_user(user_id=recentlyButtonClickedRow["user_id"]) - set_props( - "global-message", + "user-manage-delete-user-modal", { - "children": fac.AntdMessage( - type="success", - content="用户删除成功", - ) + "visible": True, + "children": fac.AntdSpace( + [ + fac.AntdAlert( + message=f"是否删除用户{recentlyButtonClickedRow['user_name']}" + ), + fac.AntdAlert( + message="该操作不可逆", + type="warning", + showIcon=True, + ), + ], + direction="vertical", + size="middle", + style={"width": "100%"}, + ), }, ) @@ -296,3 +345,123 @@ def handle_user_delete(nClicksButton, clickedContent, recentlyButtonClickedRow): "user-manage-table", {"data": refresh_user_manage_table_data()}, ) + elif clickedContent == "重置密码": + set_props( + "user-manage-reset-password-modal", + { + "visible": True, + "children": fac.AntdSpace( + [ + fac.AntdAlert( + message=f"是否重置用户{recentlyButtonClickedRow['user_name']}密码" + ), + fac.AntdAlert( + message="该操作不可逆", + type="warning", + showIcon=True, + ), + ], + direction="vertical", + size="middle", + style={"width": "100%"}, + ), + }, + ) + # 重置密码 + elif clickedContent == "重置密码": + set_props( + "user-manage-reset-password-modal", + { + "visible": True, + "children": fac.AntdSpace( + [ + fac.AntdAlert( + message=f"是否重置用户{recentlyButtonClickedRow['user_name']}密码" + ), + fac.AntdAlert( + message="该操作不可逆", + type="warning", + showIcon=True, + ), + ], + direction="vertical", + size="middle", + style={"width": "100%"}, + ), + }, + ) + + +@app.callback( + Input("user-manage-delete-user-modal", "okCounts"), + State("user-manage-table", "recentlyButtonClickedRow"), + prevent_initial_call=True, +) +def handle_user_delete(okCounts, recentlyButtonClickedRow): + """模态框处理用户删除逻辑""" + # 删除用户 + Users.delete_user(user_id=recentlyButtonClickedRow["user_id"]) + + set_props( + "global-message", + { + "children": fac.AntdMessage( + type="success", + content="用户删除成功", + ) + }, + ) + time.sleep(0.5) + set_props( + "user-manage-delete-user-modal", + {"visible": False}, + ) + + # 刷新用户列表 + set_props( + "user-manage-table", + {"data": refresh_user_manage_table_data()}, + ) + + +@app.callback( + Input("user-manage-reset-password-modal", "okCounts"), + State("user-manage-table", "recentlyButtonClickedRow"), + prevent_initial_call=True, +) +def handle_user_reset_password(okCounts, recentlyButtonClickedRow): + """模态框处理用户重置密码逻辑""" + + # 定义密码字符集 + characters = string.ascii_letters + string.digits + "!@#$%^&*" + # 生成 12 位随机密码 + new_password = "".join(secrets.choice(characters) for _ in range(8)) + + Users.update_user( + user_id=recentlyButtonClickedRow["user_id"], + password_hash=generate_password_hash(new_password), + ) + # 重置密码 + set_props( + "global-message", + { + "children": [ + fac.AntdMessage( + type="success", + content=f"用户{recentlyButtonClickedRow['user_name']}密码重置为 {new_password}", + ), + ] + }, + ) + + time.sleep(0.5) + set_props( + "user-manage-reset-password-modal", + {"visible": False}, + ) + + # 刷新用户列表 + set_props( + "user-manage-table", + {"data": refresh_user_manage_table_data()}, + ) diff --git a/magic_dash/templates/magic-dash-pro/utils/add_page.py b/magic_dash/templates/magic-dash-pro/utils/add_page.py new file mode 100644 index 0000000..f4eaae4 --- /dev/null +++ b/magic_dash/templates/magic-dash-pro/utils/add_page.py @@ -0,0 +1,300 @@ +import os +import sys +import importlib +import argparse + +# 默认配置 +core_pages_c_path = "./callbacks/core_pages_c/__init__.py" +router_config_path = "./configs/router_config.py" +core_pages_folder = "./views/core_pages" + + +# 添加core_pages_c +def page_import_adder( + page_name, + page_describe, + page_url, + core_pages_callbacks_path=core_pages_c_path, + target_import="views.core_pages", +): + """ + 添加单个页面到 callbacks.py 文件的 views.core_pages 导入语句中, + 同时将页面路由逻辑添加到 core_router 函数的 ### NEW_PAGE_TARGET 注释之后。 + + :param page_name: 页面名称 + :param page_describe: 页面描述/注释 + :param page_url: 页面的 URL 路径 + :param core_pages_callbacks_path: callbacks.py 文件路径,默认为指定路径 + :param target_import: 目标导入模块,默认为 "views.core_pages" + """ + # 检查文件是否存在 + if not os.path.exists(core_pages_callbacks_path): + raise FileNotFoundError(f"文件 {core_pages_callbacks_path} 不存在") + + # 读取 callbacks.py 文件内容 + with open(core_pages_callbacks_path, "r", encoding="utf-8") as f: + lines = f.readlines() + + # 添加新的导入语句部分 + import_line_index = None + for i, line in enumerate(lines): + if f"from {target_import} import (" in line: + import_line_index = i + break + + if import_line_index is not None: + import_end_index = import_line_index + while import_end_index < len(lines) and ")" not in lines[import_end_index]: + import_end_index += 1 + + page_exists = any( + page_name in line for line in lines[import_line_index : import_end_index + 1] + ) + if not page_exists: + new_line = f" {page_name}, # {page_describe}\n" + lines.insert(import_end_index, new_line) + with open(core_pages_callbacks_path, "w", encoding="utf-8") as f: + f.writelines(lines) + print(f"成功添加 {page_name} 引用到 {core_pages_callbacks_path} {target_import}") + else: + print(f"{page_name} 引用已存在于 {core_pages_callbacks_path} {target_import}中") + else: + new_import = [ + f"from {target_import} import (\n", + f" {page_name}, # {page_describe}\n", + ")\n", + ] + lines = new_import + lines + with open(core_pages_callbacks_path, "w", encoding="utf-8") as f: + f.writelines(lines) + print(f"未找到导入语句,已添加新的导入语句并添加页面 {page_name}") + + # 生成新的路由逻辑代码 + new_code = [ + f" # {page_describe}\n", + f' elif pathname == "{page_url}":\n', + f" page_content = {page_name}.render()\n", + ] + + # 找到 ### NEW_PAGE_TARGET 注释位置并插入新代码 + for i, line in enumerate(lines): + if "### NEW_PAGE_TARGET" in line: + lines.insert(i + 1, "".join(new_code)) + with open(core_pages_callbacks_path, "w", encoding="utf-8") as f: + f.writelines(lines) + print(f"成功添加 {page_name} 路由逻辑至 core_router() ### NEW_PAGE_TARGET") + break + else: + print("未找到 ### NEW_PAGE_TARGET 注释") + + +# 添加router配置 +def page_router_adder( + page_name, + page_title, + page_url, + page_icon, + page_describe, + RouterConfigPath=router_config_path, +): + """ + 将page_router添加到RouterConfig.core_side_menu/valid_pathnames + + :param page_name: 页面名称 + :param page_title: 页面标题 + :param page_url: 页面路径 + :param page_describe: 页面描述 + :param RouterConfigPath: router_config.py 文件的路径 + """ + + # 创建page_dict + page_dict = { + "component": "Item", + "props": { + "title": f"{page_title}", + "key": f"/core/{page_name}", + "icon": f"{page_icon}", + "href": f"{page_url}", + }, + } + + # 获取 router_config.py 文件目录 + router_config_dir = os.path.dirname(os.path.abspath(RouterConfigPath)) + router_config_file = os.path.basename(RouterConfigPath) + router_config_module = router_config_file.replace(".py", "") + + # 将目录添加到 sys.path 导入 + if router_config_dir not in sys.path: + sys.path.insert(0, router_config_dir) + + # 动态导入 RouterConfig + router_config = importlib.import_module(router_config_module) + + # 将 page_dict 添加到 RouterConfig.core_side_menu + router_config.RouterConfig.core_side_menu.append(page_dict) + + # 将页面信息添加到 RouterConfig.valid_pathnames + if page_url and page_title: + router_config.RouterConfig.valid_pathnames[page_url] = page_title # page_describe + + # 读取原文件内容 + with open(RouterConfigPath, "r", encoding="utf-8") as f: + lines = f.readlines() + + # 找到 core_side_menu 的开始结束位置 + start_index = None + end_index = None + bracket_count = 0 + for i, line in enumerate(lines): + if "core_side_menu:" in line: + start_index = i + if start_index is not None: + bracket_count += line.count("[") - line.count("]") + if bracket_count == 0: + end_index = i + break + + # 更新core_side_menu + if start_index is not None and end_index is not None: + # 找到core_side_menu的最后一个元素的结束位置 + last_bracket_index = None + for i in range(end_index, start_index, -1): + if "]" in lines[i]: + last_bracket_index = i + break + + # 在最后一个元素后添加新的页面字典 + if last_bracket_index is not None: + if not lines[last_bracket_index - 1].strip().endswith(","): + lines[last_bracket_index - 1] = lines[last_bracket_index - 1].rstrip() + ",\n" + + # 添加新的页面字典 + new_page_str = " " + str(page_dict) + "\n ]\n" + lines[last_bracket_index] = new_page_str + + # 找到 valid_pathnames 的开始和结束位置 + start_index = None + end_index = None + bracket_count = 0 + for i, line in enumerate(lines): + if "valid_pathnames:" in line: + start_index = i + if start_index is not None: + bracket_count += line.count("{") - line.count("}") + if bracket_count == 0: + end_index = i + break + + if start_index is not None and end_index is not None: + last_bracket_index = None + for i in range(end_index, start_index, -1): + if "}" in lines[i]: + last_bracket_index = i + break + + # 在最后一个元素前添加新的页面信息 + if last_bracket_index is not None: + # 添加新的页面信息 + new_page_str = f' "{page_url}": "{page_title}", # {page_describe}\n' + lines.insert(last_bracket_index, new_page_str) + + # 将修改后的内容写回文件 + with open(RouterConfigPath, "w", encoding="utf-8") as f: + f.writelines(lines) + + print( + f"成功添加 {page_name} 页面路由配置至 {RouterConfigPath} 的 RouterConfig.core_side_menu/.valid_pathnames" + ) + + +# 生成pagefile +def page_file_generator(page_name, page_title, page_describe, output_folder=core_pages_folder): + + page_file_path = f"{output_folder}/{page_name}.py" + + # 默认py文件模板 + template = f""" +from dash import html +import feffery_antd_components as fac +from feffery_dash_utils.style_utils import style + +def render(): + #"子页面:首页渲染简单示例 + + return fac.AntdSpace( + [ + fac.AntdBreadcrumb(items=[{{"title": "新页面"}}, {{"title": "{page_title}"}}]), + fac.AntdAlert( + type="info", + showIcon=True, + message="{page_name}", + description=fac.AntdText( + [ + "{page_describe}", + html.Br(), + "本页面模块路径:", + fac.AntdText("{page_file_path}", strong=True), + ] + ), + ), + ], + direction="vertical", + style=style(width="100%"), + ) +""" + + output_pyfile_path = os.path.join(output_folder, page_name) + ".py" + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + with open(f"{output_pyfile_path}", "w", encoding="utf-8") as f: + f.write(template) + print(f"成功新建 {page_name} 页面文件至 {output_pyfile_path}") + + +## main +def page_adder( + page_name, page_title=None, page_describe=None, page_url=None, page_icon="antd-menu" +): + # 当参数未输入时,从page_name自动生成 + if page_title is None: + page_title = page_name + + if page_describe is None: + page_describe = f"这是由ADD_PAGE自动生成的 {page_name} 页面的描述" + + if page_url is None: + page_url = f"/core/{page_name}" + + page_import_adder(page_name=page_name, page_describe=page_describe, page_url=page_url) + page_router_adder( + page_name=page_name, + page_title=page_title, + page_url=page_url, + page_describe=page_describe, + page_icon=page_icon, + ) + page_file_generator( + page_name=page_name, + page_title=page_title, + page_describe=page_describe, + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="添加新页面") + parser.add_argument("--name", required=True, help="页面名称") + parser.add_argument("--title", help="页面标题") + parser.add_argument("--describe", help="页面描述") + parser.add_argument("--url", help="页面URL") + parser.add_argument("--icon", default="antd-menu", help="页面图标") + + args = parser.parse_args() + + page_adder( + page_name=args.name, + page_title=args.title, + page_describe=args.describe, + page_url=args.url, + page_icon=args.icon, + ) diff --git a/magic_dash/templates/magic-dash/callbacks/core_pages_c/__init__.py b/magic_dash/templates/magic-dash/callbacks/core_pages_c/__init__.py index 39e4af4..a3761ce 100644 --- a/magic_dash/templates/magic-dash/callbacks/core_pages_c/__init__.py +++ b/magic_dash/templates/magic-dash/callbacks/core_pages_c/__init__.py @@ -156,6 +156,9 @@ def core_router( # 更新页面返回内容 page_content = url_params_page.render(current_url=current_url) + ### ADD_PAGE 逻辑插入位置 + ### NEW_PAGE_TARGET + # 多标签页形式 if page_config.get("core_layout_type") == "tabs": # 基于Patch进行标签页子项远程映射更新 diff --git a/magic_dash/templates/magic-dash/utils/add_page.py b/magic_dash/templates/magic-dash/utils/add_page.py new file mode 100644 index 0000000..f4eaae4 --- /dev/null +++ b/magic_dash/templates/magic-dash/utils/add_page.py @@ -0,0 +1,300 @@ +import os +import sys +import importlib +import argparse + +# 默认配置 +core_pages_c_path = "./callbacks/core_pages_c/__init__.py" +router_config_path = "./configs/router_config.py" +core_pages_folder = "./views/core_pages" + + +# 添加core_pages_c +def page_import_adder( + page_name, + page_describe, + page_url, + core_pages_callbacks_path=core_pages_c_path, + target_import="views.core_pages", +): + """ + 添加单个页面到 callbacks.py 文件的 views.core_pages 导入语句中, + 同时将页面路由逻辑添加到 core_router 函数的 ### NEW_PAGE_TARGET 注释之后。 + + :param page_name: 页面名称 + :param page_describe: 页面描述/注释 + :param page_url: 页面的 URL 路径 + :param core_pages_callbacks_path: callbacks.py 文件路径,默认为指定路径 + :param target_import: 目标导入模块,默认为 "views.core_pages" + """ + # 检查文件是否存在 + if not os.path.exists(core_pages_callbacks_path): + raise FileNotFoundError(f"文件 {core_pages_callbacks_path} 不存在") + + # 读取 callbacks.py 文件内容 + with open(core_pages_callbacks_path, "r", encoding="utf-8") as f: + lines = f.readlines() + + # 添加新的导入语句部分 + import_line_index = None + for i, line in enumerate(lines): + if f"from {target_import} import (" in line: + import_line_index = i + break + + if import_line_index is not None: + import_end_index = import_line_index + while import_end_index < len(lines) and ")" not in lines[import_end_index]: + import_end_index += 1 + + page_exists = any( + page_name in line for line in lines[import_line_index : import_end_index + 1] + ) + if not page_exists: + new_line = f" {page_name}, # {page_describe}\n" + lines.insert(import_end_index, new_line) + with open(core_pages_callbacks_path, "w", encoding="utf-8") as f: + f.writelines(lines) + print(f"成功添加 {page_name} 引用到 {core_pages_callbacks_path} {target_import}") + else: + print(f"{page_name} 引用已存在于 {core_pages_callbacks_path} {target_import}中") + else: + new_import = [ + f"from {target_import} import (\n", + f" {page_name}, # {page_describe}\n", + ")\n", + ] + lines = new_import + lines + with open(core_pages_callbacks_path, "w", encoding="utf-8") as f: + f.writelines(lines) + print(f"未找到导入语句,已添加新的导入语句并添加页面 {page_name}") + + # 生成新的路由逻辑代码 + new_code = [ + f" # {page_describe}\n", + f' elif pathname == "{page_url}":\n', + f" page_content = {page_name}.render()\n", + ] + + # 找到 ### NEW_PAGE_TARGET 注释位置并插入新代码 + for i, line in enumerate(lines): + if "### NEW_PAGE_TARGET" in line: + lines.insert(i + 1, "".join(new_code)) + with open(core_pages_callbacks_path, "w", encoding="utf-8") as f: + f.writelines(lines) + print(f"成功添加 {page_name} 路由逻辑至 core_router() ### NEW_PAGE_TARGET") + break + else: + print("未找到 ### NEW_PAGE_TARGET 注释") + + +# 添加router配置 +def page_router_adder( + page_name, + page_title, + page_url, + page_icon, + page_describe, + RouterConfigPath=router_config_path, +): + """ + 将page_router添加到RouterConfig.core_side_menu/valid_pathnames + + :param page_name: 页面名称 + :param page_title: 页面标题 + :param page_url: 页面路径 + :param page_describe: 页面描述 + :param RouterConfigPath: router_config.py 文件的路径 + """ + + # 创建page_dict + page_dict = { + "component": "Item", + "props": { + "title": f"{page_title}", + "key": f"/core/{page_name}", + "icon": f"{page_icon}", + "href": f"{page_url}", + }, + } + + # 获取 router_config.py 文件目录 + router_config_dir = os.path.dirname(os.path.abspath(RouterConfigPath)) + router_config_file = os.path.basename(RouterConfigPath) + router_config_module = router_config_file.replace(".py", "") + + # 将目录添加到 sys.path 导入 + if router_config_dir not in sys.path: + sys.path.insert(0, router_config_dir) + + # 动态导入 RouterConfig + router_config = importlib.import_module(router_config_module) + + # 将 page_dict 添加到 RouterConfig.core_side_menu + router_config.RouterConfig.core_side_menu.append(page_dict) + + # 将页面信息添加到 RouterConfig.valid_pathnames + if page_url and page_title: + router_config.RouterConfig.valid_pathnames[page_url] = page_title # page_describe + + # 读取原文件内容 + with open(RouterConfigPath, "r", encoding="utf-8") as f: + lines = f.readlines() + + # 找到 core_side_menu 的开始结束位置 + start_index = None + end_index = None + bracket_count = 0 + for i, line in enumerate(lines): + if "core_side_menu:" in line: + start_index = i + if start_index is not None: + bracket_count += line.count("[") - line.count("]") + if bracket_count == 0: + end_index = i + break + + # 更新core_side_menu + if start_index is not None and end_index is not None: + # 找到core_side_menu的最后一个元素的结束位置 + last_bracket_index = None + for i in range(end_index, start_index, -1): + if "]" in lines[i]: + last_bracket_index = i + break + + # 在最后一个元素后添加新的页面字典 + if last_bracket_index is not None: + if not lines[last_bracket_index - 1].strip().endswith(","): + lines[last_bracket_index - 1] = lines[last_bracket_index - 1].rstrip() + ",\n" + + # 添加新的页面字典 + new_page_str = " " + str(page_dict) + "\n ]\n" + lines[last_bracket_index] = new_page_str + + # 找到 valid_pathnames 的开始和结束位置 + start_index = None + end_index = None + bracket_count = 0 + for i, line in enumerate(lines): + if "valid_pathnames:" in line: + start_index = i + if start_index is not None: + bracket_count += line.count("{") - line.count("}") + if bracket_count == 0: + end_index = i + break + + if start_index is not None and end_index is not None: + last_bracket_index = None + for i in range(end_index, start_index, -1): + if "}" in lines[i]: + last_bracket_index = i + break + + # 在最后一个元素前添加新的页面信息 + if last_bracket_index is not None: + # 添加新的页面信息 + new_page_str = f' "{page_url}": "{page_title}", # {page_describe}\n' + lines.insert(last_bracket_index, new_page_str) + + # 将修改后的内容写回文件 + with open(RouterConfigPath, "w", encoding="utf-8") as f: + f.writelines(lines) + + print( + f"成功添加 {page_name} 页面路由配置至 {RouterConfigPath} 的 RouterConfig.core_side_menu/.valid_pathnames" + ) + + +# 生成pagefile +def page_file_generator(page_name, page_title, page_describe, output_folder=core_pages_folder): + + page_file_path = f"{output_folder}/{page_name}.py" + + # 默认py文件模板 + template = f""" +from dash import html +import feffery_antd_components as fac +from feffery_dash_utils.style_utils import style + +def render(): + #"子页面:首页渲染简单示例 + + return fac.AntdSpace( + [ + fac.AntdBreadcrumb(items=[{{"title": "新页面"}}, {{"title": "{page_title}"}}]), + fac.AntdAlert( + type="info", + showIcon=True, + message="{page_name}", + description=fac.AntdText( + [ + "{page_describe}", + html.Br(), + "本页面模块路径:", + fac.AntdText("{page_file_path}", strong=True), + ] + ), + ), + ], + direction="vertical", + style=style(width="100%"), + ) +""" + + output_pyfile_path = os.path.join(output_folder, page_name) + ".py" + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + with open(f"{output_pyfile_path}", "w", encoding="utf-8") as f: + f.write(template) + print(f"成功新建 {page_name} 页面文件至 {output_pyfile_path}") + + +## main +def page_adder( + page_name, page_title=None, page_describe=None, page_url=None, page_icon="antd-menu" +): + # 当参数未输入时,从page_name自动生成 + if page_title is None: + page_title = page_name + + if page_describe is None: + page_describe = f"这是由ADD_PAGE自动生成的 {page_name} 页面的描述" + + if page_url is None: + page_url = f"/core/{page_name}" + + page_import_adder(page_name=page_name, page_describe=page_describe, page_url=page_url) + page_router_adder( + page_name=page_name, + page_title=page_title, + page_url=page_url, + page_describe=page_describe, + page_icon=page_icon, + ) + page_file_generator( + page_name=page_name, + page_title=page_title, + page_describe=page_describe, + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="添加新页面") + parser.add_argument("--name", required=True, help="页面名称") + parser.add_argument("--title", help="页面标题") + parser.add_argument("--describe", help="页面描述") + parser.add_argument("--url", help="页面URL") + parser.add_argument("--icon", default="antd-menu", help="页面图标") + + args = parser.parse_args() + + page_adder( + page_name=args.name, + page_title=args.title, + page_describe=args.describe, + page_url=args.url, + page_icon=args.icon, + )