| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- "use client";
- import dynamic from "next/dynamic";
- import {
- ExclamationCircleFilled,
- FullscreenExitOutlined,
- FullscreenOutlined,
- GithubOutlined,
- HomeOutlined,
- LogoutOutlined,
- MenuOutlined,
- QuestionCircleFilled,
- SearchOutlined,
- UserOutlined,
- } from "@ant-design/icons";
- import {ProConfigProvider} from "@ant-design/pro-components";
- import type {SelectProps} from "antd";
- import {App, Dropdown, MenuProps, Modal, Select, Tooltip} from "antd";
- import {deleteCookie, getCookie} from "cookies-next";
- import Link from "next/link";
- import {usePathname, useRouter} from "next/navigation";
- import {useEffect, useRef, useState} from "react";
- import {IconMap, RouteInfo, UserInfo} from "../_modules/definies";
- import "./styles.css";
- import "../globals.css";
- import {displayModeIsDark, fetchApi, watchDarkModeChange,} from "../_modules/func";
- //动态引入 ProLayout,禁用 SSR
- const ProLayout = dynamic(
- () => import("@ant-design/pro-components").then((mod) => mod.ProLayout),
- { ssr: false }
- );
- export default function RootLayout({
- children,
- }: {
- children: React.ReactNode;
- }) {
- const { push } = useRouter();
- const redirectToLogin = () => {
- push("/login");
- };
- const [isDark, setIsDark] = useState(false);
- useEffect(() => {
- const token = getCookie("token");
- if (!token) {
- redirectToLogin();
- return;
- }
- getProfile();
- setIsDark(displayModeIsDark());
- const unsubscribe = watchDarkModeChange((matches: boolean) => {
- setIsDark(matches);
- });
- document.addEventListener("click", hideSearchInput);
- return () => {
- unsubscribe();
- document.removeEventListener("click", hideSearchInput);
- };
- }, []);
- const searchRef = useRef<HTMLDivElement>(null);
- const hideSearchInput = (e: any) => {
- if (searchRef.current && !searchRef.current.contains(e.target)) {
- setShowSearch(false);
- setSearchListData([]);
- }
- };
- const [showSearch, setShowSearch] = useState(false);
- const [isLogoutShow, setIsLogoutShow] = useState(false);
- const [confirmLoading, setConfirmLoading] = useState(false);
- const onActionClick: MenuProps["onClick"] = ({ key }) => {
- if (key === "logout") {
- setIsLogoutShow(true);
- } else if (key === "profile") {
- push("/user/profile");
- }
- };
- const [userInfo, setUserInfo] = useState<UserInfo>({
- nickName: "Monrtnon",
- avatar: "/avatar1.jpeg",
- });
- const getProfile = async () => {
- const data = await fetchApi("/api/getInfo", push);
- if (data) {
- setUserInfo({
- nickName: data.user.nickName,
- avatar:
- data.user.avatar === ""
- ? data.user.sex === "1"
- ? "/avatar1.jpeg"
- : "/avatar0.jpeg"
- : "/api" + data.user.avatar,
- });
- }
- };
- const [menuData, setMenuData] = useState<any[]>([]);
- const getRoutes = async () => {
- const body = await fetchApi("/api/getRouters", push);
- const rootChildren: Array<RouteInfo> = [];
- const searchMenuList: any[] = [];
- const indexRoute: RouteInfo = {
- path: "/home",
- name: "首页",
- icon: <HomeOutlined />,
- };
- searchMenuList.push({ value: indexRoute.path, text: indexRoute.name });
- rootChildren.push(indexRoute);
- if (body.data?.length > 0) {
- body.data.forEach((menu: any) => {
- const route: RouteInfo = {
- path: menu.path,
- name: menu.meta.title,
- icon:
- menu.meta.icon !== null ? (
- IconMap[menu.meta.icon.replace(/-/g, "") as "system"]
- ) : (
- <MenuOutlined />
- ),
- };
- if (menu.children?.length > 0) {
- if (route.name != null) {
- getSubMenu(route, menu.children, route.name, route.path, searchMenuList);
- }
- }
- rootChildren.push(route);
- });
- }
- setMenuData(searchMenuList);
- return rootChildren;
- };
- const getSubMenu = (
- parent: RouteInfo,
- menuChildren: any[],
- parentName: string,
- parentPath: string,
- searchMenuList: any[]
- ) => {
- const routeChildren: Array<RouteInfo> = [];
- menuChildren.forEach((menu: any) => {
- const route: RouteInfo = {
- path: menu.path,
- name: menu.meta.title,
- icon:
- menu.meta.icon !== null ? (
- IconMap[menu.meta.icon.replace(/-/g, "") as "system"]
- ) : (
- <MenuOutlined />
- ),
- };
- routeChildren.push(route);
- if (menu.children?.length > 0) {
- getSubMenu(
- route,
- menu.children,
- parentName + " > " + route.name,
- parentPath + "/" + route.path,
- searchMenuList
- );
- } else {
- searchMenuList.push({
- text: parentName + " > " + menu.meta.title,
- value: parentPath + "/" + route.path,
- });
- }
- });
- parent.routes = routeChildren;
- };
- const logout = async () => {
- setConfirmLoading(true);
- const data = await fetchApi("/api/logout", push, { method: "POST" });
- if (data.code === 200) {
- deleteCookie("token");
- redirectToLogin();
- setIsLogoutShow(false);
- setConfirmLoading(false);
- }
- };
- const pathName = usePathname();
- const [pathname, setPathname] = useState(pathName);
- const [searchListData, setSearchListData] = useState<SelectProps["options"]>([]);
- const handleSearch = (newValue: string) => {
- if (!newValue) return;
- setSearchListData(menuData.filter((item) => item.text.includes(newValue)));
- };
- const handleSearchChange = (path: string) => {
- setPathname(path || "/index");
- push(path);
- };
- const [isFullscreen, setIsFullscreen] = useState(false);
- const openFullscreen = () => {
- document.documentElement.requestFullscreen?.();
- setIsFullscreen(true);
- };
- const closeFullscreen = () => {
- document.exitFullscreen?.();
- setIsFullscreen(false);
- };
- return (
- <App>
- <ProConfigProvider dark={isDark}>
- <ProLayout
- title="MorTnon 若依"
- logo="https://static.dongfangzan.cn/img/mortnon.svg"
- menu={{ request: getRoutes }}
- layout="mix"
- splitMenus={false}
- defaultCollapsed={false}
- breakpoint={false}
- fixedHeader={false}
- location={{ pathname }}
- onMenuHeaderClick={(e) => console.log(e)}
- menuItemRender={(item, dom) => (
- <div onClick={() => setPathname(item.path || "/index")}>
- <Link href={item.path ?? ""}>{dom}</Link>
- </div>
- )}
- subMenuItemRender={(item, dom) => dom}
- avatarProps={{
- src: userInfo.avatar,
- size: "small",
- title: userInfo.nickName,
- render: (_, dom) => (
- <Dropdown
- menu={{
- items: [
- { key: "profile", icon: <UserOutlined />, label: "个人中心" },
- { type: "divider" },
- { key: "logout", icon: <LogoutOutlined />, label: "退出登录" },
- ],
- onClick: onActionClick,
- }}
- >
- {dom}
- </Dropdown>
- ),
- }}
- actionsRender={(props) => {
- if (props.isMobile) return [];
- return [
- <div
- key="search"
- style={{ height: "100%", display: "flex", alignItems: "center" }}
- ref={searchRef}
- >
- <SearchOutlined
- style={{ color: "var(--ant-primary-color)", marginRight: showSearch ? 8 : 0 }}
- onClick={() => setShowSearch(!showSearch)}
- />
- {showSearch && (
- <Select
- showSearch
- autoFocus
- style={{ borderRadius: 4, marginInlineEnd: 12, width: 300 }}
- suffixIcon={null}
- placeholder="搜索菜单"
- variant="borderless"
- filterOption={false}
- notFoundContent={null}
- onSearch={handleSearch}
- onChange={handleSearchChange}
- options={searchListData?.map((d) => ({ value: d.value, label: d.text }))}
- />
- )}
- </div>,
- <Link key="github" href="https://github.com/mortise-and-tenon/RuoYi-React-Pro" target="_blank">
- <Tooltip title="Github 源码仓库">
- <GithubOutlined style={{ color: "gray" }} />
- </Tooltip>
- </Link>,
- <Link key="question" href="https://doc.ruoyi.vip/ruoyi-vue/" target="_blank">
- <Tooltip title="RuoYi 文档">
- <QuestionCircleFilled style={{ color: "gray" }} />
- </Tooltip>
- </Link>,
- isFullscreen ? (
- <FullscreenExitOutlined key="exit" onClick={closeFullscreen} />
- ) : (
- <FullscreenOutlined key="full" onClick={openFullscreen} />
- ),
- ];
- }}
- menuFooterRender={(props) =>
- props?.collapsed ? undefined : (
- <div style={{ textAlign: "center", paddingBlockStart: 12 }}>
- <div>©{new Date().getFullYear()} Mortnon.</div>
- </div>
- )
- }
- >
- <Modal
- title={<><ExclamationCircleFilled style={{ color: "#faad14" }} /> 提示</>}
- open={isLogoutShow}
- onOk={logout}
- onCancel={() => setIsLogoutShow(false)}
- confirmLoading={confirmLoading}
- >
- 确定注销并退出系统吗?
- </Modal>
- {children}
- </ProLayout>
- </ProConfigProvider>
- </App>
- );
- }
|