"
+
+ @classmethod
+ def get_user(cls, user_id: str):
+ """根据用户id查询用户信息"""
+ user = cls.query.get(user_id)
+ return user if user else None
+
+ @classmethod
+ def get_user_by_name(cls, user_name: str):
+ """根据用户名查询用户信息"""
+ user = cls.query.filter_by(user_name=user_name).first()
+ return user if user else None
+
+ @classmethod
+ def get_all_users(cls):
+ """获取所有用户信息"""
+ users = cls.query.all()
+ return [
+ {
+ 'user_id': user.user_id,
+ 'user_name': user.user_name,
+ 'user_role': user.user_role,
+ 'session_token': user.session_token,
+ 'other_info': user.other_info
+ }
+ for user in users
+ ]
+
+ @classmethod
+ def check_user_password(cls, user_id: str, password: str):
+ """校验用户密码"""
+ user = cls.get_user(user_id)
+ if user:
+ return check_password_hash(user.password_hash, password)
+ return False
+
+ @classmethod
+ def add_user(
+ cls,
+ user_id: str,
+ user_name: str,
+ password_hash: str,
+ user_role: str = "normal",
+ other_info: Union[Dict, List] = None,
+ ):
+ """添加用户"""
+ # 若必要用户信息不完整
+ if not (user_id and user_name and password_hash):
+ raise InvalidUserError("用户信息不完整")
+
+ # 若用户id已存在
+ elif cls.get_user(user_id):
+ raise ExistingUserError("用户id已存在")
+
+ # 若用户名存在重复
+ elif cls.get_user_by_name(user_name):
+ raise ExistingUserError("用户名已存在")
+
+ # 执行用户添加操作
+ try:
+ new_user = cls(
+ user_id=user_id,
+ user_name=user_name,
+ password_hash=password_hash,
+ user_role=user_role,
+ other_info=other_info,
+ )
+ db.add(new_user)
+ db.commit()
+ except:
+ db.rollback()
+ raise
+
+ @classmethod
+ def delete_user(cls, user_id: str):
+ """删除用户"""
+ try:
+ user = cls.get_user(user_id)
+ if user:
+ db.delete(user)
+ db.commit()
+ except:
+ db.rollback()
+ raise
+
+ @classmethod
+ def update_user(cls, user_id: str, **kwargs):
+ """更新用户信息"""
+ try:
+ user = cls.get_user(user_id)
+ if user:
+ for key, value in kwargs.items():
+ setattr(user, key, value)
+ db.commit()
+ return user
+ except:
+ db.rollback()
+ raise
+ return None
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/requirements.txt b/magic_dash/templates/magic-dash-pro-sqlalchemy/requirements.txt
new file mode 100644
index 0000000..9b845fb
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/requirements.txt
@@ -0,0 +1,9 @@
+dash>=2.18.2
+feffery_antd_components>=0.3.12
+feffery_dash_utils>=0.1.5
+feffery_utils_components>=0.2.0rc25
+Flask_Login
+sqlalchemy
+user_agents
+flask-compress
+flask-principal
\ No newline at end of file
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/server.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/server.py
new file mode 100644
index 0000000..64c0026
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/server.py
@@ -0,0 +1,121 @@
+import dash
+from flask import request
+from user_agents import parse
+from flask_principal import Principal, Permission, RoleNeed, identity_loaded
+from flask_login import LoginManager, UserMixin, current_user, AnonymousUserMixin
+
+# 应用基础参数
+from models.users import Users
+from configs import BaseConfig, AuthConfig
+
+app = dash.Dash(
+ __name__,
+ title=BaseConfig.app_title,
+ suppress_callback_exceptions=True,
+ compress=True, # 隐式依赖flask-compress
+ update_title=None,
+)
+server = app.server
+
+# 设置应用密钥
+app.server.secret_key = BaseConfig.app_secret_key
+
+# 为当前应用添加flask-login用户登录管理
+login_manager = LoginManager()
+login_manager.init_app(app.server)
+
+# 为当前应用添加flask-principal权限管理
+principals = Principal(app.server)
+
+
+class User(UserMixin):
+ """flask-login专用用户类"""
+
+ def __init__(
+ self, id: str, user_name: str, user_role: str, session_token: str = None
+ ) -> None:
+ """初始化用户信息"""
+
+ self.id = id
+ self.user_name = user_name
+ self.user_role = user_role
+ self.session_token = session_token
+
+
+@login_manager.user_loader
+def user_loader(user_id):
+ """flask-login内部专用用户加载函数"""
+
+ # 根据当前要加载的用户id,从数据库中获取匹配用户信息
+ match_user = Users.get_user(user_id)
+
+ # 处理未匹配到有效用户的情况
+ if not match_user:
+ return AnonymousUserMixin()
+
+ # 当前用户实例化
+ user = User(
+ id=match_user.user_id,
+ user_name=match_user.user_name,
+ user_role=match_user.user_role,
+ session_token=match_user.session_token,
+ )
+
+ return user
+
+
+# 定义不同用户角色
+user_permissions = {role: Permission(RoleNeed(role)) for role in AuthConfig.roles}
+
+
+@identity_loaded.connect_via(app.server)
+def on_identity_loaded(sender, identity):
+ """flask-principal身份加载回调函数"""
+
+ identity.user = current_user
+
+ if hasattr(current_user, "user_role"):
+ identity.provides.add(RoleNeed(current_user.user_role))
+
+
+@app.server.before_request
+def check_browser():
+ """检查浏览器版本是否符合最低要求"""
+
+ # 提取当前请求对应的浏览器信息
+ user_agent = parse(str(request.user_agent))
+
+ # 若浏览器版本信息有效
+ if user_agent.browser.version != ():
+ # IE相关浏览器直接拦截
+ if user_agent.browser.family == "IE":
+ return (
+ ""
+ "请不要使用IE浏览器,或开启了IE内核兼容模式的其他浏览器访问本应用
"
+ )
+ # 基于BaseConfig.min_browser_versions配置,对相关浏览器最低版本进行检查
+ for rule in BaseConfig.min_browser_versions:
+ # 若当前请求对应的浏览器版本,低于声明的最低支持版本
+ if (
+ user_agent.browser.family == rule["browser"]
+ and user_agent.browser.version[0] < rule["version"]
+ ):
+ return (
+ ""
+ "您的{}浏览器版本低于本应用最低支持版本({}),请升级浏览器后再访问
"
+ ).format(rule["browser"], rule["version"])
+
+ # 若开启了严格的浏览器类型限制
+ if BaseConfig.strict_browser_type_check:
+ # 若当前浏览器不在声明的浏览器范围内
+ if user_agent.browser.family not in [
+ rule["browser"] for rule in BaseConfig.min_browser_versions
+ ]:
+ return (
+ ""
+ "当前浏览器类型不在支持的范围内,支持的浏览器类型有:{}
"
+ ).format(
+ "、".join(
+ [rule["browser"] for rule in BaseConfig.min_browser_versions]
+ )
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/utils/clear_pycache.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/utils/clear_pycache.py
new file mode 100644
index 0000000..cc01cb8
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/utils/clear_pycache.py
@@ -0,0 +1,15 @@
+import os
+import shutil
+
+
+def clear_pycache():
+ """清除当前目录下任何位置的__pycache__文件夹"""
+
+ for root, dirs, files in os.walk("."):
+ if "__pycache__" in dirs:
+ shutil.rmtree(os.path.join(root, "__pycache__"))
+
+
+if __name__ == "__main__":
+ clear_pycache()
+ print("__pycache__清除完成")
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/__init__.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/__init__.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/__init__.py
new file mode 100644
index 0000000..182e7f5
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/__init__.py
@@ -0,0 +1,342 @@
+from dash import html, dcc
+from flask_login import current_user
+import feffery_antd_components as fac
+import feffery_utils_components as fuc
+from feffery_dash_utils.style_utils import style
+
+from views.core_pages import independent_page_demo
+from components import core_side_menu, personal_info, user_manage
+from configs import BaseConfig, RouterConfig, LayoutConfig, AuthConfig
+
+# 令绑定的回调函数子模块生效
+import callbacks.core_pages_c # noqa: F401
+
+
+def get_page_search_options(current_user_access_rule: str):
+ """当前模块内工具函数,生成页面搜索选项"""
+
+ options = [{"label": "首页", "value": "/"}]
+
+ for pathname, title in RouterConfig.valid_pathnames.items():
+ # 忽略已添加的首页
+ if pathname in [RouterConfig.index_pathname, "/"]:
+ pass
+
+ elif (
+ # 公开页面全部放行
+ pathname in RouterConfig.public_pathnames
+ or current_user_access_rule["type"] == "all"
+ ):
+ options.append(
+ {
+ "label": title,
+ "value": f"{pathname}|{title}",
+ }
+ )
+
+ elif current_user_access_rule["type"] == "include":
+ if pathname in current_user_access_rule["keys"]:
+ options.append(
+ {
+ "label": title,
+ "value": f"{pathname}|{title}",
+ }
+ )
+
+ elif current_user_access_rule["type"] == "exclude":
+ if pathname not in current_user_access_rule["keys"]:
+ options.append(
+ {
+ "label": title,
+ "value": f"{pathname}|{title}",
+ }
+ )
+
+ return options
+
+
+def render(current_user_access_rule: str, current_pathname: str = None):
+ """渲染核心页面骨架
+
+ Args:
+ current_user_access_rule (str): 当前用户页面可访问性规则
+ current_pathname (str, optional): 当前页面pathname. Defaults to None.
+ """
+
+ # 判断是否需要独立渲染
+ if current_pathname in RouterConfig.independent_core_pathnames:
+ # 返回不同地址规则对应页面内容
+ if current_pathname == "/core/independent-page/demo":
+ return independent_page_demo.render()
+
+ return html.Div(
+ [
+ # 核心页面常量参数数据
+ dcc.Store(
+ id="core-page-config",
+ data=dict(core_side_width=LayoutConfig.core_side_width),
+ ),
+ # 核心页面独立路由监听
+ dcc.Location(id="core-url"),
+ # ctrl+k快捷键监听
+ fuc.FefferyKeyPress(id="core-ctrl-k-key-press", keys="ctrl.k"),
+ # 注入个人信息模态框
+ personal_info.render(),
+ # 若当前用户角色为系统管理员
+ *(
+ # 注入用户管理抽屉
+ [
+ user_manage.render(),
+ ]
+ if current_user.user_role == AuthConfig.admin_role
+ else []
+ ),
+ # 页首
+ fac.AntdRow(
+ [
+ # logo+标题+版本+侧边折叠按钮
+ fac.AntdCol(
+ fac.AntdFlex(
+ [
+ dcc.Link(
+ fac.AntdSpace(
+ [
+ # logo
+ html.Img(
+ src="/assets/imgs/logo.svg",
+ height=32,
+ style=style(display="block"),
+ ),
+ fac.AntdSpace(
+ [
+ # 标题
+ fac.AntdText(
+ BaseConfig.app_title,
+ strong=True,
+ style=style(fontSize=20),
+ ),
+ fac.AntdText(
+ BaseConfig.app_version,
+ className="global-help-text",
+ style=style(fontSize=12),
+ ),
+ ],
+ align="baseline",
+ size=3,
+ id="core-header-title",
+ ),
+ ]
+ ),
+ href="/",
+ ),
+ # 侧边折叠按钮
+ fac.AntdButton(
+ fac.AntdIcon(
+ id="core-side-menu-collapse-button-icon",
+ icon="antd-menu-fold",
+ className="global-help-text",
+ ),
+ id="core-side-menu-collapse-button",
+ type="text",
+ size="small",
+ ),
+ ],
+ id="core-header-side",
+ justify="space-between",
+ align="center",
+ style=style(
+ width=LayoutConfig.core_side_width,
+ height="100%",
+ paddingLeft=20,
+ paddingRight=20,
+ borderRight="1px solid #dae0ea",
+ boxSizing="border-box",
+ ),
+ ),
+ flex="none",
+ ),
+ # 页面搜索+功能图标+用户信息
+ fac.AntdCol(
+ fac.AntdFlex(
+ [
+ # 页面搜索
+ fac.AntdSpace(
+ [
+ fac.AntdSelect(
+ id="core-page-search",
+ placeholder="输入关键词搜索页面",
+ options=get_page_search_options(
+ current_user_access_rule
+ ),
+ variant="filled",
+ style=style(width=250),
+ ),
+ fac.AntdText(
+ [
+ fac.AntdText(
+ "Ctrl",
+ keyboard=True,
+ className="global-help-text",
+ ),
+ fac.AntdText(
+ "K",
+ keyboard=True,
+ className="global-help-text",
+ ),
+ ]
+ ),
+ ],
+ size=5,
+ ),
+ # 功能图标+用户信息
+ fac.AntdSpace(
+ [
+ # 示例功能图标1
+ fac.AntdButton(
+ icon=fac.AntdIcon(
+ icon="antd-setting",
+ className="global-help-text",
+ ),
+ type="text",
+ ),
+ # 示例功能图标2
+ fac.AntdButton(
+ icon=fac.AntdIcon(
+ icon="antd-bell",
+ className="global-help-text",
+ ),
+ type="text",
+ ),
+ # 示例功能图标3
+ fac.AntdButton(
+ icon=fac.AntdIcon(
+ icon="antd-question-circle",
+ className="global-help-text",
+ ),
+ type="text",
+ ),
+ # 自定义分隔符
+ html.Div(
+ style=style(
+ width=0,
+ height=42,
+ borderLeft="1px solid #e1e5ee",
+ margin="0 12px",
+ )
+ ),
+ # 用户头像
+ fac.AntdAvatar(
+ mode="text",
+ text="🤩",
+ size=36,
+ style=style(background="#f4f6f9"),
+ ),
+ # 用户名+角色
+ fac.AntdFlex(
+ [
+ fac.AntdText(
+ current_user.user_name.capitalize(),
+ strong=True,
+ ),
+ fac.AntdText(
+ "角色:{}".format(
+ AuthConfig.roles.get(
+ current_user.user_role
+ )["description"]
+ ),
+ className="global-help-text",
+ style=style(fontSize=12),
+ ),
+ ],
+ vertical=True,
+ ),
+ # 用户管理菜单
+ fac.AntdDropdown(
+ fac.AntdButton(
+ icon=fac.AntdIcon(
+ icon="antd-more",
+ className="global-help-text",
+ ),
+ type="text",
+ ),
+ id="core-pages-header-user-dropdown",
+ menuItems=[
+ {
+ "title": "个人信息",
+ "key": "个人信息",
+ },
+ # 若当前用户角色为系统管理员
+ *(
+ [
+ {
+ "title": "用户管理",
+ "key": "用户管理",
+ }
+ ]
+ if (
+ current_user.user_role
+ == AuthConfig.admin_role
+ )
+ else []
+ ),
+ {"isDivider": True},
+ {
+ "title": "退出登录",
+ "href": "/logout",
+ },
+ ],
+ trigger="click",
+ ),
+ ]
+ ),
+ ],
+ justify="space-between",
+ align="center",
+ style=style(
+ height="100%",
+ paddingLeft=20,
+ paddingRight=20,
+ ),
+ ),
+ flex="auto",
+ ),
+ ],
+ wrap=False,
+ align="middle",
+ style=style(
+ height=72,
+ borderBottom="1px solid #dae0ea",
+ position="sticky",
+ top=0,
+ zIndex=1000,
+ background="#fff",
+ ),
+ ),
+ # 主题区域
+ fac.AntdRow(
+ [
+ # 侧边栏
+ fac.AntdCol(
+ core_side_menu.render(
+ current_user_access_rule=current_user_access_rule
+ ),
+ flex="none",
+ ),
+ # 内容区域
+ fac.AntdCol(
+ fac.AntdSkeleton(
+ html.Div(
+ id="core-container", style=style(padding="36px 42px")
+ ),
+ listenPropsMode="include",
+ includeProps=["core-container.children"],
+ active=True,
+ style=style(padding="36px 42px"),
+ ),
+ flex="auto",
+ ),
+ ],
+ wrap=False,
+ ),
+ ]
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/independent_page.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/independent_page.py
new file mode 100644
index 0000000..adc63ab
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/independent_page.py
@@ -0,0 +1,36 @@
+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": "独立页面渲染入口页"}]
+ ),
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="这里是独立页面渲染入口页演示示例",
+ description=fac.AntdText(
+ [
+ "点击",
+ html.A(
+ "此处", href="/core/independent-page/demo", target="_blank"
+ ),
+ "打开示例独立显示页面。",
+ html.Br(),
+ "本页面模块路径:",
+ fac.AntdText(
+ "views/core_pages/independent_page.py", strong=True
+ ),
+ ]
+ ),
+ ),
+ ],
+ direction="vertical",
+ style=style(width="100%"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/independent_page_demo.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/independent_page_demo.py
new file mode 100644
index 0000000..ca0be77
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/independent_page_demo.py
@@ -0,0 +1,24 @@
+from dash import html
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render():
+ """子页面:独立页面渲染简单示例"""
+
+ return html.Div(
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="这里是独立页面演示示例",
+ description=fac.AntdText(
+ [
+ "本页面模块路径:",
+ fac.AntdText(
+ "views/core_pages/independent_page_demo.py", strong=True
+ ),
+ ]
+ ),
+ ),
+ style=style(padding="24px 32px"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/index.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/index.py
new file mode 100644
index 0000000..66425a8
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/index.py
@@ -0,0 +1,28 @@
+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": "首页"}]),
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="欢迎来到首页!",
+ description=fac.AntdText(
+ [
+ "这里以首页为例,演示核心页面下,各子页面构建方式的简单示例😉~",
+ html.Br(),
+ "本页面模块路径:",
+ fac.AntdText("views/core_pages/index.py", strong=True),
+ ]
+ ),
+ ),
+ ],
+ direction="vertical",
+ style=style(width="100%"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/page1.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/page1.py
new file mode 100644
index 0000000..00b9f7e
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/page1.py
@@ -0,0 +1,43 @@
+from dash import html
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+# 令对应当前页面的回调函数子模块生效
+import callbacks.core_pages_c.page1_c # noqa: F401
+
+
+def render():
+ """子页面:主要页面1渲染简单示例"""
+
+ return fac.AntdSpace(
+ [
+ fac.AntdBreadcrumb(items=[{"title": "主要页面"}, {"title": "主要页面1"}]),
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="这里是主要页面1演示示例",
+ description=fac.AntdText(
+ [
+ "本页面简单演示了如何在当前项目模板中添加新的自定义页面,且页面内容中包含由回调函数控制的简单演示用交互功能。",
+ html.Br(),
+ "本页面模块路径:",
+ fac.AntdText("views/core_pages/page1.py", strong=True),
+ html.Br(),
+ "本页面回调模块路径:",
+ fac.AntdText("callbacks/core_pages_c/page1_c.py", strong=True),
+ ]
+ ),
+ ),
+ fac.AntdText("回调功能演示:"),
+ fac.AntdSpace(
+ [
+ fac.AntdButton(
+ "点击测试", id="core-page1-demo-button", type="primary"
+ ),
+ fac.AntdText("累计点击次数:0", id="core-page1-demo-output"),
+ ]
+ ),
+ ],
+ direction="vertical",
+ style=style(width="100%"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page1.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page1.py
new file mode 100644
index 0000000..8c03469
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page1.py
@@ -0,0 +1,45 @@
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render():
+ """子页面:子菜单演示1渲染简单示例"""
+
+ return fac.AntdSpace(
+ [
+ fac.AntdBreadcrumb(
+ items=[
+ {"title": "主要页面"},
+ {"title": "子菜单演示"},
+ {
+ "title": "子菜单演示1",
+ "menuItems": [
+ {
+ "title": "子菜单演示2",
+ "href": "/core/sub-menu-page2",
+ "target": "_blank",
+ },
+ {
+ "title": "子菜单演示3",
+ "href": "/core/sub-menu-page3",
+ "target": "_blank",
+ },
+ ],
+ },
+ ]
+ ),
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="这里是子菜单演示1演示示例",
+ description=fac.AntdText(
+ [
+ "本页面模块路径:",
+ fac.AntdText("views/core_pages/sub_menu_page1.py", strong=True),
+ ]
+ ),
+ ),
+ ],
+ direction="vertical",
+ style=style(width="100%"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page2.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page2.py
new file mode 100644
index 0000000..94cb19c
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page2.py
@@ -0,0 +1,45 @@
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render():
+ """子页面:子菜单演示2渲染简单示例"""
+
+ return fac.AntdSpace(
+ [
+ fac.AntdBreadcrumb(
+ items=[
+ {"title": "主要页面"},
+ {"title": "子菜单演示"},
+ {
+ "title": "子菜单演示2",
+ "menuItems": [
+ {
+ "title": "子菜单演示1",
+ "href": "/core/sub-menu-page1",
+ "target": "_blank",
+ },
+ {
+ "title": "子菜单演示3",
+ "href": "/core/sub-menu-page3",
+ "target": "_blank",
+ },
+ ],
+ },
+ ]
+ ),
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="这里是子菜单演示2演示示例",
+ description=fac.AntdText(
+ [
+ "本页面模块路径:",
+ fac.AntdText("views/core_pages/sub_menu_page2.py", strong=True),
+ ]
+ ),
+ ),
+ ],
+ direction="vertical",
+ style=style(width="100%"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page3.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page3.py
new file mode 100644
index 0000000..2cd4ec1
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/core_pages/sub_menu_page3.py
@@ -0,0 +1,45 @@
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render():
+ """子页面:子菜单演示3渲染简单示例"""
+
+ return fac.AntdSpace(
+ [
+ fac.AntdBreadcrumb(
+ items=[
+ {"title": "主要页面"},
+ {"title": "子菜单演示"},
+ {
+ "title": "子菜单演示3",
+ "menuItems": [
+ {
+ "title": "子菜单演示1",
+ "href": "/core/sub-menu-page1",
+ "target": "_blank",
+ },
+ {
+ "title": "子菜单演示2",
+ "href": "/core/sub-menu-page2",
+ "target": "_blank",
+ },
+ ],
+ },
+ ]
+ ),
+ fac.AntdAlert(
+ type="info",
+ showIcon=True,
+ message="这里是子菜单演示3演示示例",
+ description=fac.AntdText(
+ [
+ "本页面模块路径:",
+ fac.AntdText("views/core_pages/sub_menu_page3.py", strong=True),
+ ]
+ ),
+ ),
+ ],
+ direction="vertical",
+ style=style(width="100%"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/login.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/login.py
new file mode 100644
index 0000000..2c70d0f
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/login.py
@@ -0,0 +1,194 @@
+from dash import html
+import feffery_utils_components as fuc
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+from configs import BaseConfig, LayoutConfig
+
+# 令绑定的回调函数子模块生效
+import callbacks.login_c # noqa: F401
+
+
+def render():
+ """渲染用户登录页面"""
+
+ return fac.AntdRow(
+ [
+ # 左侧半边
+ fac.AntdCol(
+ *(
+ [
+ fuc.FefferyMotion(
+ html.Img(
+ src="/assets/imgs/login/插图1.svg",
+ style=style(width="25vw"),
+ ),
+ style={
+ "position": "absolute",
+ "left": "10%",
+ "top": "15%",
+ "rotateZ": "-5deg",
+ },
+ animate={"y": [25, -25, 25]},
+ transition={
+ "duration": 4.5,
+ "repeat": "infinity",
+ "type": "spring",
+ },
+ ),
+ fuc.FefferyMotion(
+ html.Img(
+ src="/assets/imgs/login/插图2.svg",
+ style=style(width="15vw"),
+ ),
+ style={
+ "position": "absolute",
+ "right": "20%",
+ "top": "25%",
+ "rotateZ": "15deg",
+ },
+ animate={"y": [-15, 15, -15]},
+ transition={
+ "duration": 5.5,
+ "repeat": "infinity",
+ "type": "spring",
+ },
+ ),
+ fuc.FefferyMotion(
+ html.Img(
+ src="/assets/imgs/login/插图3.svg",
+ style=style(width="12vw"),
+ ),
+ style={
+ "position": "absolute",
+ "left": "25%",
+ "bottom": "25%",
+ "rotateZ": "-8deg",
+ },
+ animate={"y": [10, -10, 10]},
+ transition={
+ "duration": 5,
+ "repeat": "infinity",
+ "type": "spring",
+ },
+ ),
+ fuc.FefferyMotion(
+ html.Img(
+ src="/assets/imgs/login/插图4.svg",
+ style=style(width="25vw"),
+ ),
+ style={
+ "position": "absolute",
+ "right": "15%",
+ "bottom": "8%",
+ "rotateZ": "5deg",
+ },
+ animate={"y": [20, -20, 20]},
+ transition={
+ "duration": 6,
+ "repeat": "infinity",
+ "type": "spring",
+ },
+ ),
+ ]
+ if LayoutConfig.login_left_side_content_type == "image"
+ else [
+ html.Video(
+ src="/assets/videos/login-bg.mp4",
+ autoPlay=True,
+ muted=True,
+ loop=True,
+ style=style(
+ width="100%",
+ height="100%",
+ position="absolute",
+ objectFit="cover",
+ borderTopRightRadius=12,
+ borderBottomRightRadius=12,
+ pointerEvents="none",
+ ),
+ )
+ ],
+ ),
+ span=14,
+ className="login-left-side",
+ style=(
+ style()
+ if LayoutConfig.login_left_side_content_type == "image"
+ else style(backgroundImage="none")
+ ),
+ ),
+ # 右侧半边
+ fac.AntdCol(
+ fac.AntdCenter(
+ [
+ fac.AntdSpace(
+ [
+ html.Img(
+ src="/assets/imgs/logo.svg",
+ height=72,
+ ),
+ fac.AntdText(
+ BaseConfig.app_title,
+ style=style(fontSize=36),
+ ),
+ fac.AntdForm(
+ [
+ fac.AntdFormItem(
+ fac.AntdInput(
+ id="login-user-name",
+ placeholder="请输入用户名",
+ size="large",
+ prefix=fac.AntdIcon(
+ icon="antd-user",
+ className="global-help-text",
+ ),
+ autoComplete="off",
+ ),
+ label="用户名",
+ ),
+ fac.AntdFormItem(
+ fac.AntdInput(
+ id="login-password",
+ placeholder="请输入密码",
+ size="large",
+ mode="password",
+ prefix=fac.AntdIcon(
+ icon="antd-lock",
+ className="global-help-text",
+ ),
+ ),
+ label="密码",
+ ),
+ fac.AntdCheckbox(
+ id="login-remember-me", label="记住我"
+ ),
+ fac.AntdButton(
+ "登录",
+ id="login-button",
+ loadingChildren="校验中",
+ type="primary",
+ block=True,
+ size="large",
+ style=style(marginTop=18),
+ ),
+ ],
+ id="login-form",
+ enableBatchControl=True,
+ layout="vertical",
+ style=style(width=350),
+ ),
+ ],
+ direction="vertical",
+ align="center",
+ )
+ ],
+ style=style(height="calc(100% - 200px)"),
+ ),
+ span=10,
+ className="login-right-side",
+ ),
+ ],
+ wrap=False,
+ style=style(height="100vh"),
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_403.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_403.py
new file mode 100644
index 0000000..c5eeb6b
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_403.py
@@ -0,0 +1,21 @@
+from dash import html
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render():
+ """渲染403状态页面"""
+
+ return fac.AntdCenter(
+ fac.AntdResult(
+ # 自定义状态图片
+ icon=html.Img(
+ src="/assets/imgs/status/403.svg",
+ style=style(height="50vh", pointerEvents="none"),
+ ),
+ title=fac.AntdText("权限不足", style=style(fontSize=20)),
+ subTitle="您没有访问该页面的权限",
+ extra=fac.AntdButton("返回首页", type="primary", href="/", target="_self"),
+ ),
+ style={"height": "calc(60vh + 100px)"},
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_404.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_404.py
new file mode 100644
index 0000000..07ff147
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_404.py
@@ -0,0 +1,21 @@
+from dash import html
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render():
+ """渲染404状态页面"""
+
+ return fac.AntdCenter(
+ fac.AntdResult(
+ # 自定义状态图片
+ icon=html.Img(
+ src="/assets/imgs/status/404.svg",
+ style=style(height="50vh", pointerEvents="none"),
+ ),
+ title=fac.AntdText("当前页面不存在", style=style(fontSize=20)),
+ subTitle="请检查您输入的网址是否正确",
+ extra=fac.AntdButton("返回首页", type="primary", href="/", target="_self"),
+ ),
+ style={"height": "calc(60vh + 100px)"},
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_500.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_500.py
new file mode 100644
index 0000000..fbe226e
--- /dev/null
+++ b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/_500.py
@@ -0,0 +1,24 @@
+from dash import html
+import feffery_antd_components as fac
+from feffery_dash_utils.style_utils import style
+
+
+def render(e: str = None):
+ """渲染500状态页面"""
+
+ if e is None:
+ e = Exception("500状态页演示示例错误")
+
+ return fac.AntdCenter(
+ fac.AntdResult(
+ # 自定义状态图片
+ icon=html.Img(
+ src="/assets/imgs/status/500.svg",
+ style=style(height="50vh", pointerEvents="none"),
+ ),
+ title=fac.AntdText("系统内部错误", style=style(fontSize=20)),
+ subTitle="错误信息:" + str(e),
+ extra=fac.AntdButton("返回首页", type="primary", href="/", target="_self"),
+ ),
+ style={"height": "calc(60vh + 100px)"},
+ )
diff --git a/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/__init__.py b/magic_dash/templates/magic-dash-pro-sqlalchemy/views/status_pages/__init__.py
new file mode 100644
index 0000000..e69de29