# -*- coding: utf-8 -*- """ 首页 """ import reflex as rx from application.pages import conversation, knowledge_base from application.states import AuthState # 导航按钮字典 nav_buttons: dict = { "conversation": {"src": "/conversation.svg", "text": "会话"}, "knowledge_base": {"src": "/knowledge_base.svg", "text": "知识库"}, } """ 约定: 1 若组件仅用于当前页面则无需抽象为通用组件(application.components.*) """ def nav_button( nav_button: str, ) -> rx.Component: """ 导航按钮 :param nav_button: 指定的导航按钮 :return : Component """ # 导航按钮激活状态 is_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_actived, "var(--prismui-background-color-1)", "transparent", ), border_radius="var(--prismui-border-radius-3)", box_shadow=rx.cond(is_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 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-3)", 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-background-color-6)", }, }, # 点击事件:退出登录 on_click=lambda: AuthState.logout(), ), cursor="pointer", ), side="left", side_offset=8, position="relative", align="center", padding="4px 12px", border_radius="var(--prismui-border-radius-1)", 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 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( # 会话导航按钮 nav_button("conversation"), # 知识库导航按钮 nav_button("knowledge_base"), gap="8px", ), align_items="center", width="100%", margin_top="12px", ), rx.vstack( rx.vstack( # 设置按钮 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 login_popup() -> rx.Component: """ 登录弹窗 :return: Component """ return rx.center( rx.hstack( rx.vstack( # 标题 rx.text( "登录解锁更多功能", margin_bottom="24px", line_height="32px", font_size="var(--prismui-font-size-5)", font_weight="var(--prismui-font-weight-3)", ), # 邮箱输入框 rx.input( placeholder="请输入邮箱", # 值绑定邮箱 value=AuthState.email, # 值变化事件:设置邮箱 on_change=AuthState.set_email, # 获取焦点事件:重置登录错误信息 on_focus=AuthState.reset_login_error_message, width="100%", height="45px", padding="4px", margin_bottom="24px", border_radius="var(--prismui-border-radius-4)", line_height="16px", ), # 验证码输入框 rx.hstack( rx.input( placeholder="请输入验证码", # 值绑定验证码 value=AuthState.captcha, max_length=6, # 值变化事件:设置验证码 on_change=AuthState.set_captcha, # 获取焦点事件:重置登录错误信息 on_focus=AuthState.reset_login_error_message, flex=1, height="100%", padding="4px", border_radius="var(--prismui-border-radius-4)", line_height="16px", border="none !important", ), rx.button( # 若再次发送验证码倒计时大于0则显示倒计时,否则显示获取验证码 rx.cond( AuthState.resend_captcha_countdown > 0, f"{AuthState.resend_captcha_countdown} s", "获取验证码", ), # 点击事件:发送验证码 on_click=AuthState.send_captcha, # 是否禁用绑定验证码发送禁用状态 disabled=AuthState.is_captcha_sending_disabled, margin="0 8px", background_color="var(--prismui-background-color-1)", line_height="16px", # 若验证码发送禁用状态为是则颜色设置为 color-4,否则设置为 color-5(主题色) color=rx.cond( AuthState.is_captcha_sending_disabled, "var(--prismui-color-4)", "var(--prismui-color-5)", ), cursor=rx.cond( AuthState.is_captcha_sending_disabled, "not-allowed", "pointer", ), ), width="100%", height="45px", align_items="center", border="var(--prismui-border-1)", border_radius="var(--prismui-border-radius-4)", ), 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-4)", 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", ), 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-6)", ), 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-6)", 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 index() -> rx.Component: """ 首页(默认显示会话页面) """ return rx.fragment( rx.hstack( # 侧边栏 sidebar(), # 根据侧边栏状态中激活的导航按钮相应页面(本项目采用卡片布局) rx.match( AuthState.activated_nav_button, # 知识库页面 ("knowledge_base", knowledge_base()), # 会话页面 conversation(), ), width="100%", height="100vh", overflow="auto", padding="8px 8px 8px 0", background="var(--prismui-background-1)", ), # 若用户唯一标识为空则登录窗 rx.cond( AuthState.user_id, rx.fragment(), login_popup(), ), )