456 lines
16 KiB
Python
456 lines
16 KiB
Python
# -*- 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-3)",
|
||
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-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 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-4)",
|
||
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-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",
|
||
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-5)",
|
||
),
|
||
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-5)",
|
||
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(),
|
||
),
|
||
)
|