Explorar el Código

Merge pull request #1867 from visuddhinanda/agile

避免不同页面重复查询notification
visuddhinanda hace 2 años
padre
commit
9478f940a1

+ 11 - 8
dashboard/src/components/auth/Avatar.tsx

@@ -20,8 +20,9 @@ const { Title } = Typography;
 
 interface IWidget {
   placement?: TooltipPlacement;
+  style?: React.CSSProperties;
 }
-const AvatarWidget = ({ placement = "bottomRight" }: IWidget) => {
+const AvatarWidget = ({ style, placement = "bottomRight" }: IWidget) => {
   const intl = useIntl();
   const navigate = useNavigate();
   const [userName, setUserName] = useState<string>();
@@ -82,13 +83,15 @@ const AvatarWidget = ({ placement = "bottomRight" }: IWidget) => {
   return (
     <>
       <Popover content={user ? userCard : login} placement={placement}>
-        <Avatar
-          style={{ backgroundColor: user ? "#87d068" : "gray" }}
-          icon={<UserOutlined />}
-          size="small"
-        >
-          {user ? nickName?.slice(0, 1) : undefined}
-        </Avatar>
+        <span style={style}>
+          <Avatar
+            style={{ backgroundColor: user ? "#87d068" : "gray" }}
+            icon={<UserOutlined />}
+            size="small"
+          >
+            {user ? nickName?.slice(0, 1) : undefined}
+          </Avatar>
+        </span>
       </Popover>
     </>
   );

+ 20 - 15
dashboard/src/components/auth/ToStudio.tsx

@@ -5,27 +5,32 @@ import { Link } from "react-router-dom";
 import { useAppSelector } from "../../hooks";
 import { currentUser as _currentUser } from "../../reducers/current-user";
 
-const ToStudioWidget = () => {
+interface IWidget {
+  style?: React.CSSProperties;
+}
+const ToStudioWidget = ({ style }: IWidget) => {
   const intl = useIntl();
 
   const user = useAppSelector(_currentUser);
 
   if (typeof user !== "undefined") {
     return (
-      <Button
-        type="primary"
-        style={{
-          paddingLeft: 18,
-          paddingRight: 18,
-          backgroundColor: "#52974e",
-        }}
-      >
-        <Link to={`/studio/${user.realName}/home`} target="_blank">
-          {intl.formatMessage({
-            id: "columns.studio.title",
-          })}
-        </Link>
-      </Button>
+      <span style={style}>
+        <Button
+          type="primary"
+          style={{
+            paddingLeft: 18,
+            paddingRight: 18,
+            backgroundColor: "#52974e",
+          }}
+        >
+          <Link to={`/studio/${user.realName}/home`} target="_blank">
+            {intl.formatMessage({
+              id: "columns.studio.title",
+            })}
+          </Link>
+        </Button>
+      </span>
     );
   } else {
     return <></>;

+ 5 - 2
dashboard/src/components/general/SearchButton.tsx

@@ -2,9 +2,12 @@ import { Button } from "antd";
 import { Link } from "react-router-dom";
 import { SearchOutlined } from "@ant-design/icons";
 
-const SearchButtonWidget = () => {
+interface IWidget {
+  style?: React.CSSProperties;
+}
+const SearchButtonWidget = ({ style }: IWidget) => {
   return (
-    <Button type="text" size="small">
+    <Button type="text" size="small" style={style}>
       <Link to="/search/home" target={"_blank"}>
         <SearchOutlined style={{ color: "white" }} />
       </Link>

+ 53 - 17
dashboard/src/components/notification/NotificationIcon.tsx

@@ -4,15 +4,45 @@ import { Badge, Popover } from "antd";
 import { get } from "../../request";
 import { INotificationListResponse } from "../api/notification";
 import NotificationList from "./NotificationList";
+import { useAppSelector } from "../../hooks";
+import { currentUser } from "../../reducers/current-user";
 
 const NotificationIconWidget = () => {
   const [count, setCount] = useState<number>();
+  const user = useAppSelector(currentUser);
+
   useEffect(() => {
     let timer = setInterval(() => {
+      if (!user) {
+        return;
+      }
+      const now = new Date();
+      const notificationUpdatedAt = localStorage.getItem(
+        "notification/updatedAt"
+      );
+      if (notificationUpdatedAt) {
+        if (now.getTime() - parseInt(notificationUpdatedAt) < 59000) {
+          const notificationCount = localStorage.getItem("notification/count");
+          if (notificationCount !== null) {
+            setCount(parseInt(notificationCount));
+            console.debug("has notification count");
+            return;
+          }
+        }
+      }
+
       const url = `/v2/notification?view=to&status=unread&limit=1`;
       console.info("url", url);
       get<INotificationListResponse>(url).then((json) => {
         if (json.ok) {
+          localStorage.setItem(
+            "notification/updatedAt",
+            now.getTime().toString()
+          );
+          localStorage.setItem(
+            "notification/count",
+            json.data.count.toString()
+          );
           setCount(json.data.count);
           if (json.data.count > 0) {
             const newMessageTime = json.data.rows[0].created_at;
@@ -46,23 +76,29 @@ const NotificationIconWidget = () => {
 
   return (
     <>
-      <Popover
-        placement="bottomLeft"
-        arrowPointAtCenter
-        destroyTooltipOnHide
-        content={
-          <div style={{ width: 600 }}>
-            <NotificationList onChange={(unread: number) => setCount(unread)} />
-          </div>
-        }
-        trigger="click"
-      >
-        <Badge count={count} size="small">
-          <span style={{ color: "white", cursor: "pointer" }}>
-            <NotificationIcon />
-          </span>
-        </Badge>{" "}
-      </Popover>
+      {user ? (
+        <Popover
+          placement="bottomLeft"
+          arrowPointAtCenter
+          destroyTooltipOnHide
+          content={
+            <div style={{ width: 600 }}>
+              <NotificationList
+                onChange={(unread: number) => setCount(unread)}
+              />
+            </div>
+          }
+          trigger="click"
+        >
+          <Badge count={count} size="small">
+            <span style={{ color: "white", cursor: "pointer" }}>
+              <NotificationIcon />
+            </span>
+          </Badge>
+        </Popover>
+      ) : (
+        <></>
+      )}
     </>
   );
 };

+ 2 - 0
dashboard/src/components/studio/HeadBar.tsx

@@ -6,6 +6,7 @@ import UiLangSelect from "../general/UiLangSelect";
 import SignInAvatar from "../auth/SignInAvatar";
 import ToLibrary from "../auth/ToLibrary";
 import ThemeSelect from "../general/ThemeSelect";
+import NotificationIcon from "../notification/NotificationIcon";
 
 const { Search } = Input;
 const { Header } = Layout;
@@ -47,6 +48,7 @@ const HeadBarWidget = () => {
           <Space>
             <ToLibrary />
             <SignInAvatar />
+            <NotificationIcon />
             <UiLangSelect />
             <ThemeSelect />
           </Space>

+ 103 - 92
dashboard/src/pages/library/article/show.tsx

@@ -51,6 +51,7 @@ import LoginAlertModal from "../../../components/auth/LoginAlertModal";
 import ShareButton from "../../../components/export/ShareButton";
 import ChannelAlert from "../../../components/channel/ChannelAlert";
 import PrPull from "../../../components/corpus/PrPull";
+import NotificationIcon from "../../../components/notification/NotificationIcon";
 
 export interface ISearchParams {
   key: string;
@@ -146,107 +147,117 @@ const Widget = () => {
     <div id="article-root">
       <Affix offsetTop={0}>
         <Header
+          className="header"
           style={{
             height: 44,
             lineHeight: 44,
-            display: "flex",
-            justifyContent: "space-between",
-            padding: "5px",
+            paddingLeft: 10,
+            paddingRight: 10,
           }}
         >
-          <div style={{ display: "flex" }} key="left">
-            <MainMenu />
-            <NetStatus style={{ color: "white" }} />
-          </div>
-          <div style={{ display: "flex" }} key="middle"></div>
-          <div style={{ display: "flex" }} key="right">
-            {type === "article" && loadedArticleData ? (
-              <>
-                <Button
-                  ghost
-                  onClick={(event) => {
-                    const url = `/studio/${loadedArticleData.studio?.realName}/article/edit/${loadedArticleData.uid}`;
-                    if (event.ctrlKey || event.metaKey) {
-                      window.open(fullUrl(url), "_blank");
-                    } else {
-                      navigate(url);
+          <div
+            style={{
+              display: "flex",
+              width: "100%",
+              justifyContent: "space-between",
+            }}
+          >
+            <div style={{ display: "flex" }} key="left">
+              <MainMenu />
+              <NetStatus style={{ color: "white" }} />
+            </div>
+            <div style={{ display: "flex" }} key="middle"></div>
+            <div
+              style={{ display: "flex", height: 44, alignItems: "center" }}
+              key="right"
+            >
+              {type === "article" && loadedArticleData ? (
+                <>
+                  <Button
+                    ghost
+                    onClick={(event) => {
+                      const url = `/studio/${loadedArticleData.studio?.realName}/article/edit/${loadedArticleData.uid}`;
+                      if (event.ctrlKey || event.metaKey) {
+                        window.open(fullUrl(url), "_blank");
+                      } else {
+                        navigate(url);
+                      }
+                    }}
+                  >
+                    Edit
+                  </Button>
+                </>
+              ) : undefined}
+              <ShareButton
+                type={type as ArticleType}
+                book={searchParams.get("book")}
+                para={searchParams.get("par")}
+                channelId={searchParams.get("channel")}
+                articleId={id}
+                anthologyId={searchParams.get("anthology")}
+              />
+              <SearchButton style={{ marginRight: 8 }} />
+              <ToStudio style={{ marginRight: 8 }} />
+              <Avatar placement="bottom" style={{ marginRight: 8 }} />
+              <NotificationIcon />
+              <ThemeSelect />
+              <ModeSwitch
+                channel={searchParams.get("channel")}
+                currMode={currMode}
+                onModeChange={(e: ArticleMode) => {
+                  let output: any = { mode: e };
+                  searchParams.forEach((value, key) => {
+                    console.log(value, key);
+                    if (key !== "mode") {
+                      output[key] = value;
                     }
-                  }}
-                >
-                  Edit
-                </Button>
-              </>
-            ) : undefined}
-            <ShareButton
-              type={type as ArticleType}
-              book={searchParams.get("book")}
-              para={searchParams.get("par")}
-              channelId={searchParams.get("channel")}
-              articleId={id}
-              anthologyId={searchParams.get("anthology")}
-            />
-            <SearchButton />
-            <Divider type="vertical" />
-            <ToStudio />
-            <Divider type="vertical" />
-            <Avatar placement="bottom" />
-            <Divider type="vertical" />
-            <ThemeSelect />
-            <Divider type="vertical" />
-            <ModeSwitch
-              channel={searchParams.get("channel")}
-              currMode={currMode}
-              onModeChange={(e: ArticleMode) => {
-                let output: any = { mode: e };
-                searchParams.forEach((value, key) => {
-                  console.log(value, key);
-                  if (key !== "mode") {
-                    output[key] = value;
-                  }
-                });
-                setSearchParams(output);
-              }}
-              onChannelChange={(channels: IChannel[], mode: ArticleMode) => {
-                let output: any = {
-                  mode: mode,
-                };
-                if (channels.length > 0) {
-                  output["channel"] = channels.map((item) => item.id).join("_");
-                }
-                searchParams.forEach((value, key) => {
-                  console.log(value, key);
-                  if (key !== "mode" && key !== "channel") {
-                    output[key] = value;
+                  });
+                  setSearchParams(output);
+                }}
+                onChannelChange={(channels: IChannel[], mode: ArticleMode) => {
+                  let output: any = {
+                    mode: mode,
+                  };
+                  if (channels.length > 0) {
+                    output["channel"] = channels
+                      .map((item) => item.id)
+                      .join("_");
                   }
-                });
-                setSearchParams(output);
-              }}
-            />
-            <Tooltip title="文章目录" placement="bottomLeft">
-              <Button
-                style={{ display: "block", color: "white" }}
-                icon={<UnorderedListOutlined />}
-                type="text"
-                onClick={() => setAnchorNavOpen((value) => !value)}
-              />
-            </Tooltip>
-            <Tooltip title="侧边栏" placement="bottomLeft">
-              <Button
-                style={{ display: "block", color: "white" }}
-                icon={<ColumnOutlinedIcon />}
-                type="text"
-                onClick={() =>
-                  setRightPanel((value) => {
-                    if (value === "close") {
-                      setAnchorNavShow(false);
-                    } else {
-                      setAnchorNavShow(true);
+                  searchParams.forEach((value, key) => {
+                    console.log(value, key);
+                    if (key !== "mode" && key !== "channel") {
+                      output[key] = value;
                     }
-                    return value === "close" ? "open" : "close";
-                  })
-                }
+                  });
+                  setSearchParams(output);
+                }}
               />
-            </Tooltip>
+              <Tooltip title="文章目录" placement="bottomLeft">
+                <Button
+                  style={{ display: "block", color: "white" }}
+                  icon={<UnorderedListOutlined />}
+                  type="text"
+                  onClick={() => setAnchorNavOpen((value) => !value)}
+                />
+              </Tooltip>
+              <Tooltip title="侧边栏" placement="bottomLeft">
+                <Button
+                  style={{ display: "block", color: "white" }}
+                  icon={<ColumnOutlinedIcon />}
+                  type="text"
+                  onClick={() =>
+                    setRightPanel((value) => {
+                      if (value === "close") {
+                        setAnchorNavShow(false);
+                      } else {
+                        setAnchorNavShow(true);
+                      }
+                      return value === "close" ? "open" : "close";
+                    })
+                  }
+                />
+              </Tooltip>
+            </div>
           </div>
         </Header>
       </Affix>