Jelajahi Sumber

Merge branch 'agile' of github.com:iapt-platform/mint into agile

Jeremy Zheng 2 tahun lalu
induk
melakukan
4d3b494eff
59 mengubah file dengan 327 tambahan dan 169 penghapusan
  1. 4 3
      dashboard/src/components/anthology/AnthologyCreate.tsx
  2. 3 3
      dashboard/src/components/anthology/AnthologyList.tsx
  3. 6 1
      dashboard/src/components/anthology/EditableTocTree.tsx
  4. 1 0
      dashboard/src/components/api/Article.ts
  5. 2 0
      dashboard/src/components/api/Dict.ts
  6. 7 1
      dashboard/src/components/article/AnthologyDetail.tsx
  7. 0 6
      dashboard/src/components/article/Article.tsx
  8. 5 1
      dashboard/src/components/article/ArticleCardMainMenu.tsx
  9. 1 0
      dashboard/src/components/article/ArticleDrawer.tsx
  10. 0 1
      dashboard/src/components/article/ArticleEditDrawer.tsx
  11. 3 3
      dashboard/src/components/article/ArticleList.tsx
  12. 33 4
      dashboard/src/components/article/NavigateButton.tsx
  13. 9 1
      dashboard/src/components/article/ToolButtonToc.tsx
  14. 1 3
      dashboard/src/components/article/TypeArticle.tsx
  15. 19 5
      dashboard/src/components/article/TypeArticleReader.tsx
  16. 7 1
      dashboard/src/components/auth/Avatar.tsx
  17. 7 1
      dashboard/src/components/auth/SignInAvatar.tsx
  18. 2 8
      dashboard/src/components/channel/ChannelTable.tsx
  19. 0 4
      dashboard/src/components/channel/ChannelTypeSelect.tsx
  20. 1 1
      dashboard/src/components/corpus/ChapterInChannel.tsx
  21. 1 1
      dashboard/src/components/corpus/Recent.tsx
  22. 6 0
      dashboard/src/components/dict/MyCreate.tsx
  23. 9 3
      dashboard/src/components/dict/UserDictList.tsx
  24. 8 2
      dashboard/src/components/home/ChapterNew.tsx
  25. 5 1
      dashboard/src/components/home/CourseNew.tsx
  26. 8 3
      dashboard/src/components/library/FooterBar.tsx
  27. 1 1
      dashboard/src/components/library/HeadBar.tsx
  28. 1 1
      dashboard/src/components/nut/users/NonSignInSharedLinks.tsx
  29. 3 1
      dashboard/src/components/nut/users/SignUp.tsx
  30. 3 1
      dashboard/src/components/share/ShareModal.tsx
  31. 1 3
      dashboard/src/components/studio/HeadBar.tsx
  32. 10 4
      dashboard/src/components/studio/LeftSider.tsx
  33. 3 1
      dashboard/src/components/studio/PublicitySelect.tsx
  34. 14 2
      dashboard/src/components/template/Wbw/WbwDetail.tsx
  35. 1 1
      dashboard/src/components/template/Wbw/WbwDetailFm.tsx
  36. 1 1
      dashboard/src/components/template/Wbw/WbwFactorMeaning.tsx
  37. 2 2
      dashboard/src/components/template/Wbw/WbwPali.tsx
  38. 2 2
      dashboard/src/components/template/Wbw/WbwWord.tsx
  39. 11 5
      dashboard/src/components/template/WbwSent.tsx
  40. 9 1
      dashboard/src/components/term/TermList.tsx
  41. 9 7
      dashboard/src/components/transfer/TransferList.tsx
  42. 5 5
      dashboard/src/components/users/SignUp.tsx
  43. 1 0
      dashboard/src/locales/en-US/buttons.ts
  44. 0 1
      dashboard/src/locales/en-US/channel/index.ts
  45. 1 0
      dashboard/src/locales/en-US/index.ts
  46. 5 0
      dashboard/src/locales/en-US/label.ts
  47. 5 4
      dashboard/src/locales/en-US/nut/index.ts
  48. 2 1
      dashboard/src/locales/zh-Hans/buttons.ts
  49. 14 15
      dashboard/src/locales/zh-Hans/channel/index.ts
  50. 1 0
      dashboard/src/locales/zh-Hans/index.ts
  51. 6 1
      dashboard/src/locales/zh-Hans/label.ts
  52. 2 1
      dashboard/src/locales/zh-Hans/nut/index.ts
  53. 12 15
      dashboard/src/pages/admin/users/list.tsx
  54. 8 1
      dashboard/src/pages/library/article/show.tsx
  55. 7 1
      dashboard/src/pages/nut/users/sign-in.tsx
  56. 4 1
      dashboard/src/pages/studio/anthology/edit.tsx
  57. 29 30
      dashboard/src/pages/studio/course/list.tsx
  58. 2 2
      dashboard/src/pages/studio/group/list.tsx
  59. 4 1
      dashboard/src/pages/users/sign-up.tsx

+ 4 - 3
dashboard/src/components/anthology/AnthologyCreate.tsx

@@ -33,12 +33,13 @@ const AnthologyCreateWidget = ({ studio, onSuccess }: IWidget) => {
           return;
         }
         values.studio = studio;
-        console.log(values);
+        const url = `/v2/anthology`;
+        console.info("api request", url, values);
         const res = await post<IAnthologyCreateRequest, IAnthologyResponse>(
-          `/v2/anthology`,
+          url,
           values
         );
-        console.log(res);
+        console.debug("api response", res);
         if (res.ok) {
           message.success(intl.formatMessage({ id: "flashes.success" }));
           if (typeof onSuccess !== "undefined") {

+ 3 - 3
dashboard/src/components/anthology/AnthologyList.tsx

@@ -353,7 +353,7 @@ const AnthologyListWidget = ({
                 key: "my",
                 label: (
                   <span>
-                    此工作室的
+                    {intl.formatMessage({ id: "labels.this-studio" })}
                     {renderBadge(myNumber, activeKey === "my")}
                   </span>
                 ),
@@ -362,7 +362,7 @@ const AnthologyListWidget = ({
                 key: "collaboration",
                 label: (
                   <span>
-                    协作
+                    {intl.formatMessage({ id: "labels.collaboration" })}
                     {renderBadge(
                       collaborationNumber,
                       activeKey === "collaboration"
@@ -383,7 +383,7 @@ const AnthologyListWidget = ({
       <Modal
         destroyOnClose={true}
         width={700}
-        title="协作"
+        title={intl.formatMessage({ id: "labels.collaboration" })}
         open={isModalOpen}
         onOk={handleOk}
         onCancel={handleCancel}

+ 6 - 1
dashboard/src/components/anthology/EditableTocTree.tsx

@@ -6,6 +6,7 @@ import { get as getUiLang } from "../../locales";
 
 import { get, post, put } from "../../request";
 import {
+  IAnthologyDataResponse,
   IArticleCreateRequest,
   IArticleDataResponse,
   IArticleMapAddResponse,
@@ -25,10 +26,12 @@ import { fullUrl, randomString } from "../../utils";
 interface IWidget {
   anthologyId?: string;
   studioName?: string;
+  anthology?: IAnthologyDataResponse;
   onSelect?: Function;
 }
 const EditableTocTreeWidget = ({
   anthologyId,
+  anthology,
   studioName,
   onSelect,
 }: IWidget) => {
@@ -140,9 +143,10 @@ const EditableTocTreeWidget = ({
             `/v2/article`,
             {
               title: "new article",
-              lang: getUiLang(),
+              lang: anthology?.lang ?? getUiLang(),
               studio: studioName,
               anthologyId: anthologyId,
+              status: anthology?.status ?? undefined,
             }
           );
 
@@ -174,6 +178,7 @@ const EditableTocTreeWidget = ({
       />
       <ArticleDrawer
         articleId={viewArticle?.id}
+        anthologyId={anthologyId}
         type="article"
         open={openViewer}
         title={viewArticle?.title_text}

+ 1 - 0
dashboard/src/components/api/Article.ts

@@ -129,6 +129,7 @@ export interface IArticleCreateRequest {
   studio: string;
   anthologyId?: string;
   parentId?: string;
+  status?: number;
 }
 
 export interface IAnthologyCreateRequest {

+ 2 - 0
dashboard/src/components/api/Dict.ts

@@ -20,6 +20,7 @@ export interface IDictRequest {
   creator_id?: number;
   editor?: IUser;
   studio?: IStudio;
+  status?: number;
   updated_at?: string;
 }
 export interface IUserDictCreate {
@@ -58,6 +59,7 @@ export interface IApiResponseDictData {
   updated_at: string;
   exp?: number;
   editor?: IUser;
+  status?: number;
 }
 export interface IApiResponseDict {
   ok: boolean;

+ 7 - 1
dashboard/src/components/article/AnthologyDetail.tsx

@@ -11,6 +11,7 @@ import StudioName from "../auth/Studio";
 import TimeShow from "../general/TimeShow";
 import Marked from "../general/Marked";
 import AnthologyTocTree from "../anthology/AnthologyTocTree";
+import { useIntl } from "react-intl";
 
 const { Title, Text, Paragraph } = Typography;
 
@@ -36,6 +37,7 @@ const AnthologyDetailWidget = ({
   onError,
 }: IWidgetAnthologyDetail) => {
   const [tableData, setTableData] = useState<IAnthologyData>();
+  const intl = useIntl();
 
   useEffect(() => {
     fetchData(aid);
@@ -100,7 +102,11 @@ const AnthologyDetailWidget = ({
       <Paragraph>
         <Marked text={tableData?.summary} />
       </Paragraph>
-      <Title level={5}>目录</Title>
+      <Title level={5}>
+        {intl.formatMessage({
+          id: "labels.table-of-content",
+        })}
+      </Title>
       <AnthologyTocTree
         anthologyId={aid}
         channels={channels}

+ 0 - 6
dashboard/src/components/article/Article.tsx

@@ -53,14 +53,11 @@ interface IWidget {
   para?: string | null;
   anthologyId?: string | null;
   courseId?: string | null;
-  exerciseId?: string;
-  userName?: string;
   active?: boolean;
   focus?: string | null;
   hideInteractive?: boolean;
   hideTitle?: boolean;
   onArticleChange?: Function;
-  onFinal?: Function;
   onLoad?: Function;
   onAnthologySelect?: Function;
   onTitle?: Function;
@@ -74,15 +71,12 @@ const ArticleWidget = ({
   articleId,
   anthologyId,
   courseId,
-  exerciseId,
-  userName,
   mode = "read",
   active = false,
   focus,
   hideInteractive = false,
   hideTitle = false,
   onArticleChange,
-  onFinal,
   onLoad,
   onAnthologySelect,
   onTitle,

+ 5 - 1
dashboard/src/components/article/ArticleCardMainMenu.tsx

@@ -4,12 +4,14 @@ import { MenuOutlined, PushpinOutlined } from "@ant-design/icons";
 import PaliTextToc from "./PaliTextToc";
 import Find from "./Find";
 import Nav from "./Nav";
+import { useIntl } from "react-intl";
 
 interface IWidget {
   type?: string;
   articleId?: string;
 }
 const ArticleCardMainMenuWidget = ({ type, articleId }: IWidget) => {
+  const intl = useIntl();
   const id = articleId?.split("_");
   let tocWidget = <></>;
   if (id && id.length > 0) {
@@ -34,7 +36,9 @@ const ArticleCardMainMenuWidget = ({ type, articleId }: IWidget) => {
       }}
       items={[
         {
-          label: `目录`,
+          label: intl.formatMessage({
+            id: "labels.table-of-content",
+          }),
           key: "1",
           children: <div style={styleTabBody}>{tocWidget}</div>,
         },

+ 1 - 0
dashboard/src/components/article/ArticleDrawer.tsx

@@ -105,6 +105,7 @@ const ArticleDrawerWidget = ({
           para={para}
           channelId={channelId}
           articleId={articleId}
+          anthologyId={anthologyId}
           mode={mode}
           onArticleEdit={(value: IArticleDataResponse) => {
             setDrawerTitle(value.title_text);

+ 0 - 1
dashboard/src/components/article/ArticleEditDrawer.tsx

@@ -3,7 +3,6 @@ import React, { useEffect, useState } from "react";
 import { IArticleDataResponse } from "../api/Article";
 
 import ArticleEdit from "./ArticleEdit";
-import ArticleEditTools from "./ArticleEditTools";
 
 interface IWidget {
   trigger?: React.ReactNode;

+ 3 - 3
dashboard/src/components/article/ArticleList.tsx

@@ -507,7 +507,7 @@ const ArticleListWidget = ({
                 key: "my",
                 label: (
                   <span>
-                    此工作室的
+                    {intl.formatMessage({ id: "labels.this-studio" })}
                     {renderBadge(myNumber, activeKey === "my")}
                   </span>
                 ),
@@ -516,7 +516,7 @@ const ArticleListWidget = ({
                 key: "collaboration",
                 label: (
                   <span>
-                    协作
+                    {intl.formatMessage({ id: "labels.collaboration" })}
                     {renderBadge(
                       collaborationNumber,
                       activeKey === "collaboration"
@@ -538,7 +538,7 @@ const ArticleListWidget = ({
       <Modal
         destroyOnClose={true}
         width={700}
-        title="协作"
+        title={intl.formatMessage({ id: "labels.collaboration" })}
         open={isModalOpen}
         onOk={handleOk}
         onCancel={handleCancel}

+ 33 - 4
dashboard/src/components/article/NavigateButton.tsx

@@ -1,7 +1,8 @@
-import { Affix, Button, Dropdown, Space, Typography } from "antd";
+import { Button, Dropdown, Modal, Space, Typography } from "antd";
 import { DoubleRightOutlined, DoubleLeftOutlined } from "@ant-design/icons";
+import { FolderOutlined } from "@ant-design/icons";
+
 import { ITocPathNode } from "../corpus/TocPath";
-import { useEffect, useRef } from "react";
 
 const { Paragraph, Text } = Typography;
 
@@ -23,6 +24,8 @@ interface IWidget {
   prevTitle?: string;
   nextTitle?: string;
   path?: ITocPathNode[];
+  topOfChapter?: boolean;
+  endOfChapter?: boolean;
   onPrev?: Function;
   onNext?: Function;
   onPathChange?: Function;
@@ -30,6 +33,8 @@ interface IWidget {
 const NavigateButtonWidget = ({
   prevTitle,
   nextTitle,
+  topOfChapter = false,
+  endOfChapter = false,
   path,
   onPrev,
   onNext,
@@ -49,10 +54,22 @@ const NavigateButtonWidget = ({
     >
       <Button
         size="middle"
+        icon={topOfChapter ? <FolderOutlined /> : undefined}
         disabled={typeof prevTitle === "undefined"}
         onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
           if (typeof onPrev !== "undefined") {
-            onPrev(event);
+            if (topOfChapter) {
+              Modal.confirm({
+                content: "已经到达章节开头,去上一个章节吗?",
+                okText: "确认",
+                cancelText: "取消",
+                onOk: () => {
+                  onPrev(event);
+                },
+              });
+            } else {
+              onPrev(event);
+            }
           }
         }}
       >
@@ -81,11 +98,23 @@ const NavigateButtonWidget = ({
         </Dropdown>
       </div>
       <Button
+        icon={endOfChapter ? <FolderOutlined /> : undefined}
         size="middle"
         disabled={typeof nextTitle === "undefined"}
         onClick={(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
           if (typeof onNext !== "undefined") {
-            onNext(event);
+            if (endOfChapter) {
+              Modal.confirm({
+                content: "已经到达章节末尾,去下一个章节吗?",
+                okText: "确认",
+                cancelText: "取消",
+                onOk: () => {
+                  onNext(event);
+                },
+              });
+            } else {
+              onNext(event);
+            }
           }
         }}
       >

+ 9 - 1
dashboard/src/components/article/ToolButtonToc.tsx

@@ -6,6 +6,7 @@ import { ArticleType } from "./Article";
 import PaliTextToc from "./PaliTextToc";
 import ToolButton from "./ToolButton";
 import TextBookToc from "../anthology/TextBookToc";
+import { useIntl } from "react-intl";
 
 interface IWidget {
   type?: ArticleType;
@@ -23,6 +24,7 @@ const ToolButtonTocWidget = ({
   channels,
   onSelect,
 }: IWidget) => {
+  const intl = useIntl();
   //TODO 都放return里面
   let tocWidget = <></>;
   if (type === "chapter" || type === "para") {
@@ -72,7 +74,13 @@ const ToolButtonTocWidget = ({
   }
 
   return (
-    <ToolButton title="目录" icon={<MenuOutlined />} content={tocWidget} />
+    <ToolButton
+      title={intl.formatMessage({
+        id: "labels.table-of-content",
+      })}
+      icon={<MenuOutlined />}
+      content={tocWidget}
+    />
   );
 };
 

+ 1 - 3
dashboard/src/components/article/TypeArticle.tsx

@@ -1,5 +1,5 @@
 import { useState } from "react";
-import { Alert, Button, Modal } from "antd";
+import { Modal } from "antd";
 import { ExclamationCircleOutlined } from "@ant-design/icons";
 import { IArticleDataResponse } from "../api/Article";
 import { ArticleMode, ArticleType } from "./Article";
@@ -17,7 +17,6 @@ interface IWidget {
   hideTitle?: boolean;
   onArticleChange?: Function;
   onArticleEdit?: Function;
-  onFinal?: Function;
   onLoad?: Function;
   onAnthologySelect?: Function;
 }
@@ -31,7 +30,6 @@ const TypeArticleWidget = ({
   hideInteractive = false,
   hideTitle = false,
   onArticleChange,
-  onFinal,
   onLoad,
   onAnthologySelect,
   onArticleEdit,

+ 19 - 5
dashboard/src/components/article/TypeArticleReader.tsx

@@ -30,7 +30,6 @@ interface IWidget {
   hideInteractive?: boolean;
   hideTitle?: boolean;
   onArticleChange?: Function;
-  onFinal?: Function;
   onLoad?: Function;
   onAnthologySelect?: Function;
   onEdit?: Function;
@@ -45,7 +44,6 @@ const TypeArticleReaderWidget = ({
   hideInteractive = false,
   hideTitle = false,
   onArticleChange,
-  onFinal,
   onLoad,
   onAnthologySelect,
   onEdit,
@@ -143,8 +141,10 @@ const TypeArticleReaderWidget = ({
 
   useEffect(() => {
     const url = `/v2/nav-article/${articleId}_${anthologyId}`;
+    console.info("api request", url);
     get<IArticleNavResponse>(url)
       .then((json) => {
+        console.debug("api response", json);
         if (json.ok) {
           setNav(json.data);
         }
@@ -163,9 +163,21 @@ const TypeArticleReaderWidget = ({
     };
   }
 
-  const title = articleData?.title_text
-    ? articleData?.title_text
-    : articleData?.title;
+  const title = articleData?.title_text ?? articleData?.title;
+
+  let endOfChapter = false;
+  if (nav?.curr && nav?.next) {
+    if (nav?.curr?.level > nav?.next?.level) {
+      endOfChapter = true;
+    }
+  }
+
+  let topOfChapter = false;
+  if (nav?.curr && nav?.prev) {
+    if (nav?.curr?.level > nav?.prev?.level) {
+      topOfChapter = true;
+    }
+  }
 
   return (
     <div>
@@ -235,6 +247,8 @@ const TypeArticleReaderWidget = ({
           <NavigateButton
             prevTitle={nav?.prev?.title}
             nextTitle={nav?.next?.title}
+            topOfChapter={topOfChapter}
+            endOfChapter={endOfChapter}
             path={currPath}
             onNext={() => {
               if (typeof onArticleChange !== "undefined") {

+ 7 - 1
dashboard/src/components/auth/Avatar.tsx

@@ -82,7 +82,13 @@ const AvatarWidget = ({ style, placement = "bottomRight" }: IWidget) => {
       </ProCard>
     );
   };
-  const Login = () => <Link to="/anonymous/users/sign-in">登录/注册</Link>;
+  const Login = () => (
+    <Link to="/anonymous/users/sign-in">
+      {intl.formatMessage({
+        id: "nut.users.sign-in-up.title",
+      })}
+    </Link>
+  );
   return (
     <>
       <Popover content={user ? <UserCard /> : <Login />} placement={placement}>

+ 7 - 1
dashboard/src/components/auth/SignInAvatar.tsx

@@ -39,7 +39,13 @@ const SignInAvatarWidget = ({ style, placement = "bottomRight" }: IWidget) => {
   }, [user]);
 
   if (typeof user === "undefined") {
-    return <Link to="/anonymous/users/sign-in">登录/注册</Link>;
+    return (
+      <Link to="/anonymous/users/sign-in">
+        {intl.formatMessage({
+          id: "nut.users.sign-in-up.title",
+        })}
+      </Link>
+    );
   } else {
     return (
       <>

+ 2 - 8
dashboard/src/components/channel/ChannelTable.tsx

@@ -300,12 +300,6 @@ const ChannelTableWidget = ({
                 }),
                 status: "Default",
               },
-              general: {
-                text: intl.formatMessage({
-                  id: "channel.type.general.label",
-                }),
-                status: "Default",
-              },
             },
           },
           {
@@ -519,7 +513,7 @@ const ChannelTableWidget = ({
                 key: "my",
                 label: (
                   <span>
-                    此工作室的
+                    {intl.formatMessage({ id: "labels.this-studio" })}
                     {renderBadge(myNumber, activeKey === "my")}
                   </span>
                 ),
@@ -528,7 +522,7 @@ const ChannelTableWidget = ({
                 key: "collaboration",
                 label: (
                   <span>
-                    协作
+                    {intl.formatMessage({ id: "labels.collaboration" })}
                     {renderBadge(
                       collaborationNumber,
                       activeKey === "collaboration"

+ 0 - 4
dashboard/src/components/channel/ChannelTypeSelect.tsx

@@ -25,10 +25,6 @@ const ChannelTypeSelectWidget = ({ readonly }: IWidget) => {
       value: "original",
       label: intl.formatMessage({ id: "channel.type.original.label" }),
     },
-    {
-      value: "general",
-      label: intl.formatMessage({ id: "channel.type.general.label" }),
-    },
   ];
   return (
     <ProFormSelect

+ 1 - 1
dashboard/src/components/corpus/ChapterInChannel.tsx

@@ -131,7 +131,7 @@ const ChapterInChannelWidget = ({
               setOpen(true);
             }}
           >
-            更多
+            {intl.formatMessage({ id: "buttons.more" })}
           </Button>
         </div>
         <Modal

+ 1 - 1
dashboard/src/components/corpus/Recent.tsx

@@ -56,7 +56,7 @@ const RecentWidget = () => {
           );
         }}
       />
-      <Button type="link">更多</Button>
+      <Button type="link">{intl.formatMessage({ id: "buttons.more" })}</Button>
     </div>
   );
 };

+ 6 - 0
dashboard/src/components/dict/MyCreate.tsx

@@ -18,6 +18,7 @@ import { add, updateIndex, wordIndex } from "../../reducers/inline-dict";
 import store from "../../store";
 import { get as getUiLang } from "../../locales";
 import { myDictDirty } from "../../reducers/command";
+import { currentUser } from "../../reducers/current-user";
 
 export const UserWbwPost = (data: IDictRequest[], view: string) => {
   let wordData: IDictRequest[] = data;
@@ -52,6 +53,7 @@ export const UserWbwPost = (data: IDictRequest[], view: string) => {
           factormean: pFm,
           confidence: value.confidence,
           language: value.language,
+          status: value.status,
         });
       }
     }
@@ -71,6 +73,7 @@ export const UserWbwPost = (data: IDictRequest[], view: string) => {
             mean: meaning,
             confidence: value.confidence,
             language: value.language,
+            status: value.status,
           });
         }
 
@@ -84,6 +87,7 @@ export const UserWbwPost = (data: IDictRequest[], view: string) => {
               mean: subFactorsMeaning[index1],
               confidence: value.confidence,
               language: value.language,
+              status: value.status,
             });
           }
         });
@@ -115,6 +119,7 @@ const MyCreateWidget = ({ word, onSave }: IWidget) => {
   });
   const [loading, setLoading] = useState(false);
   const inlineWordIndex = useAppSelector(wordIndex);
+  const user = useAppSelector(currentUser);
 
   useEffect(() => setWordSpell(word), [word]);
 
@@ -270,6 +275,7 @@ const MyCreateWidget = ({ word, onSave }: IWidget) => {
                 factors: editWord.factors?.value,
                 factormean: editWord.factorMeaning?.value,
                 language: getUiLang(),
+                status: user?.roles?.includes("basic") ? 5 : 30,
                 confidence: 100,
               },
             ];

+ 9 - 3
dashboard/src/components/dict/UserDictList.tsx

@@ -46,6 +46,7 @@ export interface IWord {
   note?: string | null;
   factors?: string | null;
   dict?: IDictInfo;
+  status?: number;
   updated_at?: string;
   created_at?: string;
 }
@@ -204,19 +205,21 @@ const UserDictListWidget = ({
           },
           description: {
             dataIndex: "meaning",
-            title: "整体意思",
-            search: word ? false : undefined,
+            title: "description",
+            search: false,
             render(dom, entity, index, action, schema) {
               return (
                 <div>
                   <Space>
                     {entity.meaning}
-
+                    {"|"}
                     <TimeShow
                       updatedAt={entity.updated_at}
                       createdAt={entity.updated_at}
                       type="secondary"
                     />
+                    {"|"}
+                    {entity.status === 5 ? "私有" : "公开"}
                   </Space>
                   {compact ? (
                     <div>
@@ -232,6 +235,7 @@ const UserDictListWidget = ({
           content: compact
             ? undefined
             : {
+                search: false,
                 render(dom, entity, index, action, schema) {
                   return (
                     <div>
@@ -506,6 +510,7 @@ const UserDictListWidget = ({
               note: item.note,
               factors: item.factors,
               dict: item.dict,
+              status: item.status,
               updated_at: item.updated_at,
             };
           });
@@ -568,6 +573,7 @@ const UserDictListWidget = ({
       </Drawer>
       <Drawer
         title={drawerTitle}
+        width={500}
         placement="right"
         open={isEditOpen}
         onClose={() => {

+ 8 - 2
dashboard/src/components/home/ChapterNew.tsx

@@ -2,8 +2,10 @@ import { Col, Row } from "antd";
 import { Link } from "react-router-dom";
 import img_book from "../../assets/library/images/books.svg";
 import ChapterNewList from "./ChapterNewList";
+import { useIntl } from "react-intl";
 
 const ChapterNewWidget = () => {
+  const intl = useIntl();
   return (
     <Row
       style={{
@@ -21,9 +23,13 @@ const ChapterNewWidget = () => {
         }}
       >
         <div style={{ flex: 4, margin: "2em" }}>
-          <span style={{ fontSize: "250%", fontWeight: 700 }}>圣典</span>
+          <span style={{ fontSize: "250%", fontWeight: 700 }}>
+            {intl.formatMessage({ id: "columns.library.community.title" })}
+          </span>
           <span style={{ position: "absolute", right: 30, bottom: 0 }}>
-            <Link to="community/list">更多</Link>
+            <Link to="community/list">
+              {intl.formatMessage({ id: "buttons.more" })}
+            </Link>
           </span>
         </div>
         <div style={{ flex: 9 }}>

+ 5 - 1
dashboard/src/components/home/CourseNew.tsx

@@ -2,8 +2,10 @@ import { Col, Row } from "antd";
 import { Link } from "react-router-dom";
 import img_book from "../../assets/library/images/teachers.svg";
 import CourseNewList from "./CourseNewList";
+import { useIntl } from "react-intl";
 
 const CourseNewWidget = () => {
+  const intl = useIntl(); //i18n
   return (
     <Row
       style={{
@@ -29,7 +31,9 @@ const CourseNewWidget = () => {
             课程
           </span>
           <span style={{ position: "absolute", right: 30, bottom: 0 }}>
-            <Link to="course/list">更多</Link>
+            <Link to="course/list">
+              {intl.formatMessage({ id: "buttons.more" })}
+            </Link>
           </span>
         </div>
       </Col>

+ 8 - 3
dashboard/src/components/library/FooterBar.tsx

@@ -2,13 +2,14 @@ import { Link } from "react-router-dom";
 import { Layout, Row, Col, Typography } from "antd";
 import BeiAn from "../general/BeiAn";
 import Feedback from "../general/Feedback";
+import { useIntl } from "react-intl";
 
 const { Footer } = Layout;
 const { Paragraph } = Typography;
 
 const FooterBarWidget = () => {
-  //Library foot bar
-  // TODO 补充项目信息
+  const intl = useIntl();
+
   return (
     <Footer>
       <Row>
@@ -24,7 +25,11 @@ const FooterBarWidget = () => {
           </ul>
         </Col>
         <Col span={16}>
-          <Paragraph strong>问题反馈</Paragraph>
+          <Paragraph strong>
+            {intl.formatMessage({
+              id: "labels.feedback",
+            })}
+          </Paragraph>
           <Feedback />
         </Col>
       </Row>

+ 1 - 1
dashboard/src/components/library/HeadBar.tsx

@@ -22,7 +22,7 @@ export const mainMenuItems: MenuProps["items"] = [
   {
     label: (
       <a href="/" rel="noreferrer">
-        首页
+        <FormattedMessage id="columns.library.home.title" />
       </a>
     ),
     key: "home",

+ 1 - 1
dashboard/src/components/nut/users/NonSignInSharedLinks.tsx

@@ -9,7 +9,7 @@ const Widget = () => {
         <FormattedMessage id="nut.users.sign-in.title" />
       </Link>
       <Divider type="vertical" />
-      <Link to="/users/sign-up">
+      <Link hidden to="/users/sign-up">
         <FormattedMessage id="nut.users.sign-up.title" />
       </Link>
       <Divider type="vertical" />

+ 3 - 1
dashboard/src/components/nut/users/SignUp.tsx

@@ -46,7 +46,9 @@ const SignUpWidget = ({ token }: IWidget) => {
           type="primary"
           onClick={() => navigate("/anonymous/users/sign-in")}
         >
-          登录
+          {intl.formatMessage({
+            id: "nut.users.sign-up.title",
+          })}
         </Button>
       }
     />

+ 3 - 1
dashboard/src/components/share/ShareModal.tsx

@@ -1,6 +1,7 @@
 import { useState } from "react";
 import { Modal } from "antd";
 import Share, { EResType } from "./Share";
+import { useIntl } from "react-intl";
 
 interface IWidget {
   resId: string;
@@ -9,6 +10,7 @@ interface IWidget {
 }
 const ShareModalWidget = ({ resId, resType, trigger }: IWidget) => {
   const [isModalOpen, setIsModalOpen] = useState(false);
+  const intl = useIntl();
   const showModal = () => {
     setIsModalOpen(true);
   };
@@ -27,7 +29,7 @@ const ShareModalWidget = ({ resId, resType, trigger }: IWidget) => {
       <Modal
         destroyOnClose={true}
         width={700}
-        title="协作"
+        title={intl.formatMessage({ id: "labels.collaboration" })}
         open={isModalOpen}
         onOk={handleOk}
         onCancel={handleCancel}

+ 1 - 3
dashboard/src/components/studio/HeadBar.tsx

@@ -34,9 +34,7 @@ const HeadBarWidget = () => {
         }}
       >
         <div style={{ display: "flex" }}>
-          <Link to="/">
-            <img alt="code" style={{ height: 36 }} src={img_banner} />
-          </Link>
+          <img alt="code" style={{ height: 36 }} src={img_banner} />
           <SoftwareEdition style={{ color: "white" }} />
         </div>
         <div style={{ width: 500, lineHeight: 44 }}>

+ 10 - 4
dashboard/src/components/studio/LeftSider.tsx

@@ -39,7 +39,9 @@ const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
 
   const items: MenuProps["items"] = [
     {
-      label: "常用",
+      label: intl.formatMessage({
+        id: "columns.studio.basic.title",
+      }),
       key: "basic",
       icon: <HomeOutlined />,
       children: [
@@ -87,7 +89,9 @@ const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
       ],
     },
     {
-      label: "高级",
+      label: intl.formatMessage({
+        id: "columns.studio.advance.title",
+      }),
       key: "advance",
       icon: <AppstoreOutlined />,
       children: [
@@ -162,10 +166,10 @@ const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
           ),
           key: "setting",
         },
-      ],
+      ].filter((value) => value.disabled !== true),
     },
     {
-      label: "协作",
+      label: intl.formatMessage({ id: "labels.collaboration" }),
       key: "collaboration",
       icon: <TeamOutlined />,
       children: [
@@ -178,6 +182,7 @@ const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
             </Link>
           ),
           key: "group",
+          disabled: user?.roles?.includes("basic"),
         },
         {
           label: (
@@ -188,6 +193,7 @@ const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
             </Link>
           ),
           key: "invite",
+          disabled: user?.roles?.includes("basic"),
         },
         {
           label: (

+ 3 - 1
dashboard/src/components/studio/PublicitySelect.tsx

@@ -48,9 +48,11 @@ const PublicitySelectWidget = ({ width, disable = [], readonly }: IWidget) => {
       disable: disable.includes("public"),
     },
   ];
+
+  console.debug("disable", disable, options);
   return (
     <ProFormSelect
-      options={options}
+      options={options.filter((value) => value.disable === false)}
       readonly={readonly}
       width={width}
       name="status"

+ 14 - 2
dashboard/src/components/template/Wbw/WbwDetail.tsx

@@ -16,6 +16,8 @@ import {
 import { IAttachmentRequest } from "../../api/Attachments";
 import WbwDetailAttachment from "./WbwDetailAttachment";
 import CommentBox from "../../discussion/DiscussionDrawer";
+import { useAppSelector } from "../../../hooks";
+import { currentUser } from "../../../reducers/current-user";
 
 interface IWidget {
   data: IWbw;
@@ -38,6 +40,7 @@ const WbwDetailWidget = ({
     JSON.parse(JSON.stringify(data))
   );
   const [tabKey, setTabKey] = useState<string>("basic");
+  const currUser = useAppSelector(currentUser);
 
   useEffect(() => {
     console.debug("input data", data);
@@ -266,14 +269,23 @@ const WbwDetailWidget = ({
           menu={{
             items: [
               {
-                key: "user-dict",
+                key: "user-dict-public",
                 label: intl.formatMessage({ id: "buttons.save.publish" }),
+                disabled: currUser?.roles?.includes("basic"),
+              },
+              {
+                key: "user-dict-private",
+                label: intl.formatMessage({ id: "buttons.save.my.dict" }),
               },
             ],
             onClick: (e) => {
               if (typeof onSave !== "undefined") {
                 //保存并发布
-                onSave(currWbwData, true);
+                if (e.key === "user-dict-public") {
+                  onSave(currWbwData, true, true);
+                } else {
+                  onSave(currWbwData, true, false);
+                }
               }
             },
           }}

+ 1 - 1
dashboard/src/components/template/Wbw/WbwDetailFm.tsx

@@ -148,7 +148,7 @@ const WbwFactorMeaningItem = ({ pali, meaning = "", onChange }: IWFMI) => {
           ...items.filter((value, index) => index <= 5),
           {
             key: "more",
-            label: "更多",
+            label: intl.formatMessage({ id: "buttons.more" }),
             disabled: items.length <= 5,
             children: items.filter((value, index) => index > 5),
           },

+ 1 - 1
dashboard/src/components/template/Wbw/WbwFactorMeaning.tsx

@@ -103,7 +103,7 @@ const WbwFactorMeaningWidget = ({
                 ...items.filter((value, index) => index <= 5),
                 {
                   key: "more",
-                  label: "更多",
+                  label: intl.formatMessage({ id: "buttons.more" }),
                   children: items.filter((value, index) => index > 5),
                 },
               ],

+ 2 - 2
dashboard/src/components/template/Wbw/WbwPali.tsx

@@ -174,9 +174,9 @@ const WbwPaliWidget = ({ data, channelId, mode, display, onSave }: IWidget) => {
         setPaliColor("unset");
         setPopOpen(false);
       }}
-      onSave={(e: IWbw, isPublish: boolean) => {
+      onSave={(e: IWbw, isPublish: boolean, isPublic: boolean) => {
         if (typeof onSave !== "undefined") {
-          onSave(e, isPublish);
+          onSave(e, isPublish, isPublic);
           setPopOpen(false);
           setPaliColor("unset");
         }

+ 2 - 2
dashboard/src/components/template/Wbw/WbwWord.tsx

@@ -249,11 +249,11 @@ const WbwWordWidget = ({
           channelId={channelId}
           mode={mode}
           display={display}
-          onSave={(e: IWbw, isPublish: boolean) => {
+          onSave={(e: IWbw, isPublish: boolean, isPublic: boolean) => {
             const newData: IWbw = JSON.parse(JSON.stringify(e));
             setWordData(newData);
             if (typeof onChange !== "undefined") {
-              onChange(e, isPublish);
+              onChange(e, isPublish, isPublic);
             }
           }}
         />

+ 11 - 5
dashboard/src/components/template/WbwSent.tsx

@@ -23,6 +23,7 @@ import { GetUserSetting } from "../auth/setting/default";
 import { getGrammar } from "../../reducers/term-vocabulary";
 import modal from "antd/lib/modal";
 import { UserWbwPost } from "../dict/MyCreate";
+import { currentUser } from "../../reducers/current-user";
 
 export const paraMark = (wbwData: IWbw[]): IWbw[] => {
   //处理段落标记,支持点击段落引用弹窗
@@ -176,6 +177,7 @@ export const WbwSentCtl = ({
   const [loading, setLoading] = useState(false);
   const [progress, setProgress] = useState(0);
   const [showProgress, setShowProgress] = useState(false);
+  const user = useAppSelector(currentUser);
 
   console.debug("wbw sent lang", channelLang);
 
@@ -479,7 +481,7 @@ export const WbwSentCtl = ({
     }
   };
 
-  const wbwPublish = (wbwData: IWbw[]) => {
+  const wbwPublish = (wbwData: IWbw[], isPublic: boolean) => {
     let wordData: IDictRequest[] = [];
 
     wbwData.forEach((data) => {
@@ -507,6 +509,7 @@ export const WbwSentCtl = ({
           note: data.note?.value,
           confidence: conf,
           language: channelLang,
+          status: isPublic ? 30 : 5,
         });
       }
     });
@@ -534,7 +537,7 @@ export const WbwSentCtl = ({
         mode={displayMode}
         display={wbwMode}
         fields={fieldDisplay}
-        onChange={(e: IWbw, isPublish?: boolean) => {
+        onChange={(e: IWbw, isPublish: boolean, isPublic: boolean) => {
           let newData = [...wordData];
           newData.forEach((value, index, array) => {
             //把新的数据更新到数组
@@ -582,8 +585,8 @@ export const WbwSentCtl = ({
           }
           update(newData);
           saveWord(newData, e.sn[0]);
-          if (isPublish) {
-            wbwPublish([e]);
+          if (isPublish === true) {
+            wbwPublish([e], isPublic);
           }
         }}
         onSplit={() => {
@@ -708,7 +711,10 @@ export const WbwSentCtl = ({
                   magicDictLookup();
                   break;
                 case "wbw-dict-publish-all":
-                  wbwPublish(wordData);
+                  wbwPublish(
+                    wordData,
+                    user?.roles?.includes("basic") ? false : true
+                  );
                   break;
                 case "copy-text":
                   const paliText = wordData

+ 9 - 1
dashboard/src/components/term/TermList.tsx

@@ -20,6 +20,8 @@ import TermExport from "./TermExport";
 import DataImport from "../admin/relation/DataImport";
 import TermModal from "./TermModal";
 import { getSorterUrl } from "../../utils";
+import { useAppSelector } from "../../hooks";
+import { currentUser } from "../../reducers/current-user";
 
 interface IItem {
   sn: number;
@@ -39,6 +41,7 @@ interface IWidget {
 }
 const TermListWidget = ({ studioName, channelId }: IWidget) => {
   const intl = useIntl();
+  const currUser = useAppSelector(currentUser);
 
   const showDeleteConfirm = (id: string[], title: string) => {
     Modal.confirm({
@@ -319,7 +322,12 @@ const TermListWidget = ({ studioName, channelId }: IWidget) => {
           <TermExport channelId={channelId} studioName={studioName} />,
           <TermModal
             trigger={
-              <Button key="button" icon={<PlusOutlined />} type="primary">
+              <Button
+                key="button"
+                icon={<PlusOutlined />}
+                type="primary"
+                disabled={currUser?.roles?.includes("basic")}
+              >
                 {intl.formatMessage({ id: "buttons.create" })}
               </Button>
             }

+ 9 - 7
dashboard/src/components/transfer/TransferList.tsx

@@ -59,8 +59,8 @@ const TransferListWidget = ({ studioName }: IWidget) => {
     const data: ITransferRequest = {
       status: status,
     };
-    put<ITransferRequest, ITransferResponse>(`/v2/transfer/${id}`, data).then(
-      (json) => {
+    put<ITransferRequest, ITransferResponse>(`/v2/transfer/${id}`, data)
+      .then((json) => {
         if (json.ok) {
           ref.current?.reload();
           openNotification(
@@ -69,8 +69,10 @@ const TransferListWidget = ({ studioName }: IWidget) => {
         } else {
           message.error(json.message);
         }
-      }
-    );
+      })
+      .catch((e) => {
+        console.error(e);
+      });
   };
   return (
     <>
@@ -106,9 +108,9 @@ const TransferListWidget = ({ studioName }: IWidget) => {
             render(dom, entity, index, action, schema) {
               return (
                 <Space>
-                  <UserName {...entity.transferor} />
-                  {"transfer at"}
-                  <TimeShow createdAt={entity.created_at} />
+                  <UserName key={"user"} {...entity.transferor} />
+                  <span key="text">{"transfer at"}</span>
+                  <TimeShow key={"time"} createdAt={entity.created_at} />
                 </Space>
               );
             },

+ 5 - 5
dashboard/src/components/users/SignUp.tsx

@@ -58,9 +58,9 @@ const SingUpWidget = () => {
         name: string;
       }>
         name="welcome"
-        title="注册"
+        title={intl.formatMessage({ id: "labels.sign-in" })}
         stepProps={{
-          description: "注册wikipali教育版",
+          description: "注册wikipali基础版",
         }}
         onFinish={async () => {
           return true;
@@ -79,7 +79,7 @@ const SingUpWidget = () => {
           size="small"
         >
           <CheckCard
-            title="未注册"
+            title={intl.formatMessage({ id: "labels.software.edition.guest" })}
             description={
               <div>
                 <div>✅经文阅读</div>
@@ -128,10 +128,10 @@ const SingUpWidget = () => {
         <ProFormCheckbox.Group
           name="checkbox"
           layout="horizontal"
-          options={["我已经了解教育版的功能限制"]}
+          options={["我已经了解基础版的功能限制"]}
           fieldProps={{
             onChange(checkedValue) {
-              if (checkedValue.includes("我已经了解教育版的功能限制")) {
+              if (checkedValue.length > 0) {
                 setAgree(true);
               } else {
                 setAgree(false);

+ 1 - 0
dashboard/src/locales/en-US/buttons.ts

@@ -14,6 +14,7 @@ const items = {
   "buttons.option": "operation",
   "buttons.save": "save",
   "buttons.save.publish": "save & publish",
+  "buttons.save.my.dict": "save & my dict",
   "buttons.cancel": "cancel",
   "buttons.setting": "setting",
   "buttons.sign-in": "sign in",

+ 0 - 1
dashboard/src/locales/en-US/channel/index.ts

@@ -8,7 +8,6 @@ const items = {
   "channel.type.nissaya.label": "Nissaya",
   "channel.type.commentary.label": "commentary",
   "channel.type.original.label": "original",
-  "channel.type.general.label": "general",
   "channel.type.message.required": "请输入版本类型",
   "channel.lang": "language",
   "channel.fields.lang.label": "语言",

+ 1 - 0
dashboard/src/locales/en-US/index.ts

@@ -15,6 +15,7 @@ import message from "./message";
 import label from "./label";
 const items = {
   "columns.library.title": "Library",
+  "columns.library.home.title": "Home",
   "columns.library.community.title": "Community",
   "columns.library.palicanon.title": "Palicanon",
   "columns.library.course.title": "Course",

+ 5 - 0
dashboard/src/locales/en-US/label.ts

@@ -7,6 +7,7 @@ const items = {
   "labels.week.5": "Fri",
   "labels.week.6": "Sat",
   "labels.collaborators": "collaborators",
+  "labels.collaboration": "collaboration",
   "labels.link": "link",
   "labels.upload": "upload",
   "labels.first-term": "first term",
@@ -36,8 +37,12 @@ const items = {
   "labels.loading": "loading",
   "labels.empty": "empty",
   "labels.curr.paragraph.cart.tpl": "Add to Cart",
+  "labels.software.edition.guest": "guest",
   "labels.software.edition.basic": "basic",
   "labels.software.edition.pro": "pro",
+  "labels.table-of-content": "table of content",
+  "labels.this-studio": "this studio",
+  "labels.feedback": "feedback",
 };
 
 export default items;

+ 5 - 4
dashboard/src/locales/en-US/nut/index.ts

@@ -1,8 +1,9 @@
 const items = {
-  "nut.users.sign-in.title": "登录",
-  "nut.users.sign-up.title": "新用户注册",
-  "nut.users.logs.title": "日志列表",
-  "nut.users.forgot-password.title": "忘记密码",
+  "nut.users.sign-in.title": "sign in",
+  "nut.users.sign-up.title": "sign up",
+  "nut.users.sign-in-up.title": "sign in/sign up",
+  "nut.users.logs.title": "logs",
+  "nut.users.forgot-password.title": "forgot password",
 };
 
 export default items;

+ 2 - 1
dashboard/src/locales/zh-Hans/buttons.ts

@@ -13,7 +13,8 @@ const items = {
   "buttons.unselect": "全不选",
   "buttons.option": "操作",
   "buttons.save": "保存",
-  "buttons.save.publish": "保存并公开",
+  "buttons.save.publish": "保存并公开到社区词典",
+  "buttons.save.my.dict": "保存并添加到个人单词本",
   "buttons.cancel": "取消",
   "buttons.setting": "设置",
   "buttons.sign-in": "登录",

+ 14 - 15
dashboard/src/locales/zh-Hans/channel/index.ts

@@ -1,19 +1,18 @@
 const items = {
-	"channel.title": "版本风格",
-	"channel.type": "类型",
-	"channel.name": "名称",
-	"channel.create.message.noname": "请输入版本名称",
-	"channel.type.all.title": "全部",
-	"channel.type.translation.label": "译文",
-	"channel.type.nissaya.label": "Nissaya",
-	"channel.type.commentary.label": "注疏",
-	"channel.type.original.label": "原文",
-	"channel.type.general.label": "通用",
-	"channel.type.message.required": "请输入版本类型",
-	"channel.lang": "语言",
-	"channel.fields.lang.label": "语言",
-	"channel.fields.type.label": "类型",
-	"channel.fields.name.label": "名称",
+  "channel.title": "版本风格",
+  "channel.type": "类型",
+  "channel.name": "名称",
+  "channel.create.message.noname": "请输入版本名称",
+  "channel.type.all.title": "全部",
+  "channel.type.translation.label": "译文",
+  "channel.type.nissaya.label": "Nissaya",
+  "channel.type.commentary.label": "注疏",
+  "channel.type.original.label": "原文",
+  "channel.type.message.required": "请输入版本类型",
+  "channel.lang": "语言",
+  "channel.fields.lang.label": "语言",
+  "channel.fields.type.label": "类型",
+  "channel.fields.name.label": "名称",
 };
 
 export default items;

+ 1 - 0
dashboard/src/locales/zh-Hans/index.ts

@@ -17,6 +17,7 @@ import error from "./error";
 
 const items = {
   "columns.library.title": "藏经阁",
+  "columns.library.home.title": "首页",
   "columns.library.community.title": "社区",
   "columns.library.palicanon.title": "圣典",
   "columns.library.course.title": "课程",

+ 6 - 1
dashboard/src/locales/zh-Hans/label.ts

@@ -7,6 +7,7 @@ const items = {
   "labels.week.5": "星期五",
   "labels.week.6": "星期六",
   "labels.collaborators": "协作者",
+  "labels.collaboration": "协作",
   "labels.link": "链接",
   "labels.upload": "上传",
   "labels.first-term": "第一个术语",
@@ -41,8 +42,12 @@ const items = {
   "labels.dict.pass.2": "查词干",
   "labels.dict.pass.3": "查衍生",
   "labels.dict.pass.4": "查二次衍生",
-  "labels.software.edition.basic": "基本版",
+  "labels.software.edition.guest": "未注册",
+  "labels.software.edition.basic": "基础版",
   "labels.software.edition.pro": "增强版",
+  "labels.table-of-content": "目录",
+  "labels.this-studio": "此工作室",
+  "labels.feedback": "问题反馈",
 };
 
 export default items;

+ 2 - 1
dashboard/src/locales/zh-Hans/nut/index.ts

@@ -1,6 +1,7 @@
 const items = {
   "nut.users.sign-in.title": "登录",
-  "nut.users.sign-up.title": "新用户注册",
+  "nut.users.sign-up.title": "注册",
+  "nut.users.sign-in-up.title": "登录/注册",
   "nut.users.logs.title": "日志列表",
   "nut.users.forgot-password.title": "忘记密码",
 };

+ 12 - 15
dashboard/src/pages/admin/users/list.tsx

@@ -16,7 +16,7 @@ import User from "../../../components/auth/User";
 const { Text } = Typography;
 
 interface IParams {
-  content_type?: string;
+  role?: string;
 }
 
 const UsersWidget = () => {
@@ -39,6 +39,8 @@ const UsersWidget = () => {
             },
           },
           description: {
+            editable: false,
+            search: false,
             render: (dom, entity, index, action, schema) => {
               return (
                 <Text type="secondary">
@@ -53,10 +55,9 @@ const UsersWidget = () => {
                 </Text>
               );
             },
-            editable: false,
-            search: false,
           },
           subTitle: {
+            search: false,
             render: (dom, entity, index, action, schema) => {
               return entity.role ? (
                 <Space>
@@ -88,11 +89,7 @@ const UsersWidget = () => {
               return [
                 <Dropdown
                   menu={{
-                    items: [
-                      { label: "替换", key: "replace" },
-                      { label: "引用模版", key: "tpl" },
-                      { label: "删除", key: "delete", danger: true },
-                    ],
+                    items: [],
                     onClick: (e) => {
                       console.log("click ", e.key);
                     },
@@ -109,13 +106,12 @@ const UsersWidget = () => {
               ];
             },
           },
-          content_type: {
+          role: {
             // 自己扩展的字段,主要用于筛选,不在列表中显示
             title: "类型",
             valueType: "select",
             valueEnum: {
-              all: { text: "全部", status: "Default" },
-              admin: {
+              administrator: {
                 text: "管理员",
                 status: "Error",
               },
@@ -127,8 +123,8 @@ const UsersWidget = () => {
                 text: "会员",
                 status: "Processing",
               },
-              user: {
-                text: "用户",
+              basic: {
+                text: "基础版",
                 status: "Processing",
               },
             },
@@ -140,14 +136,15 @@ const UsersWidget = () => {
             ((params.current ? params.current : 1) - 1) *
             (params.pageSize ? params.pageSize : 20);
 
-          let url = "/v2/user?view=all";
+          let url = "/v2/user?view=all&order=created_at&dir=desc";
           url += `&limit=${params.pageSize}&offset=${offset}`;
 
           url += params.keyword ? "&search=" + params.keyword : "";
+          url += params.role ? "&role=" + params.role : "";
 
           url += getSorterUrl(sorter);
 
-          console.log(url);
+          console.info("api request", url);
           const res = await get<IUserListResponse2>(url);
           return {
             total: res.data.count,

+ 8 - 1
dashboard/src/pages/library/article/show.tsx

@@ -53,6 +53,7 @@ import ChannelAlert from "../../../components/channel/ChannelAlert";
 import PrPull from "../../../components/corpus/PrPull";
 import NotificationIcon from "../../../components/notification/NotificationIcon";
 import SentCart from "../../../components/template/SentEdit/SentCart";
+import { useIntl } from "react-intl";
 
 export const scrollToTop = () => {
   document.getElementById("article-root")?.scrollIntoView();
@@ -84,6 +85,7 @@ const Widget = () => {
   const [recentModalOpen, setRecentModalOpen] = useState(false);
   const [loadedArticleData, setLoadedArticleData] =
     useState<IArticleDataResponse>();
+  const intl = useIntl();
 
   const paraChange = useAppSelector(paraParam);
 
@@ -220,7 +222,12 @@ const Widget = () => {
                   setSearchParams(output);
                 }}
               />
-              <Tooltip title="文章目录" placement="bottomLeft">
+              <Tooltip
+                title={intl.formatMessage({
+                  id: "labels.table-of-content",
+                })}
+                placement="bottomLeft"
+              >
                 <Button
                   style={{ display: "block", color: "white" }}
                   icon={<UnorderedListOutlined />}

+ 7 - 1
dashboard/src/pages/nut/users/sign-in.tsx

@@ -1,11 +1,17 @@
 import SignInForm from "../../../components/nut/users/SignIn";
 import SharedLinks from "../../../components/nut/users/NonSignInSharedLinks";
 import { Card, Space } from "antd";
+import { useIntl } from "react-intl";
 
 const Widget = () => {
+  const intl = useIntl();
   return (
     <div>
-      <Card title="登录">
+      <Card
+        title={intl.formatMessage({
+          id: "nut.users.sign-up.title",
+        })}
+      >
         <Space direction="vertical">
           <SignInForm />
           <SharedLinks />

+ 4 - 1
dashboard/src/pages/studio/anthology/edit.tsx

@@ -61,11 +61,14 @@ const Widget = () => {
             },
             {
               key: "toc",
-              label: `目录`,
+              label: intl.formatMessage({
+                id: "labels.table-of-content",
+              }),
               children: (
                 <EditableTocTree
                   studioName={anthologyInfo?.studio.realName}
                   anthologyId={anthology_id}
+                  anthology={anthologyInfo}
                 />
               ),
             },

+ 29 - 30
dashboard/src/pages/studio/course/list.tsx

@@ -140,6 +140,8 @@ const Widget = () => {
     });
   };
 
+  const canCreate = !(activeKey !== "create" || user?.roles?.includes("basic"));
+
   return (
     <>
       <ProTable<DataItem>
@@ -436,38 +438,35 @@ const Widget = () => {
           search: true,
         }}
         toolBarRender={() => [
-          <Popover
-            content={
-              <CourseCreate
-                studio={studioname}
-                onCreate={() => {
-                  //新建课程成功后刷新
-                  setActiveKey("create");
-                  setCreateNumber(createNumber + 1);
-                  ref.current?.reload();
-                  setOpenCreate(false);
-                }}
-              />
-            }
-            title="Create"
-            placement="bottomRight"
-            trigger="click"
-            open={openCreate}
-            onOpenChange={(newOpen: boolean) => {
-              setOpenCreate(newOpen);
-            }}
-          >
-            <Button
-              disabled={
-                activeKey !== "create" || user?.roles?.includes("basic")
+          canCreate ? (
+            <Popover
+              content={
+                <CourseCreate
+                  studio={studioname}
+                  onCreate={() => {
+                    //新建课程成功后刷新
+                    setActiveKey("create");
+                    setCreateNumber(createNumber + 1);
+                    ref.current?.reload();
+                    setOpenCreate(false);
+                  }}
+                />
               }
-              key="button"
-              icon={<PlusOutlined />}
-              type="primary"
+              title="Create"
+              placement="bottomRight"
+              trigger="click"
+              open={openCreate}
+              onOpenChange={(newOpen: boolean) => {
+                setOpenCreate(newOpen);
+              }}
             >
-              {intl.formatMessage({ id: "buttons.create" })}
-            </Button>
-          </Popover>,
+              <Button key="button" icon={<PlusOutlined />} type="primary">
+                {intl.formatMessage({ id: "buttons.create" })}
+              </Button>
+            </Popover>
+          ) : (
+            <></>
+          ),
         ]}
         toolbar={{
           menu: {

+ 2 - 2
dashboard/src/pages/studio/group/list.tsx

@@ -293,7 +293,7 @@ const Widget = () => {
                 key: "my",
                 label: (
                   <span>
-                    此工作室的
+                    {intl.formatMessage({ id: "labels.this-studio" })}
                     {renderBadge(myNumber, activeKey === "my")}
                   </span>
                 ),
@@ -302,7 +302,7 @@ const Widget = () => {
                 key: "collaboration",
                 label: (
                   <span>
-                    我加入的
+                    {intl.formatMessage({ id: "labels.collaboration" })}
                     {renderBadge(
                       collaborationNumber,
                       activeKey === "collaboration"

+ 4 - 1
dashboard/src/pages/users/sign-up.tsx

@@ -1,8 +1,11 @@
 import { Card } from "antd";
 
 import SignUp from "../../components/users/SignUp";
+import { useIntl } from "react-intl";
 
 const Widget = () => {
+  const intl = useIntl();
+
   return (
     <div
       style={{
@@ -12,7 +15,7 @@ const Widget = () => {
         marginRight: "auto",
       }}
     >
-      <Card title="注册">
+      <Card title={intl.formatMessage({ id: "labels.sign-in" })}>
         <SignUp />
       </Card>
     </div>