# -*- coding: utf-8 -*- """ 首页 """ import reflex as rx from application.pages import render_conversation, render_library from application.pages.conversation import render_conversation from application.states import AuthState # 导航按钮字典 nav_buttons: dict = { "conversation": {"src": "/conversation.svg", "text": "会话"}, "library": {"src": "/library.svg", "text": "知识库"}, } """ 约定: 1 若组件仅用于当前页面则无需抽象为通用组件(application.components.*) """ def render_nav_button( nav_button: str, ) -> rx.Component: """ 渲染导航按钮 :param nav_button: 指定的导航按钮 :return : Component """ # 导航按钮激活状态 is_nav_button_actived = nav_button == AuthState.activated_nav_button return rx.vstack( # 渲染图标 rx.image( nav_buttons[nav_button]["src"], width="36px", height="36px", object_fit="contain", padding="6px", background_color=rx.cond( is_nav_button_actived, "var(--prismui-background-color-1)", "transparent", ), border_radius="var(--prismui-border-radius-2)", box_shadow=rx.cond( is_nav_button_actived, "var(--prismui-box-shadow-2)", "none" ), ), # 渲染标签 rx.text( nav_buttons[nav_button]["text"], line_height="20px", font_size="var(--prismui-font-size-1)", color="var(--prismui-color-1)", ), align_items="center", gap="4px", cursor="pointer", # 点击事件:将指定导航按钮设置为激活的导航按钮 on_click=AuthState.switch_activated_nav_button(nav_button), ) def render_settings_button(): """ 渲染设置按钮 :return: Component """ return rx.box( rx.hover_card.root( # 触发事件:点击自定义按钮 rx.hover_card.trigger( rx.box( rx.icon( "settings", stroke_width=1.5, width="20px", height="20px", color="var(--prismui-color-1)", ), display="flex", flex_flow="column", justify_content="center", align_items="center", width="36px", height="36px", border_radius="var(--prismui-border-radius-2)", style={ "&:hover": { "background": "var(--prismui-background-color-5)", }, # 鼠标悬停渲染背景颜色 }, cursor="pointer", ) ), # 渲染悬停卡片 rx.hover_card.content( rx.box( rx.box( transform="translateX(-50%) rotate(45deg)", position="absolute", top="calc(50% - 4px)", left="0", width="8px", height="8px", background_color="var(--prismui-background-color-1)", ), rx.box( "退出登录", display="flex", justify_content="center", align_items="center", width="100%", height="24px", line_height="16px", border_radius="var(--prismui-border-radius-1)", style={ "_hover": { "background_color": "var(--prismui-color-7, #f2f2f3)", }, }, # 点击事件:退出登录 on_click=lambda: AuthState.logout(), ), cursor="pointer", ), side="left", side_offset=8, position="relative", align="center", padding="4px 12px", box_shadow="var(--prismui-box-shadow-3)", font_family="var(--prismui-font-family)", overflow="visible", style={ "font-size": "var(--prismui-font-size-1) !important", "color": "var(--prismui-color-1) !important", }, ), open=AuthState.is_settings_hover_card_open, # 悬停卡片打开状态变化事件:设置设置悬停卡片打开状态 on_open_change=AuthState.set_settings_hover_card_open, ), min_width="14px", cursor="pointer", ) def render_sidebar() -> rx.Component: """ 渲染侧边栏 :return: Component """ return rx.vstack( rx.vstack( rx.vstack( # 品牌图标 rx.image( src="/logo.png", width="32px", height="32px", object_fit="contain", ), # 品牌名称 rx.text( "棱镜球", line_height="20px", font_size="calc(var(--prismui-font-size-1) - 1px)", # 较导航按钮标签弱化 font_weight="var(--prismui-font-weight-2)", ), align_items="center", gap="4px", ), # 渲染分隔线 rx.divider( width="32px", height="1px", margin="16px 0", background_color="var(--prismui-background-color-4)", ), rx.vstack( # 渲染会话导航按钮 render_nav_button("conversation"), # 渲染知识库导航按钮 render_nav_button("library"), gap="8px", ), align_items="center", width="100%", margin_top="12px", ), rx.vstack( rx.vstack( # 渲染设置按钮 render_settings_button(), ), padding_bottom="16px", gap="8px", ), flex_shrink="0", justify_content="space-between", align_items="center", width="60px", min_width="60px", max_width="60px", height="100%", ) def render_login_window() -> rx.Component: """ 渲染登录窗 :return: Component """ return rx.center( rx.hstack( rx.vstack( rx.text( "登录解锁更多功能", line_height="32px", font_size="var(--prismui-font-size-3)", font_weight="var(--prismui-font-weight-3)", margin_bottom="24px", ), rx.input( placeholder="请输入邮箱地址", value=AuthState.email, width="100%", height="45px", margin_bottom="24px", padding="4px", border_radius="var(--prismui-border-radius-3)", line_height="16px", # 值变化事件:设置邮箱 on_change=AuthState.set_email, # 获取焦点事件:重置登录错误信息 on_focus=AuthState.reset_error_message, ), # 自定义验证码输入框 rx.hstack( rx.input( placeholder="请输入验证码", max_length=6, value=AuthState.captcha, # 值变化事件:设置验证码 on_change=AuthState.set_captcha, # 获取焦点事件:重置登录错误信息 on_focus=AuthState.reset_error_message, flex=1, height="100%", padding="4px", line_height="16px", border="none", style={ "&": { "border": "none !important", }, # 清除父元素边框 }, ), rx.button( # 若验证码发送倒计时大于0则显示倒计时,否则显示获取验证码 rx.cond( AuthState.resend_captcha_countdown > 0, f"{AuthState.resend_captcha_countdown} s", "获取验证码", ), margin="0 8px", background_color="var(--prismui-background-color-1)", line_height="16px", color=rx.cond( AuthState.is_captcha_sending_disabled, "var(--prismui-color-4)", "var(--prismui-color-5)", ), cursor="pointer", # 点击事件:发送验证码 on_click=AuthState.send_captcha, disabled=AuthState.is_captcha_sending_disabled, ), width="100%", height="45px", align_items="center", border="var(--prismui-border-1)", border_radius="var(--prismui-border-radius-3)", ), rx.text( AuthState.login_error_message, width="100%", min_height="20px", text_align="left", line_height="20px", font_size="var(--prismui-font-size-1)", color="var(--prismui-color-6)", ), rx.box( rx.cond( AuthState.is_captcha_sent, rx.hstack( rx.icon( "circle-check", width="12px", height="12px", color="var(--prismui-color-2)", ), rx.text( "验证码已发送,请耐心等待", line_height="14px", font_size="var(--prismui-font-size-1)", color="var(--prismui-color-2)", ), margin="0 0 10px 0", gap="4px", ), ), display="flex", align_items="center", min_height="34px", ), rx.hstack( rx.checkbox( checked=AuthState.is_policies_agreed, # 值变化事件:切换协议同意状态 on_change=AuthState.toggle_policies_agreed, cursor="pointer", style={ "&::before": { "width": "12px !important", "height": "12px !important", }, "& svg": { "width": "8px !important", "height": "8px !important", }, "&[data-state='checked']::before": { "background-color": "var(--prismui-color-5) !important", }, }, ), rx.text( "我已阅读并同意", color="var(--prismui-color-2)", line_height="14px", ), rx.link( "用户协议、隐私政策", href="#", line_height="14px", color="var(--prismui-color-5)", cursor="pointer", ), margin_bottom="10px", align_items="center", gap="4px", font_size="var(--prismui-font-size-1)", ), rx.button( "登录", width="100%", height="38px", border_radius="var(--prismui-border-radius-3)", background_color="var(--prismui-color-5)", color="var(--prismui-color-9)", cursor="pointer", on_click=AuthState.login, loading=AuthState.is_logging_in, ), rx.text( "登录遇到问题?加他微信去吐槽", margin="20px 0 40px 0", line_height="14px", font_size="12px", color="var(--prismui-color-2)", ), width="396px", height="100%", padding="40px 48px 0 48px", align_items="center", background_color="var(--prismui-background-color-1)", ), rx.vstack( rx.image( src="/qrcode.png", width="208px", height="208px", padding="20px", background_color="var(--prismui-background-color-1)", border="var(--prismui-border-2)", border_radius="var(--prismui-border-radius-4)", ), rx.text( "加微信好友", margin="10px 0 12px 0", line_height="32px", font_size="var(--prismui-font-size-2)", font_weight="var(--prismui-font-weight-2)", ), align_items="center", justify_content="center", width="396px", height="100%", padding="72px 32px", ), background="var(--prismui-background-2)", border="1px solid #2222220f", border_radius="var(--prismui-border-radius-4)", box_shadow="0 4px 64px 0 #0000001a", overflow="hidden", ), position="absolute", top="0", left="0", z_index="9999", width="100vw", height="100vh", backdrop_filter="blur(4px)", ) def render_index() -> rx.Component: """ 渲染首页 """ return rx.fragment( rx.hstack( # 渲染侧边栏 render_sidebar(), # 根据侧边栏状态中激活的导航按钮渲染相应页面(本项目采用卡片布局) rx.match( AuthState.activated_nav_button, # 渲染知识库页面 ("library", render_library()), # 渲染会话页面 render_conversation(), ), width="100%", height="100vh", overflow="auto", padding="8px 8px 8px 0", background="var(--prismui-background-1)", ), # 若用户唯一标识为空则渲染登录窗 rx.cond( AuthState.user_id, rx.fragment(), render_login_window(), ), )