Bläddra i källkod

Merge pull request #1041 from visuddhinanda/agile

antd 引用 和自己的组件之间加空行
visuddhinanda 3 år sedan
förälder
incheckning
354598275d
100 ändrade filer med 1618 tillägg och 1486 borttagningar
  1. 1 0
      dashboard/src/components/api/Corpus.ts
  2. 42 42
      dashboard/src/components/article/AnthologStudioList.tsx
  3. 41 42
      dashboard/src/components/article/AnthologyCard.tsx
  4. 65 65
      dashboard/src/components/article/AnthologyDetail.tsx
  5. 52 51
      dashboard/src/components/article/AnthologyList.tsx
  6. 1 0
      dashboard/src/components/article/Article.tsx
  7. 5 4
      dashboard/src/components/article/ArticleCard.tsx
  8. 1 0
      dashboard/src/components/article/ArticleCardMainMenu.tsx
  9. 1 1
      dashboard/src/components/article/ArticleCreate.tsx
  10. 1 4
      dashboard/src/components/article/ArticleTabs.tsx
  11. 1 1
      dashboard/src/components/article/Find.tsx
  12. 1 1
      dashboard/src/components/article/PaliTextToc.tsx
  13. 1 0
      dashboard/src/components/article/TermShell.tsx
  14. 1 0
      dashboard/src/components/article/TocTree.tsx
  15. 80 64
      dashboard/src/components/auth/SignInAvatar.tsx
  16. 49 0
      dashboard/src/components/auth/StudioCard.tsx
  17. 14 10
      dashboard/src/components/auth/StudioName.tsx
  18. 14 7
      dashboard/src/components/auth/ToLibaray.tsx
  19. 24 13
      dashboard/src/components/auth/ToStudio.tsx
  20. 11 10
      dashboard/src/components/auth/User.tsx
  21. 1 0
      dashboard/src/components/auth/setting/SettingArticle.tsx
  22. 2 1
      dashboard/src/components/auth/setting/SettingItem.tsx
  23. 1 0
      dashboard/src/components/auth/setting/default.ts
  24. 58 54
      dashboard/src/components/blog/TopArticleCard.tsx
  25. 41 21
      dashboard/src/components/blog/TopArticles.tsx
  26. 1 1
      dashboard/src/components/channel/ChannelCreate.tsx
  27. 2 1
      dashboard/src/components/channel/ChannelList.tsx
  28. 1 0
      dashboard/src/components/channel/ChannelListItem.tsx
  29. 1 0
      dashboard/src/components/channel/ChannelPicker.tsx
  30. 2 30
      dashboard/src/components/channel/ChannelPickerTable.tsx
  31. 42 42
      dashboard/src/components/channel/ChannelTypeSelect.tsx
  32. 69 69
      dashboard/src/components/corpus/BookTree.tsx
  33. 2 5
      dashboard/src/components/corpus/BookTreeList.tsx
  34. 27 20
      dashboard/src/components/corpus/ChapterCard.tsx
  35. 20 19
      dashboard/src/components/corpus/ChapterFilter.tsx
  36. 1 0
      dashboard/src/components/corpus/ChapterInChannel.tsx
  37. 30 27
      dashboard/src/components/corpus/ChapterList.tsx
  38. 50 49
      dashboard/src/components/corpus/ChapterTagList.tsx
  39. 51 50
      dashboard/src/components/corpus/PaliChapterCard.tsx
  40. 1 0
      dashboard/src/components/corpus/PaliChapterChannelList.tsx
  41. 3 2
      dashboard/src/components/corpus/PaliChapterHead.tsx
  42. 27 26
      dashboard/src/components/corpus/PaliChapterList.tsx
  43. 1 0
      dashboard/src/components/corpus/PaliChapterListByPara.tsx
  44. 1 0
      dashboard/src/components/corpus/PaliChapterListByTag.tsx
  45. 18 18
      dashboard/src/components/dict/Confidence.tsx
  46. 2 2
      dashboard/src/components/dict/DictComponent.tsx
  47. 28 28
      dashboard/src/components/dict/DictContent.tsx
  48. 29 32
      dashboard/src/components/dict/DictCreate.tsx
  49. 5 5
      dashboard/src/components/dict/DictEdit.tsx
  50. 1 2
      dashboard/src/components/dict/DictEditInner.tsx
  51. 20 18
      dashboard/src/components/dict/DictList.tsx
  52. 31 29
      dashboard/src/components/dict/DictSearch.tsx
  53. 38 39
      dashboard/src/components/dict/GrammarPop.tsx
  54. 1 1
      dashboard/src/components/dict/SelectCase.tsx
  55. 40 40
      dashboard/src/components/dict/WordCard.tsx
  56. 27 25
      dashboard/src/components/dict/WordCardByDict.tsx
  57. 1 1
      dashboard/src/components/general/LangSelect.tsx
  58. 30 28
      dashboard/src/components/group/GroupCreate.tsx
  59. 92 87
      dashboard/src/components/group/GroupFile.tsx
  60. 91 84
      dashboard/src/components/group/GroupMember.tsx
  61. 0 0
      dashboard/src/components/library/article/.txt
  62. 0 0
      dashboard/src/components/library/dict/.txt
  63. 1 3
      dashboard/src/components/nut/users/SignIn.tsx
  64. 184 184
      dashboard/src/components/studio/EditableTree.tsx
  65. 2 2
      dashboard/src/components/template/Quote.tsx
  66. 1 0
      dashboard/src/components/template/SentEdit/EditInfo.tsx
  67. 1 0
      dashboard/src/components/template/SentEdit/SentCell.tsx
  68. 4 2
      dashboard/src/components/template/SentEdit/SentCellEditable.tsx
  69. 1 0
      dashboard/src/components/template/SentEdit/SentContent.tsx
  70. 0 1
      dashboard/src/components/template/SentEdit/SentMenu.tsx
  71. 1 1
      dashboard/src/components/template/SentEdit/SentTabButton.tsx
  72. 0 2
      dashboard/src/components/template/SentEdit/SuggestionAdd.tsx
  73. 1 0
      dashboard/src/components/template/SentEdit/SuggestionList.tsx
  74. 1 0
      dashboard/src/components/template/SentEdit/SuggestionTabs.tsx
  75. 3 2
      dashboard/src/components/template/SentRead.tsx
  76. 3 2
      dashboard/src/components/template/Term.tsx
  77. 0 3
      dashboard/src/components/template/Wbw/WbwDetailAdvance.tsx
  78. 0 2
      dashboard/src/components/template/Wbw/WbwDetailNote.tsx
  79. 7 6
      dashboard/src/components/template/Wbw/WbwWord.tsx
  80. 1 0
      dashboard/src/components/template/WbwSent.tsx
  81. 1 0
      dashboard/src/components/template/Wd.tsx
  82. 2 0
      dashboard/src/components/template/utilities.ts
  83. 0 6
      dashboard/src/load.ts
  84. 5 0
      dashboard/src/locales/zh-Hans/buttons.ts
  85. 1 0
      dashboard/src/locales/zh-Hans/index.ts
  86. 35 34
      dashboard/src/pages/library/anthology/show.tsx
  87. 1 0
      dashboard/src/pages/library/blog/anthology.tsx
  88. 1 0
      dashboard/src/pages/library/blog/course.tsx
  89. 1 0
      dashboard/src/pages/library/blog/overview.tsx
  90. 1 0
      dashboard/src/pages/library/blog/term.tsx
  91. 1 0
      dashboard/src/pages/library/blog/translation.tsx
  92. 1 0
      dashboard/src/pages/library/course/course.tsx
  93. 1 0
      dashboard/src/pages/library/course/index.tsx
  94. 3 2
      dashboard/src/pages/library/dict/show.tsx
  95. 0 1
      dashboard/src/pages/library/palicanon/chapter.tsx
  96. 40 42
      dashboard/src/pages/studio/analysis/list.tsx
  97. 6 7
      dashboard/src/pages/studio/anthology/edit.tsx
  98. 1 1
      dashboard/src/pages/studio/article/edit.tsx
  99. 8 10
      dashboard/src/pages/studio/article/index.tsx
  100. 3 2
      dashboard/src/pages/studio/channel/edit.tsx

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

@@ -168,6 +168,7 @@ export interface IChapterData {
   path: string;
   tags: TagNode[];
   channel: { name: string; owner_uid: string };
+  channel_id: string;
   summary: string;
   view: number;
   like: number;

+ 42 - 42
dashboard/src/components/article/AnthologStudioList.tsx

@@ -1,16 +1,17 @@
+import { Link } from "react-router-dom";
 import { useState, useEffect } from "react";
 import { List, Space, Card } from "antd";
+
 import StudioName from "../auth/StudioName";
 import type { IAnthologyStudioListApiResponse } from "../api/Article";
 import type { IStudioApiResponse } from "../api/Auth";
 import { get } from "../../request";
-import { Link, useNavigate } from "react-router-dom";
 
 const defaultData: IAnthologyStudioData[] = [];
 
 interface IAnthologyStudioData {
-	count: number;
-	studio: IStudioApiResponse;
+  count: number;
+  studio: IStudioApiResponse;
 }
 /*
 interface IWidgetAnthologyList {
@@ -18,47 +19,46 @@ interface IWidgetAnthologyList {
 }
 */
 const Widget = () => {
-	const [tableData, setTableData] = useState(defaultData);
-	const navigate = useNavigate();
-	useEffect(() => {
-		console.log("useEffect");
-		fetchData();
-	}, []);
+  const [tableData, setTableData] = useState(defaultData);
+  useEffect(() => {
+    console.log("useEffect");
+    fetchData();
+  }, []);
 
-	function fetchData() {
-		let url = `/v2/anthology?view=studio_list`;
-		get(url).then(function (myJson) {
-			console.log("ajex", myJson);
-			const json = myJson as unknown as IAnthologyStudioListApiResponse;
-			let newTree: IAnthologyStudioData[] = json.data.rows.map((item) => {
-				return {
-					count: item.count,
-					studio: item.studio,
-				};
-			});
-			setTableData(newTree);
-		});
-	}
+  function fetchData() {
+    let url = `/v2/anthology?view=studio_list`;
+    get(url).then(function (myJson) {
+      console.log("ajex", myJson);
+      const json = myJson as unknown as IAnthologyStudioListApiResponse;
+      let newTree: IAnthologyStudioData[] = json.data.rows.map((item) => {
+        return {
+          count: item.count,
+          studio: item.studio,
+        };
+      });
+      setTableData(newTree);
+    });
+  }
 
-	return (
-		<Card title="作者">
-			<List
-				itemLayout="vertical"
-				size="large"
-				dataSource={tableData}
-				renderItem={(item) => (
-					<List.Item>
-						<Link to={`/blog/${item.studio.studioName}/anthology`}>
-							<Space>
-								<StudioName data={item.studio} />
-								<span>({item.count})</span>
-							</Space>
-						</Link>
-					</List.Item>
-				)}
-			/>
-		</Card>
-	);
+  return (
+    <Card title="作者">
+      <List
+        itemLayout="vertical"
+        size="large"
+        dataSource={tableData}
+        renderItem={(item) => (
+          <List.Item>
+            <Link to={`/blog/${item.studio.studioName}/anthology`}>
+              <Space>
+                <StudioName data={item.studio} />
+                <span>({item.count})</span>
+              </Space>
+            </Link>
+          </List.Item>
+        )}
+      />
+    </Card>
+  );
 };
 
 export default Widget;

+ 41 - 42
dashboard/src/components/article/AnthologyCard.tsx

@@ -2,6 +2,7 @@ import { Link } from "react-router-dom";
 import { Row, Col } from "antd";
 import { Card } from "antd";
 import { Typography } from "antd";
+
 import StudioName from "../auth/StudioName";
 import type { IStudio } from "../auth/StudioName";
 import type { ListNodeData } from "../studio/EditableTree";
@@ -9,57 +10,55 @@ import type { ListNodeData } from "../studio/EditableTree";
 const { Title, Text } = Typography;
 
 export interface IArticleData {
-	id: string;
-	title: string;
-	subTitle: string;
-	summary: string;
-	created_at: string;
-	updated_at: string;
+  id: string;
+  title: string;
+  subTitle: string;
+  summary: string;
+  created_at: string;
+  updated_at: string;
 }
 
 export interface IAnthologyData {
-	id: string;
-	title: string;
-	subTitle: string;
-	summary: string;
-	articles: ListNodeData[];
-	studio: IStudio;
-	created_at: string;
-	updated_at: string;
+  id: string;
+  title: string;
+  subTitle: string;
+  summary: string;
+  articles: ListNodeData[];
+  studio: IStudio;
+  created_at: string;
+  updated_at: string;
 }
 
 interface IWidgetAnthologyCard {
-	data: IAnthologyData;
+  data: IAnthologyData;
 }
 
 const Widget = (prop: IWidgetAnthologyCard) => {
-	const articleList = prop.data.articles.map((item, id) => {
-		return <div key={id}>{item.title}</div>;
-	});
-	return (
-		<>
-			<Card hoverable bordered={false} style={{ width: "100%" }}>
-				<Title level={4}>
-					<Link to={`/anthology/${prop.data.id}`}>
-						{prop.data.title}
-					</Link>
-				</Title>
-				<div>
-					<Text type="secondary">{prop.data.subTitle}</Text>
-				</div>
-				<div>
-					<Text>{prop.data.summary}</Text>
-				</div>
-				<Link to={`/blog/${prop.data.studio.studioName}/anthology`}>
-					<StudioName data={prop.data.studio} />
-				</Link>
-				<Row>
-					<Col flex={"100px"}>Content</Col>
-					<Col flex={"auto"}>{articleList}</Col>
-				</Row>
-			</Card>
-		</>
-	);
+  const articleList = prop.data.articles.map((item, id) => {
+    return <div key={id}>{item.title}</div>;
+  });
+  return (
+    <>
+      <Card hoverable bordered={false} style={{ width: "100%" }}>
+        <Title level={4}>
+          <Link to={`/anthology/${prop.data.id}`}>{prop.data.title}</Link>
+        </Title>
+        <div>
+          <Text type="secondary">{prop.data.subTitle}</Text>
+        </div>
+        <div>
+          <Text>{prop.data.summary}</Text>
+        </div>
+        <Link to={`/blog/${prop.data.studio.studioName}/anthology`}>
+          <StudioName data={prop.data.studio} />
+        </Link>
+        <Row>
+          <Col flex={"100px"}>Content</Col>
+          <Col flex={"auto"}>{articleList}</Col>
+        </Row>
+      </Card>
+    </>
+  );
 };
 
 export default Widget;

+ 65 - 65
dashboard/src/components/article/AnthologyDetail.tsx

@@ -1,87 +1,87 @@
 import { useState, useEffect } from "react";
 import { Typography } from "antd";
+import MDEditor from "@uiw/react-md-editor";
 
-import type { IAnthologyData } from "./AnthologyCard";
+import { get } from "../../request";
 import type {
-	IAnthologyDataResponse,
-	IAnthologyResponse,
+  IAnthologyDataResponse,
+  IAnthologyResponse,
 } from "../api/Article";
+import type { IAnthologyData } from "./AnthologyCard";
 import TocTree from "./TocTree";
-import { get } from "../../request";
-import MDEditor from "@uiw/react-md-editor";
 
 const { Title, Text } = Typography;
 
 interface IWidgetAnthologyDetail {
-	aid: string;
-	channels?: string[];
+  aid: string;
+  channels?: string[];
 }
 
 const defaultData: IAnthologyData = {
-	id: "",
-	title: "",
-	subTitle: "",
-	summary: "",
-	articles: [],
-	studio: {
-		id: "",
-		studioName: "",
-		nickName: "",
-		avatar: "",
-	},
-	created_at: "",
-	updated_at: "",
+  id: "",
+  title: "",
+  subTitle: "",
+  summary: "",
+  articles: [],
+  studio: {
+    id: "",
+    studioName: "",
+    nickName: "",
+    avatar: "",
+  },
+  created_at: "",
+  updated_at: "",
 };
 
 const Widget = (prop: IWidgetAnthologyDetail) => {
-	const [tableData, setTableData] = useState(defaultData);
+  const [tableData, setTableData] = useState(defaultData);
 
-	useEffect(() => {
-		console.log("useEffect");
-		fetchData(prop.aid);
-	}, [prop.aid]);
+  useEffect(() => {
+    console.log("useEffect");
+    fetchData(prop.aid);
+  }, [prop.aid]);
 
-	function fetchData(id: string) {
-		get<IAnthologyResponse>(`/v2/anthology/${id}`)
-			.then((response) => {
-				const item: IAnthologyDataResponse = response.data;
-				let newTree: IAnthologyData = {
-					id: item.uid,
-					title: item.title,
-					subTitle: item.subtitle,
-					summary: item.summary,
-					articles: item.article_list.map((al) => {
-						return {
-							key: al.article,
-							title: al.title,
-							level: parseInt(al.level),
-						};
-					}),
-					studio: item.studio,
-					created_at: item.created_at,
-					updated_at: item.updated_at,
-				};
-				setTableData(newTree);
-				console.log("toc", newTree.articles);
-			})
-			.catch((error) => {
-				console.error(error);
-			});
-	}
-	return (
-		<>
-			<Title level={4}>{tableData.title}</Title>
-			<div>
-				<Text type="secondary">{tableData.subTitle}</Text>
-			</div>
-			<div>
-				<MDEditor.Markdown source={tableData.summary} />
-			</div>
-			<Title level={5}>目录</Title>
+  function fetchData(id: string) {
+    get<IAnthologyResponse>(`/v2/anthology/${id}`)
+      .then((response) => {
+        const item: IAnthologyDataResponse = response.data;
+        let newTree: IAnthologyData = {
+          id: item.uid,
+          title: item.title,
+          subTitle: item.subtitle,
+          summary: item.summary,
+          articles: item.article_list.map((al) => {
+            return {
+              key: al.article,
+              title: al.title,
+              level: parseInt(al.level),
+            };
+          }),
+          studio: item.studio,
+          created_at: item.created_at,
+          updated_at: item.updated_at,
+        };
+        setTableData(newTree);
+        console.log("toc", newTree.articles);
+      })
+      .catch((error) => {
+        console.error(error);
+      });
+  }
+  return (
+    <>
+      <Title level={4}>{tableData.title}</Title>
+      <div>
+        <Text type="secondary">{tableData.subTitle}</Text>
+      </div>
+      <div>
+        <MDEditor.Markdown source={tableData.summary} />
+      </div>
+      <Title level={5}>目录</Title>
 
-			<TocTree treeData={tableData.articles} />
-		</>
-	);
+      <TocTree treeData={tableData.articles} />
+    </>
+  );
 };
 
 export default Widget;

+ 52 - 51
dashboard/src/components/article/AnthologyList.tsx

@@ -1,66 +1,67 @@
 import { useState, useEffect } from "react";
 import { List } from "antd";
+
+import { get } from "../../request";
+import type { IAnthologyListResponse } from "../api/Article";
 import AnthologyCard from "./AnthologyCard";
 import type { IAnthologyData } from "./AnthologyCard";
-import type { IAnthologyListResponse } from "../api/Article";
-import { get } from "../../request";
 
 const defaultData: IAnthologyData[] = [];
 interface IWidgetAnthologyList {
-	view: string;
-	id?: string;
+  view: string;
+  id?: string;
 }
 const Widget = (prop: IWidgetAnthologyList) => {
-	const [tableData, setTableData] = useState(defaultData);
+  const [tableData, setTableData] = useState(defaultData);
 
-	useEffect(() => {
-		console.log("useEffect", prop);
-		if (typeof prop.id === "undefined") {
-			fetchData(prop.view);
-		} else {
-			fetchData(prop.view, prop.id);
-		}
-	}, [prop]);
+  useEffect(() => {
+    console.log("useEffect", prop);
+    if (typeof prop.id === "undefined") {
+      fetchData(prop.view);
+    } else {
+      fetchData(prop.view, prop.id);
+    }
+  }, [prop]);
 
-	function fetchData(view: string, id?: string) {
-		let url = `/v2/anthology?view=${view}` + (id ? `&studio=${id}` : "");
-		console.log("get-url", url);
-		get<IAnthologyListResponse>(url).then(function (response) {
-			console.log("ajex", response);
-			let newTree: IAnthologyData[] = response.data.rows.map((item) => {
-				return {
-					id: item.uid,
-					title: item.title,
-					subTitle: item.subtitle,
-					summary: item.summary,
-					articles: item.article_list.map((al) => {
-						return {
-							key: al.article,
-							title: al.title,
-							level: parseInt(al.level),
-						};
-					}),
-					studio: item.studio,
-					created_at: item.created_at,
-					updated_at: item.updated_at,
-				};
-			});
-			setTableData(newTree);
-		});
-	}
+  function fetchData(view: string, id?: string) {
+    let url = `/v2/anthology?view=${view}` + (id ? `&studio=${id}` : "");
+    console.log("get-url", url);
+    get<IAnthologyListResponse>(url).then(function (response) {
+      console.log("ajex", response);
+      let newTree: IAnthologyData[] = response.data.rows.map((item) => {
+        return {
+          id: item.uid,
+          title: item.title,
+          subTitle: item.subtitle,
+          summary: item.summary,
+          articles: item.article_list.map((al) => {
+            return {
+              key: al.article,
+              title: al.title,
+              level: parseInt(al.level),
+            };
+          }),
+          studio: item.studio,
+          created_at: item.created_at,
+          updated_at: item.updated_at,
+        };
+      });
+      setTableData(newTree);
+    });
+  }
 
-	return (
-		<List
-			itemLayout="vertical"
-			size="large"
-			dataSource={tableData}
-			renderItem={(item) => (
-				<List.Item>
-					<AnthologyCard data={item} />
-				</List.Item>
-			)}
-		/>
-	);
+  return (
+    <List
+      itemLayout="vertical"
+      size="large"
+      dataSource={tableData}
+      renderItem={(item) => (
+        <List.Item>
+          <AnthologyCard data={item} />
+        </List.Item>
+      )}
+    />
+  );
 };
 
 export default Widget;

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

@@ -1,5 +1,6 @@
 import { message } from "antd";
 import { useEffect, useState } from "react";
+
 import { get } from "../../request";
 import { IArticleDataResponse, IArticleResponse } from "../api/Article";
 import ArticleView from "./ArticleView";

+ 5 - 4
dashboard/src/components/article/ArticleCard.tsx

@@ -1,12 +1,13 @@
+import { useIntl } from "react-intl";
+import { useState } from "react";
 import { Button, Card, Dropdown, Space, Segmented } from "antd";
 import { MoreOutlined, ReloadOutlined } from "@ant-design/icons";
 import type { MenuProps } from "antd";
-import { IWidgetArticleData } from "./ArticleView";
-import { useIntl } from "react-intl";
-import { useState } from "react";
-import ArticleCardMainMenu from "./ArticleCardMainMenu";
+
 import store from "../../store";
 import { modeChange } from "../../reducers/article-mode";
+import { IWidgetArticleData } from "./ArticleView";
+import ArticleCardMainMenu from "./ArticleCardMainMenu";
 import { ArticleMode } from "./Article";
 
 interface IWidgetArticleCard {

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

@@ -1,5 +1,6 @@
 import { Tabs, Button, Popover } from "antd";
 import { MenuOutlined, PushpinOutlined } from "@ant-design/icons";
+
 import PaliTextToc from "./PaliTextToc";
 import Find from "./Find";
 import Nav from "./Nav";

+ 1 - 1
dashboard/src/components/article/ArticleCreate.tsx

@@ -2,9 +2,9 @@ import { useIntl } from "react-intl";
 import { ProForm, ProFormText } from "@ant-design/pro-components";
 import { message } from "antd";
 
-import LangSelect from "../general/LangSelect";
 import { post } from "../../request";
 import { IArticleCreateRequest, IArticleResponse } from "../api/Article";
+import LangSelect from "../general/LangSelect";
 
 interface IFormData {
   title: string;

+ 1 - 4
dashboard/src/components/article/ArticleTabs.tsx

@@ -1,16 +1,13 @@
 import { useState, useRef, useEffect } from "react";
 import { Tabs, Button } from "antd";
 import { CloseOutlined } from "@ant-design/icons";
+
 import { useAppSelector } from "../../hooks";
 import { siteInfo as _siteInfo } from "../../reducers/open-article";
 import Article from "./Article";
 
 const defaultPanes = [{ label: `Tab`, children: <></>, key: "1" }];
 
-const Test = () => {
-  console.log("new");
-  return <div>hello</div>;
-};
 interface IWidget {
   onClose?: Function;
 }

+ 1 - 1
dashboard/src/components/article/Find.tsx

@@ -1,5 +1,5 @@
-import { Input, Space, Select } from "antd";
 import { useState } from "react";
+import { Input, Space, Select } from "antd";
 
 const { Search } = Input;
 

+ 1 - 1
dashboard/src/components/article/PaliTextToc.tsx

@@ -1,5 +1,5 @@
-import { parseInt } from "lodash";
 import { useState, useEffect } from "react";
+
 import { get } from "../../request";
 import { IPaliTocListResponse } from "../api/Corpus";
 import { ListNodeData } from "../studio/EditableTree";

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

@@ -1,4 +1,5 @@
 import { useEffect, useState } from "react";
+
 import { useAppSelector } from "../../hooks";
 import { message } from "../../reducers/command";
 

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

@@ -1,4 +1,5 @@
 import { Tree } from "antd";
+
 import type { TreeProps } from "antd/es/tree";
 import type { ListNodeData } from "../studio/EditableTree";
 

+ 80 - 64
dashboard/src/components/auth/SignInAvatar.tsx

@@ -1,3 +1,4 @@
+import { useIntl } from "react-intl";
 import { useEffect, useState } from "react";
 import { Link, useNavigate } from "react-router-dom";
 import { Tooltip } from "antd";
@@ -5,78 +6,93 @@ import { Avatar } from "antd";
 import { Popover } from "antd";
 import { ProCard } from "@ant-design/pro-components";
 import {
-	UserOutlined,
-	HomeOutlined,
-	LogoutOutlined,
-	SettingOutlined,
+  UserOutlined,
+  HomeOutlined,
+  LogoutOutlined,
+  SettingOutlined,
 } from "@ant-design/icons";
 
 import { useAppSelector } from "../../hooks";
 import { currentUser as _currentUser } from "../../reducers/current-user";
 
 const Widget = () => {
-	// TODO
-	const navigate = useNavigate();
-	const [userName, setUserName] = useState("");
-	const [nickName, setNickName] = useState("");
-	const user = useAppSelector(_currentUser);
-	useEffect(() => {
-		setUserName(user ? user.realName : "");
-		setNickName(user ? user.nickName : "");
-	}, [user]);
+  // TODO
+  const intl = useIntl();
+  const navigate = useNavigate();
+  const [userName, setUserName] = useState("");
+  const [nickName, setNickName] = useState("");
+  const user = useAppSelector(_currentUser);
+  useEffect(() => {
+    setUserName(user ? user.realName : "");
+    setNickName(user ? user.nickName : "");
+  }, [user]);
 
-	const userCard = (
-		<>
-			<ProCard
-				style={{ maxWidth: 500, minWidth: 300 }}
-				actions={[
-					<Tooltip title="设置">
-						<SettingOutlined key="setting" />
-					</Tooltip>,
-					<Tooltip title="个人空间">
-						<Link to={`/blog/${userName}/overview`}>
-							<HomeOutlined key="home" />
-						</Link>
-					</Tooltip>,
-					<Tooltip title="退出登录">
-						<LogoutOutlined
-							key="logout"
-							onClick={() => {
-								sessionStorage.removeItem("token");
-								localStorage.removeItem("token");
-								navigate("/anonymous/users/sign-in");
-							}}
-						/>
-					</Tooltip>,
-				]}
-			>
-				<div>
-					<h2>{nickName}</h2>
-					<div>欢迎遨游法的海洋</div>
-				</div>
-			</ProCard>
-		</>
-	);
+  const userCard = (
+    <>
+      <ProCard
+        style={{ maxWidth: 500, minWidth: 300 }}
+        actions={[
+          <Tooltip
+            title={intl.formatMessage({
+              id: "buttons.setting",
+            })}
+          >
+            <SettingOutlined key="setting" />
+          </Tooltip>,
+          <Tooltip
+            title={intl.formatMessage({
+              id: "columns.library.blog.label",
+            })}
+          >
+            <Link to={`/blog/${userName}/overview`}>
+              <HomeOutlined key="home" />
+            </Link>
+          </Tooltip>,
+          <Tooltip
+            title={intl.formatMessage({
+              id: "buttons.sign-out",
+            })}
+          >
+            <LogoutOutlined
+              key="logout"
+              onClick={() => {
+                sessionStorage.removeItem("token");
+                localStorage.removeItem("token");
+                navigate("/anonymous/users/sign-in");
+              }}
+            />
+          </Tooltip>,
+        ]}
+      >
+        <div>
+          <h2>{nickName}</h2>
+          <div style={{ textAlign: "right" }}>
+            {intl.formatMessage({
+              id: "buttons.welcome",
+            })}
+          </div>
+        </div>
+      </ProCard>
+    </>
+  );
 
-	if (typeof user === "undefined") {
-		return <Link to="/anonymous/users/sign-in">登录</Link>;
-	} else {
-		return (
-			<>
-				<Popover content={userCard} placement="bottomRight">
-					<Avatar
-						style={{ backgroundColor: "#87d068" }}
-						icon={<UserOutlined />}
-					>
-						{nickName.slice(0, 1)}
-					</Avatar>
-				</Popover>
-			</>
-		);
-	}
+  if (typeof user === "undefined") {
+    return <Link to="/anonymous/users/sign-in">登录</Link>;
+  } else {
+    return (
+      <>
+        <Popover content={userCard} placement="bottomRight">
+          <Avatar
+            style={{ backgroundColor: "#87d068" }}
+            icon={<UserOutlined />}
+            size="small"
+          >
+            {nickName.slice(0, 1)}
+          </Avatar>
+        </Popover>
+      </>
+    );
+  }
 };
 
 export default Widget;
-function currentUser(currentUser: any) {
-	throw new Error("Function not implemented.");
-}

+ 49 - 0
dashboard/src/components/auth/StudioCard.tsx

@@ -0,0 +1,49 @@
+import { useIntl } from "react-intl";
+import { Popover, Avatar } from "antd";
+import { IStudio } from "./StudioName";
+import { Link } from "react-router-dom";
+
+interface IWidget {
+  studio: IStudio;
+  children?: JSX.Element;
+}
+const Widget = ({ studio, children }: IWidget) => {
+  const intl = useIntl();
+
+  return (
+    <>
+      <Popover
+        content={
+          <>
+            <div style={{ display: "flex" }}>
+              <div style={{ paddingRight: 8 }}>
+                <Avatar style={{ backgroundColor: "#87d068" }} size="small">
+                  {studio.nickName.slice(0, 1)}
+                </Avatar>
+              </div>
+              <div>
+                <div>{studio.nickName}</div>
+                <div>译文(2) | 课程(3)</div>
+                <div>
+                  <Link
+                    to={`/blog/${studio.studioName}/overview`}
+                    target="_blank"
+                  >
+                    {intl.formatMessage({
+                      id: "columns.library.blog.label",
+                    })}
+                  </Link>
+                </div>
+              </div>
+            </div>
+          </>
+        }
+        placement="bottomRight"
+      >
+        {children}
+      </Popover>
+    </>
+  );
+};
+
+export default Widget;

+ 14 - 10
dashboard/src/components/auth/StudioName.tsx

@@ -1,5 +1,7 @@
 import { Avatar, Space } from "antd";
 
+import StudioCard from "./StudioCard";
+
 export interface IStudio {
   id: string;
   nickName: string;
@@ -21,16 +23,18 @@ const Widget = ({
   // TODO
   const avatar = <Avatar size="small">{data.nickName.slice(0, 1)}</Avatar>;
   return (
-    <Space
-      onClick={() => {
-        if (typeof onClick !== "undefined") {
-          onClick(data.studioName);
-        }
-      }}
-    >
-      {showAvatar ? avatar : ""}
-      {showName ? data.nickName : ""}
-    </Space>
+    <StudioCard studio={data}>
+      <Space
+        onClick={() => {
+          if (typeof onClick !== "undefined") {
+            onClick(data.studioName);
+          }
+        }}
+      >
+        {showAvatar ? avatar : ""}
+        {showName ? data.nickName : ""}
+      </Space>
+    </StudioCard>
   );
 };
 

+ 14 - 7
dashboard/src/components/auth/ToLibaray.tsx

@@ -1,14 +1,21 @@
+import { useIntl } from "react-intl";
 import { Button } from "antd";
 import { Link } from "react-router-dom";
 
 const Widget = () => {
-	return (
-		<>
-			<Link to="/palicanon/list">
-				<Button type="primary">藏经阁</Button>
-			</Link>
-		</>
-	);
+  const intl = useIntl();
+
+  return (
+    <>
+      <Link to="/palicanon/list">
+        <Button type="primary">
+          {intl.formatMessage({
+            id: "columns.library.title",
+          })}
+        </Button>
+      </Link>
+    </>
+  );
 };
 
 export default Widget;

+ 24 - 13
dashboard/src/components/auth/ToStudio.tsx

@@ -1,23 +1,34 @@
+import { useIntl } from "react-intl";
 import { Button } from "antd";
-//import { useEffect, useState } from "react";
 import { Link } from "react-router-dom";
+
 import { useAppSelector } from "../../hooks";
 import { currentUser as _currentUser } from "../../reducers/current-user";
 
 const Widget = () => {
-	const user = useAppSelector(_currentUser);
+  const intl = useIntl();
+
+  const user = useAppSelector(_currentUser);
 
-	if (typeof user !== "undefined") {
-		return (
-			<>
-				<Link to={`/studio/${user.realName}/home`}>
-					<Button type="primary">藏经阁</Button>
-				</Link>
-			</>
-		);
-	} else {
-		return <></>;
-	}
+  if (typeof user !== "undefined") {
+    return (
+      <>
+        <Link to={`/studio/${user.realName}/home`}>
+          <Button
+            type="primary"
+            size="small"
+            style={{ paddingLeft: 18, paddingRight: 18 }}
+          >
+            {intl.formatMessage({
+              id: "columns.studio.title",
+            })}
+          </Button>
+        </Link>
+      </>
+    );
+  } else {
+    return <></>;
+  }
 };
 
 export default Widget;

+ 11 - 10
dashboard/src/components/auth/User.tsx

@@ -1,17 +1,18 @@
 import { Avatar } from "antd";
+
 export interface IUser {
-	id: string;
-	nickName: string;
-	realName: string;
-	avatar: string;
+  id: string;
+  nickName: string;
+  realName: string;
+  avatar: string;
 }
 const Widget = ({ nickName, realName, avatar }: IUser) => {
-	return (
-		<>
-			<Avatar size="small">{nickName?.slice(0, 1)}</Avatar>
-			{nickName}
-		</>
-	);
+  return (
+    <>
+      <Avatar size="small">{nickName?.slice(0, 1)}</Avatar>
+      {nickName}
+    </>
+  );
 };
 
 export default Widget;

+ 1 - 0
dashboard/src/components/auth/setting/SettingArticle.tsx

@@ -1,4 +1,5 @@
 import { Divider } from "antd";
+
 import { SettingFind } from "./default";
 import SettingItem from "./SettingItem";
 

+ 2 - 1
dashboard/src/components/auth/setting/SettingItem.tsx

@@ -1,4 +1,6 @@
+import { useEffect, useState } from "react";
 import { Switch, Typography, Radio, RadioChangeEvent, Select } from "antd";
+
 import {
   onChange as onSettingChanged,
   settingInfo,
@@ -7,7 +9,6 @@ import {
 import { useAppSelector } from "../../../hooks";
 import store from "../../../store";
 import { ISetting } from "./default";
-import { useEffect, useState } from "react";
 
 const { Title, Text } = Typography;
 

+ 1 - 0
dashboard/src/components/auth/setting/default.ts

@@ -1,4 +1,5 @@
 import { useIntl } from "react-intl";
+
 import { ISettingItem } from "../../../reducers/setting";
 
 export interface ISettingItemOption {

+ 58 - 54
dashboard/src/components/blog/TopArticleCard.tsx

@@ -1,73 +1,77 @@
+import { Link } from "react-router-dom";
 import { Card } from "antd";
-import { AppstoreOutlined, LikeOutlined, FieldTimeOutlined } from "@ant-design/icons";
-
+import {
+  AppstoreOutlined,
+  LikeOutlined,
+  FieldTimeOutlined,
+} from "@ant-design/icons";
 import { Space } from "antd";
-import { Link } from "react-router-dom";
 
 interface IIconParamListData {
-	label: React.ReactNode;
-	key: string;
-	icon: React.ReactNode;
+  label: React.ReactNode;
+  key: string;
+  icon: React.ReactNode;
 }
 interface IWidgetIconParamList {
-	data: IIconParamListData[];
+  data: IIconParamListData[];
 }
 const IconParamList = (prop: IWidgetIconParamList) => {
-	return (
-		<><Space>
-			{prop.data.map((item, id) => {
-				return (
-					<Space>
-						{item.icon} {item.label}
-					</Space>
-				);
-			})}
-			</Space>
-		</>
-	);
+  return (
+    <>
+      <Space>
+        {prop.data.map((item, id) => {
+          return (
+            <Space>
+              {item.icon} {item.label}
+            </Space>
+          );
+        })}
+      </Space>
+    </>
+  );
 };
 
 export interface ITopArticleCardData {
-	title: string;
-	link: string;
-	like: number;
-	hit: number;
-	updatedAt: string;
+  title: string;
+  link: string;
+  like: number;
+  hit: number;
+  updatedAt: string;
 }
 interface IWidgetTopArticleCard {
-	data: ITopArticleCardData;
+  data: ITopArticleCardData;
 }
 const Widget = (prop: IWidgetTopArticleCard) => {
-	const items: IIconParamListData[] = [
-		{
-			label: "经藏",
-			key: "sutta",
-			icon: <AppstoreOutlined />,
-		},
-		{
-			label: prop.data.like,
-			key: "like",
-			icon: <LikeOutlined />,
-		},
-		{
-			label: prop.data.updatedAt,
-			key: "updated",
-			icon: <FieldTimeOutlined />,
-		},
-	];
+  const items: IIconParamListData[] = [
+    {
+      label: "经藏",
+      key: "sutta",
+      icon: <AppstoreOutlined />,
+    },
+    {
+      label: prop.data.like,
+      key: "like",
+      icon: <LikeOutlined />,
+    },
+    {
+      label: prop.data.updatedAt,
+      key: "updated",
+      icon: <FieldTimeOutlined />,
+    },
+  ];
 
-	return (
-		<>
-			<Card>
-				<h4>
-					<Link to={prop.data.link}>{prop.data.title}</Link>
-				</h4>
-				<div>
-					<IconParamList data={items} />
-				</div>
-			</Card>
-		</>
-	);
+  return (
+    <>
+      <Card>
+        <h4>
+          <Link to={prop.data.link}>{prop.data.title}</Link>
+        </h4>
+        <div>
+          <IconParamList data={items} />
+        </div>
+      </Card>
+    </>
+  );
 };
 
 export default Widget;

+ 41 - 21
dashboard/src/components/blog/TopArticles.tsx

@@ -1,30 +1,50 @@
 import { Row, Col } from "antd";
+
 import TopArticleCard, { ITopArticleCardData } from "./TopArticleCard";
+
 interface IWidgetTopArticles {
-	studio: string;
+  studio: string;
 }
 const Widget = (prop: IWidgetTopArticles) => {
-	const data: ITopArticleCardData[] = [
-		{
-			title: "法句心品",
-			link: "/my/article/12345",
-			like: 50,
-			hit: 100,
-			updatedAt: "2022-12-3",
-		},
-		{ title: "法句心品", link: "/my/article/12345", like: 50, hit: 100, updatedAt: "2022-12-3" },
-		{ title: "疑惑度脱-应学法", link: "/my/article/12345", like: 50, hit: 100, updatedAt: "2022-12-3" },
-		{ title: "法句心品", link: "/my/article/12345", like: 50, hit: 100, updatedAt: "2022-12-3" },
-	];
+  const data: ITopArticleCardData[] = [
+    {
+      title: "法句心品",
+      link: "/my/article/12345",
+      like: 50,
+      hit: 100,
+      updatedAt: "2022-12-3",
+    },
+    {
+      title: "法句心品",
+      link: "/my/article/12345",
+      like: 50,
+      hit: 100,
+      updatedAt: "2022-12-3",
+    },
+    {
+      title: "疑惑度脱-应学法",
+      link: "/my/article/12345",
+      like: 50,
+      hit: 100,
+      updatedAt: "2022-12-3",
+    },
+    {
+      title: "法句心品",
+      link: "/my/article/12345",
+      like: 50,
+      hit: 100,
+      updatedAt: "2022-12-3",
+    },
+  ];
 
-	const list = data.map((item, id) => {
-		return (
-			<Col flex="400px">
-				<TopArticleCard data={item} key={id} />
-			</Col>
-		);
-	});
-	return <Row>{list}</Row>;
+  const list = data.map((item, id) => {
+    return (
+      <Col flex="400px">
+        <TopArticleCard data={item} key={id} />
+      </Col>
+    );
+  });
+  return <Row>{list}</Row>;
 };
 
 export default Widget;

+ 1 - 1
dashboard/src/components/channel/ChannelCreate.tsx

@@ -2,9 +2,9 @@ import { useIntl } from "react-intl";
 import { ProForm, ProFormText } from "@ant-design/pro-components";
 import { message } from "antd";
 
-import ChannelTypeSelect from "./ChannelTypeSelect";
 import { post } from "../../request";
 import { IApiResponseChannel } from "../api/Channel";
+import ChannelTypeSelect from "./ChannelTypeSelect";
 import LangSelect from "../general/LangSelect";
 
 interface IFormData {

+ 2 - 1
dashboard/src/components/channel/ChannelList.tsx

@@ -1,9 +1,10 @@
 import { useState, useEffect } from "react";
 import { List } from "antd";
-import ChannelListItem from "./ChannelListItem";
+
 import type { ChannelInfoProps } from "../api/Channel";
 import { IApiResponseChannelList } from "../api/Corpus";
 import { get } from "../../request";
+import ChannelListItem from "./ChannelListItem";
 
 export interface ChannelFilterProps {
   chapterProgress: number;

+ 1 - 0
dashboard/src/components/channel/ChannelListItem.tsx

@@ -1,5 +1,6 @@
 import { Space } from "antd";
 import { Avatar } from "antd";
+
 import type { ChannelInfoProps } from "../api/Channel";
 
 type IWidgetChannelListItem = {

+ 1 - 0
dashboard/src/components/channel/ChannelPicker.tsx

@@ -1,5 +1,6 @@
 import { useState } from "react";
 import { Button, Modal } from "antd";
+
 import ChannelPickerTable from "./ChannelPickerTable";
 import { IChannel } from "./Channel";
 

+ 2 - 30
dashboard/src/components/channel/ChannelPickerTable.tsx

@@ -2,9 +2,8 @@ import { ProTable } from "@ant-design/pro-components";
 import { useIntl } from "react-intl";
 import { Space, Table } from "antd";
 import { GlobalOutlined } from "@ant-design/icons";
-import type { MenuProps } from "antd";
-import { Button, Menu } from "antd";
-import { SearchOutlined } from "@ant-design/icons";
+import { Button } from "antd";
+
 import { PublicityValueEnum } from "../studio/table";
 import { IApiResponseChannelList, IFinal } from "../api/Channel";
 import { get } from "../../request";
@@ -13,33 +12,6 @@ import StudioName, { IStudio } from "../auth/StudioName";
 import ProgressSvg from "./ProgressSvg";
 import { IChannel } from "./Channel";
 
-const onMenuClick: MenuProps["onClick"] = (e) => {
-  console.log("click", e);
-};
-
-const menu = (
-  <Menu
-    onClick={onMenuClick}
-    items={[
-      {
-        key: "1",
-        label: "在藏经阁中打开",
-        icon: <SearchOutlined />,
-      },
-      {
-        key: "2",
-        label: "分享",
-        icon: <SearchOutlined />,
-      },
-      {
-        key: "3",
-        label: "删除",
-        icon: <SearchOutlined />,
-      },
-    ]}
-  />
-);
-
 export interface IItem {
   id: number;
   uid: string;

+ 42 - 42
dashboard/src/components/channel/ChannelTypeSelect.tsx

@@ -1,49 +1,49 @@
-import { ProFormSelect } from "@ant-design/pro-components";
 import { useIntl } from "react-intl";
+import { ProFormSelect } from "@ant-design/pro-components";
 
 const Widget = () => {
-	const intl = useIntl();
+  const intl = useIntl();
 
-	const channelTypeOptions = [
-		{
-			value: "translation",
-			label: intl.formatMessage({ id: "channel.type.translation.label" }),
-		},
-		{
-			value: "nissaya",
-			label: intl.formatMessage({ id: "channel.type.nissaya.label" }),
-		},
-		{
-			value: "commentary",
-			label: intl.formatMessage({ id: "channel.type.commentary.label" }),
-		},
-		{
-			value: "original",
-			label: intl.formatMessage({ id: "channel.type.original.label" }),
-		},
-		{
-			value: "general",
-			label: intl.formatMessage({ id: "channel.type.general.label" }),
-		},
-	];
-	return (
-		<ProFormSelect
-			options={channelTypeOptions}
-			initialValue="translation"
-			width="xs"
-			name="type"
-			allowClear={false}
-			label={intl.formatMessage({ id: "channel.type" })}
-			rules={[
-				{
-					required: true,
-					message: intl.formatMessage({
-						id: "channel.type.message.required",
-					}),
-				},
-			]}
-		/>
-	);
+  const channelTypeOptions = [
+    {
+      value: "translation",
+      label: intl.formatMessage({ id: "channel.type.translation.label" }),
+    },
+    {
+      value: "nissaya",
+      label: intl.formatMessage({ id: "channel.type.nissaya.label" }),
+    },
+    {
+      value: "commentary",
+      label: intl.formatMessage({ id: "channel.type.commentary.label" }),
+    },
+    {
+      value: "original",
+      label: intl.formatMessage({ id: "channel.type.original.label" }),
+    },
+    {
+      value: "general",
+      label: intl.formatMessage({ id: "channel.type.general.label" }),
+    },
+  ];
+  return (
+    <ProFormSelect
+      options={channelTypeOptions}
+      initialValue="translation"
+      width="xs"
+      name="type"
+      allowClear={false}
+      label={intl.formatMessage({ id: "channel.type" })}
+      rules={[
+        {
+          required: true,
+          message: intl.formatMessage({
+            id: "channel.type.message.required",
+          }),
+        },
+      ]}
+    />
+  );
 };
 
 export default Widget;

+ 69 - 69
dashboard/src/components/corpus/BookTree.tsx

@@ -1,90 +1,90 @@
-//import { useIntl } from "react-intl";
 import { useState, useEffect } from "react";
 import { DownOutlined } from "@ant-design/icons";
 import { Layout, Space, Tree } from "antd";
 import { Select } from "antd";
 import { Typography } from "antd";
 import type { TreeProps } from "antd/es/tree";
+
 import { get } from "../../request";
 
 const { Text } = Typography;
 
 const { Option } = Select;
 interface IWidgetBookTree {
-	root?: string;
-	path?: string[];
+  root?: string;
+  path?: string[];
 }
 const Widget = (prop: IWidgetBookTree) => {
-	//Library foot bar
-	//const intl = useIntl(); //i18n
-	const defaultTreeData: NewTree[] = [];
-	const [treeData, setTreeData] = useState(defaultTreeData);
+  //Library foot bar
+  //const intl = useIntl(); //i18n
+  const defaultTreeData: NewTree[] = [];
+  const [treeData, setTreeData] = useState(defaultTreeData);
 
-	useEffect(() => {
-		if (typeof prop.root !== "undefined") fetchBookTree(prop.root);
-	}, [prop.root]);
+  useEffect(() => {
+    if (typeof prop.root !== "undefined") fetchBookTree(prop.root);
+  }, [prop.root]);
 
-	type OrgTree = {
-		name: string;
-		tag: string[];
-		children: OrgTree[];
-	};
-	type NewTree = {
-		title: string;
-		key: string;
-		tag: string[];
-		children: NewTree[];
-	};
-	const onSelect: TreeProps["onSelect"] = (selectedKeys, info) => {
-		//let aaa: NewTree = info.node;
-		//console.log("selected", aaa.tag);
-	};
+  type OrgTree = {
+    name: string;
+    tag: string[];
+    children: OrgTree[];
+  };
+  type NewTree = {
+    title: string;
+    key: string;
+    tag: string[];
+    children: NewTree[];
+  };
+  const onSelect: TreeProps["onSelect"] = (selectedKeys, info) => {
+    //let aaa: NewTree = info.node;
+    //console.log("selected", aaa.tag);
+  };
 
-	function fetchBookTree(value: string) {
-		function treeMap(params: OrgTree): NewTree {
-			return {
-				title: params.name,
-				key: params.tag.join(),
-				tag: params.tag,
-				children: Array.isArray(params.children)
-					? params.children.map(treeMap)
-					: [],
-			};
-		}
-		get(`/v2/palibook/${value}`).then((response) => {
-			const myJson = response as unknown as OrgTree[];
-			let newTree = myJson.map(treeMap);
-			setTreeData(newTree);
-		});
-	}
-	const handleChange = (value: string) => {
-		console.log(`selected ${value}`);
-		fetchBookTree(value);
-	};
+  function fetchBookTree(value: string) {
+    function treeMap(params: OrgTree): NewTree {
+      return {
+        title: params.name,
+        key: params.tag.join(),
+        tag: params.tag,
+        children: Array.isArray(params.children)
+          ? params.children.map(treeMap)
+          : [],
+      };
+    }
+    get(`/v2/palibook/${value}`).then((response) => {
+      const myJson = response as unknown as OrgTree[];
+      let newTree = myJson.map(treeMap);
+      setTreeData(newTree);
+    });
+  }
+  const handleChange = (value: string) => {
+    console.log(`selected ${value}`);
+    fetchBookTree(value);
+  };
 
-	// TODO
-	return (
-		<Layout>
-			<Space>
-				<Text>目录风格</Text>
-				<Select
-					defaultValue={prop.root}
-					loading={false}
-					onChange={handleChange}
-				>
-					<Option value="defualt">Defualt</Option>
-					<Option value="cscd">CSCD</Option>
-				</Select>
-			</Space>
-			<Tree
-				showLine
-				switcherIcon={<DownOutlined />}
-				defaultExpandedKeys={["sutta"]}
-				onSelect={onSelect}
-				treeData={treeData}
-			/>
-		</Layout>
-	);
+  // TODO
+  return (
+    <Layout>
+      <Space>
+        <Text>目录风格</Text>
+        <Select
+          defaultValue={prop.root}
+          loading={false}
+          onChange={handleChange}
+        >
+          <Option value="defualt">Defualt</Option>
+          <Option value="cscd">CSCD</Option>
+        </Select>
+      </Space>
+      <Tree
+        showLine
+        switcherIcon={<DownOutlined />}
+        defaultExpandedKeys={["sutta"]}
+        onSelect={onSelect}
+        treeData={treeData}
+      />
+    </Layout>
+  );
 };
 
 export default Widget;

+ 2 - 5
dashboard/src/components/corpus/BookTreeList.tsx

@@ -1,9 +1,11 @@
 import { Link } from "react-router-dom";
 import { useState, useEffect } from "react";
 import { List, Breadcrumb, Card, Select, Space } from "antd";
+
 import { PaliToEn } from "../../utils";
 import { get } from "../../request";
 import { IPaliBookListResponse } from "../api/Corpus";
+
 const { Option } = Select;
 
 interface IWidgetBookTreeList {
@@ -32,11 +34,6 @@ const Widget = (prop: IWidgetBookTreeList) => {
     if (prop.root) fetchBookTree(prop.root);
   }, [prop.root]);
 
-  type OrgTree = {
-    name: string;
-    tag: string[];
-    children: OrgTree[];
-  };
   type NewTree = {
     title: string;
     dir: string;

+ 27 - 20
dashboard/src/components/corpus/ChapterCard.tsx

@@ -1,5 +1,7 @@
+import { Link } from "react-router-dom";
 import { Row, Col } from "antd";
 import { Typography } from "antd";
+
 import TimeShow from "../general/TimeShow";
 import TocPath from "../corpus/TocPath";
 import TagArea from "../tag/TagArea";
@@ -7,21 +9,21 @@ import type { TagNode } from "../tag/TagArea";
 import type { ChannelInfoProps } from "../api/Channel";
 import ChannelListItem from "../channel/ChannelListItem";
 
-const { Title, Paragraph, Link, Text } = Typography;
+const { Title, Paragraph, Text } = Typography;
 
 export interface ChapterData {
-  Title: string;
-  PaliTitle: string;
-  Path: string;
-  Book: number;
-  Paragraph: number;
-  Summary: string;
-  Tag: TagNode[];
-  Channel: ChannelInfoProps;
-  CreatedAt: string;
-  UpdatedAt: string;
-  Hit: number;
-  Like: number;
+  title: string;
+  paliTitle: string;
+  path: string;
+  book: number;
+  paragraph: number;
+  summary: string;
+  tag: TagNode[];
+  channel: ChannelInfoProps;
+  createdAt: string;
+  updatedAt: string;
+  hit: number;
+  like: number;
 }
 
 interface IWidgetChapterCard {
@@ -29,8 +31,8 @@ interface IWidgetChapterCard {
 }
 
 const Widget = ({ data }: IWidgetChapterCard) => {
-  const path = JSON.parse(data.Path);
-  const tags = data.Tag;
+  const path = JSON.parse(data.path);
+  const tags = data.tag;
   return (
     <>
       <Row>
@@ -38,9 +40,14 @@ const Widget = ({ data }: IWidgetChapterCard) => {
           <Row>
             <Col span={16}>
               <Title level={5}>
-                <Link>{data.Title}</Link>
+                <Link
+                  to={`/article/chapter/${data.book}-${data.paragraph}_${data.channel.channelId}`}
+                  target="_blank"
+                >
+                  {data.title}
+                </Link>
               </Title>
-              <Text type="secondary">{data.PaliTitle}</Text>
+              <Text type="secondary">{data.paliTitle}</Text>
               <TocPath data={path} />
             </Col>
             <Col span={8}>进度条</Col>
@@ -54,7 +61,7 @@ const Widget = ({ data }: IWidgetChapterCard) => {
                   symbol: "more",
                 }}
               >
-                {data.Summary}
+                {data.summary}
               </Paragraph>
             </Col>
           </Row>
@@ -63,10 +70,10 @@ const Widget = ({ data }: IWidgetChapterCard) => {
               <TagArea data={tags} />
             </Col>
             <Col span={5}>
-              <ChannelListItem data={data.Channel} />
+              <ChannelListItem data={data.channel} />
             </Col>
             <Col span={3}>
-              <TimeShow time={data.UpdatedAt} title="UpdatedAt" />
+              <TimeShow time={data.updatedAt} title="UpdatedAt" />
             </Col>
           </Row>
         </Col>

+ 20 - 19
dashboard/src/components/corpus/ChapterFilter.tsx

@@ -1,29 +1,30 @@
 import { Layout, Row, Col } from "antd";
 import { Button } from "antd";
+
 import ChapterFilterType from "./ChapterFilterType";
 import ChapterFilterLang from "./ChapterFilterLang";
 import ChapterFilterProgress from "./ChapterFilterProgress";
 
 const Widget = () => {
-	return (
-		<Layout>
-			<Row>
-				<Col>
-					<ChapterFilterType />
-				</Col>
-				<Col>
-					<ChapterFilterLang />
-				</Col>
-				<Col>
-					<ChapterFilterProgress />
-				</Col>
-				<Col>
-					<Button>Search</Button>
-					<Button>Reset</Button>
-				</Col>
-			</Row>
-		</Layout>
-	);
+  return (
+    <Layout>
+      <Row>
+        <Col>
+          <ChapterFilterType />
+        </Col>
+        <Col>
+          <ChapterFilterLang />
+        </Col>
+        <Col>
+          <ChapterFilterProgress />
+        </Col>
+        <Col>
+          <Button>Search</Button>
+          <Button>Reset</Button>
+        </Col>
+      </Row>
+    </Layout>
+  );
 };
 
 export default Widget;

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

@@ -1,6 +1,7 @@
 import { Col, Progress, Row, Space, Tabs } from "antd";
 import { Typography } from "antd";
 import { LikeOutlined, EyeOutlined } from "@ant-design/icons";
+
 import { ChannelInfoProps } from "../api/Channel";
 import ChannelListItem from "../channel/ChannelListItem";
 import TimeShow from "../general/TimeShow";

+ 30 - 27
dashboard/src/components/corpus/ChapterList.tsx

@@ -1,10 +1,11 @@
 import { useState, useEffect } from "react";
 import { List } from "antd";
+
+import { get } from "../../request";
+import { IChapterData, IChapterListResponse } from "../api/Corpus";
 import ChapterCard from "./ChapterCard";
 import type { ChapterData } from "./ChapterCard";
 import type { ChannelFilterProps } from "../channel/ChannelList";
-import { IChapterData, IChapterListResponse } from "../api/Corpus";
-import { get } from "../../request";
 
 const defaultChannelFilterProps: ChannelFilterProps = {
   chapterProgress: 0.9,
@@ -33,31 +34,33 @@ const Widget = ({
     const strTags = tags.length > 0 ? "&tags=" + tags.join() : "";
     get<IChapterListResponse>(`/v2/progress?view=chapter${strTags}`).then(
       (json) => {
-        console.log("ajax", json);
-        let newTree = json.data.rows.map((item: IChapterData) => {
-          return {
-            Title: item.title,
-            PaliTitle: item.toc,
-            Path: item.path,
-            Book: item.book,
-            Paragraph: item.para,
-            Summary: item.summary,
-            Tag: item.tags,
-            Channel: {
-              channelName: item.channel.name,
-              channelId: "",
-              channelType: "translation",
-              studioName: item.channel.name,
-              studioId: item.channel.owner_uid,
-              studioType: "",
-            },
-            CreatedAt: item.created_at,
-            UpdatedAt: item.updated_at,
-            Hit: item.view,
-            Like: item.like,
-            ChannelInfo: "string",
-          };
-        });
+        console.log("chapter list ajax", json);
+        let newTree: ChapterData[] = json.data.rows.map(
+          (item: IChapterData) => {
+            return {
+              title: item.title,
+              paliTitle: item.toc,
+              path: item.path,
+              book: item.book,
+              paragraph: item.para,
+              summary: item.summary,
+              tag: item.tags,
+              channel: {
+                channelName: item.channel.name,
+                channelId: item.channel_id,
+                channelType: "translation",
+                studioName: item.channel.name,
+                studioId: item.channel.owner_uid,
+                studioType: "",
+              },
+              createdAt: item.created_at,
+              updatedAt: item.updated_at,
+              hit: item.view,
+              like: item.like,
+              channelInfo: "string",
+            };
+          }
+        );
         setTableData(newTree);
       }
     );

+ 50 - 49
dashboard/src/components/corpus/ChapterTagList.tsx

@@ -1,64 +1,65 @@
 import { message, Tag, Button } from "antd";
 import { useState, useEffect } from "react";
+
 import { get } from "../../request";
 import { IApiChapterTag, IApiResponseChapterTagList } from "../api/Corpus";
 
 interface ITagData {
-	title: string;
-	key: string;
+  title: string;
+  key: string;
 }
 interface IWidgetChapterTagList {
-	max?: number;
-	onTagClick: Function;
+  max?: number;
+  onTagClick: Function;
 }
 const Widget = (prop: IWidgetChapterTagList) => {
-	const defaultData: ITagData[] = [];
-	const [tableData, setTableData] = useState(defaultData);
+  const defaultData: ITagData[] = [];
+  const [tableData, setTableData] = useState(defaultData);
 
-	useEffect(() => {
-		console.log("useEffect");
-		fetchData();
-	}, []);
+  useEffect(() => {
+    console.log("useEffect");
+    fetchData();
+  }, []);
 
-	function fetchData() {
-		get(`/v2/progress?view=chapter-tag`)
-			.then((response) => {
-				const json = response as unknown as IApiResponseChapterTagList;
-				const tags: IApiChapterTag[] = json.data.rows;
-				let newTags: ITagData[] = tags.map((item) => {
-					return {
-						key: item.name,
-						title: `${item.name}(${item.count})`,
-					};
-				});
-				setTableData(newTags);
-			})
-			.catch((error) => {
-				message.error(error);
-			});
-	}
-	let iTag = prop.max ? prop.max : tableData.length;
-	if (iTag > tableData.length) {
-		iTag = tableData.length;
-	}
-	return (
-		<>
-			{tableData.map((item, id) => {
-				return (
-					<Tag
-						key={id}
-						onClick={() => {
-							if (typeof prop.onTagClick !== "undefined") {
-								prop.onTagClick(item.key);
-							}
-						}}
-					>
-						<Button type="link">{item.title}</Button>
-					</Tag>
-				);
-			})}
-		</>
-	);
+  function fetchData() {
+    get(`/v2/progress?view=chapter-tag`)
+      .then((response) => {
+        const json = response as unknown as IApiResponseChapterTagList;
+        const tags: IApiChapterTag[] = json.data.rows;
+        let newTags: ITagData[] = tags.map((item) => {
+          return {
+            key: item.name,
+            title: `${item.name}(${item.count})`,
+          };
+        });
+        setTableData(newTags);
+      })
+      .catch((error) => {
+        message.error(error);
+      });
+  }
+  let iTag = prop.max ? prop.max : tableData.length;
+  if (iTag > tableData.length) {
+    iTag = tableData.length;
+  }
+  return (
+    <>
+      {tableData.map((item, id) => {
+        return (
+          <Tag
+            key={id}
+            onClick={() => {
+              if (typeof prop.onTagClick !== "undefined") {
+                prop.onTagClick(item.key);
+              }
+            }}
+          >
+            <Button type="link">{item.title}</Button>
+          </Tag>
+        );
+      })}
+    </>
+  );
 };
 
 export default Widget;

+ 51 - 50
dashboard/src/components/corpus/PaliChapterCard.tsx

@@ -1,67 +1,68 @@
 import { Row, Col } from "antd";
 import { Typography } from "antd";
+
 import TocPath from "./TocPath";
 
 const { Title, Link } = Typography;
 
 export interface IPaliChapterData {
-	Title: string;
-	PaliTitle: string;
-	Path: string;
-	Book: number;
-	Paragraph: number;
+  Title: string;
+  PaliTitle: string;
+  Path: string;
+  Book: number;
+  Paragraph: number;
 }
 
 interface IWidgetPaliChapterCard {
-	data: IPaliChapterData;
-	onTitleClick?: Function;
+  data: IPaliChapterData;
+  onTitleClick?: Function;
 }
 
 const Widget = (prop: IWidgetPaliChapterCard) => {
-	const path = JSON.parse(prop.data.Path);
+  const path = JSON.parse(prop.data.Path);
 
-	return (
-		<>
-			<Row>
-				<Col span={3}>封面</Col>
-				<Col span={21}>
-					<Row>
-						<Col span={16}>
-							<Row>
-								<Col>
-									<Title
-										level={5}
-										onClick={(e) => {
-											if (typeof prop.onTitleClick !== "undefined") {
-												prop.onTitleClick(e);
-											}
-										}}
-									>
-										<Link>{prop.data.Title}</Link>
-									</Title>
-								</Col>
-							</Row>
-							<Row>
-								<Col>{prop.data.PaliTitle}</Col>
-							</Row>
-							<Row>
-								<Col>
-									<TocPath data={path} />
-								</Col>
-							</Row>
-						</Col>
-						<Col span={8}>进度条</Col>
-					</Row>
-					<Row>
-						<Col></Col>
-					</Row>
-					<Row>
-						<Col span={16}></Col>
-					</Row>
-				</Col>
-			</Row>
-		</>
-	);
+  return (
+    <>
+      <Row>
+        <Col span={3}>封面</Col>
+        <Col span={21}>
+          <Row>
+            <Col span={16}>
+              <Row>
+                <Col>
+                  <Title
+                    level={5}
+                    onClick={(e) => {
+                      if (typeof prop.onTitleClick !== "undefined") {
+                        prop.onTitleClick(e);
+                      }
+                    }}
+                  >
+                    <Link>{prop.data.Title}</Link>
+                  </Title>
+                </Col>
+              </Row>
+              <Row>
+                <Col>{prop.data.PaliTitle}</Col>
+              </Row>
+              <Row>
+                <Col>
+                  <TocPath data={path} />
+                </Col>
+              </Row>
+            </Col>
+            <Col span={8}>进度条</Col>
+          </Row>
+          <Row>
+            <Col></Col>
+          </Row>
+          <Row>
+            <Col span={16}></Col>
+          </Row>
+        </Col>
+      </Row>
+    </>
+  );
 };
 
 export default Widget;

+ 1 - 0
dashboard/src/components/corpus/PaliChapterChannelList.tsx

@@ -1,4 +1,5 @@
 import { useState, useEffect } from "react";
+
 import { get } from "../../request";
 import { IApiResponseChapterChannelList } from "../api/Corpus";
 import { IParagraph } from "./BookViewer";

+ 3 - 2
dashboard/src/components/corpus/PaliChapterHead.tsx

@@ -1,10 +1,11 @@
 import { useState, useEffect } from "react";
 import { message } from "antd";
+
+import { IApiResponsePaliChapter } from "../api/Corpus";
+import { get } from "../../request";
 import ChapterHead, { IChapterInfo } from "./ChapterHead";
 import { IParagraph } from "./BookViewer";
 import TocPath, { ITocPathNode } from "./TocPath";
-import { IApiResponsePaliChapter } from "../api/Corpus";
-import { get } from "../../request";
 
 interface IWidgetPaliChapterHead {
   para: IParagraph;

+ 27 - 26
dashboard/src/components/corpus/PaliChapterList.tsx

@@ -1,38 +1,39 @@
 import { List } from "antd";
+
 import PaliChapterCard, { IPaliChapterData } from "./PaliChapterCard";
 
 interface IWidgetPaliChapterList {
-	data: IPaliChapterData[];
-	onChapterClick?: Function;
+  data: IPaliChapterData[];
+  onChapterClick?: Function;
 }
 
 export interface IChapterClickEvent {
-	para: IPaliChapterData;
-	event: React.MouseEvent<HTMLDivElement, MouseEvent>;
+  para: IPaliChapterData;
+  event: React.MouseEvent<HTMLDivElement, MouseEvent>;
 }
 const Widget = (prop: IWidgetPaliChapterList) => {
-	return (
-		<List
-			itemLayout="vertical"
-			size="large"
-			dataSource={prop.data}
-			renderItem={(item) => (
-				<List.Item>
-					<PaliChapterCard
-						onTitleClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
-							if (typeof prop.onChapterClick !== "undefined") {
-								prop.onChapterClick({
-									para: item,
-									event: e,
-								});
-							}
-						}}
-						data={item}
-					/>
-				</List.Item>
-			)}
-		/>
-	);
+  return (
+    <List
+      itemLayout="vertical"
+      size="large"
+      dataSource={prop.data}
+      renderItem={(item) => (
+        <List.Item>
+          <PaliChapterCard
+            onTitleClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
+              if (typeof prop.onChapterClick !== "undefined") {
+                prop.onChapterClick({
+                  para: item,
+                  event: e,
+                });
+              }
+            }}
+            data={item}
+          />
+        </List.Item>
+      )}
+    />
+  );
 };
 
 export default Widget;

+ 1 - 0
dashboard/src/components/corpus/PaliChapterListByPara.tsx

@@ -1,4 +1,5 @@
 import { useState, useEffect } from "react";
+
 import { get } from "../../request";
 import { IApiResponsePaliChapterList } from "../api/Corpus";
 import { IParagraph } from "./BookViewer";

+ 1 - 0
dashboard/src/components/corpus/PaliChapterListByTag.tsx

@@ -1,4 +1,5 @@
 import { useState, useEffect } from "react";
+
 import { get } from "../../request";
 import { IApiResponsePaliChapterList } from "../api/Corpus";
 import { IPaliChapterData } from "./PaliChapterCard";

+ 18 - 18
dashboard/src/components/dict/Confidence.tsx

@@ -1,27 +1,27 @@
+import { useIntl } from "react-intl";
 import { ProFormSlider } from "@ant-design/pro-components";
 import type { SliderMarks } from "antd/es/slider";
-import { useIntl } from "react-intl";
 
 type IWidgetConfidence = {
-	defaultValue?: number;
+  defaultValue?: number;
 };
 const Widget = ({ defaultValue = 75 }: IWidgetConfidence) => {
-	const intl = useIntl();
-	const marks: SliderMarks = {
-		0: intl.formatMessage({ id: "forms.fields.confidence.0.label" }),
-		25: intl.formatMessage({ id: "forms.fields.confidence.25.label" }),
-		50: intl.formatMessage({ id: "forms.fields.confidence.50.label" }),
-		75: intl.formatMessage({ id: "forms.fields.confidence.75.label" }),
-		100: intl.formatMessage({ id: "forms.fields.confidence.100.label" }),
-	};
-	return (
-		<ProFormSlider
-			name="confidence"
-			label={intl.formatMessage({ id: "forms.fields.confidence.label" })}
-			width="xl"
-			marks={marks}
-		/>
-	);
+  const intl = useIntl();
+  const marks: SliderMarks = {
+    0: intl.formatMessage({ id: "forms.fields.confidence.0.label" }),
+    25: intl.formatMessage({ id: "forms.fields.confidence.25.label" }),
+    50: intl.formatMessage({ id: "forms.fields.confidence.50.label" }),
+    75: intl.formatMessage({ id: "forms.fields.confidence.75.label" }),
+    100: intl.formatMessage({ id: "forms.fields.confidence.100.label" }),
+  };
+  return (
+    <ProFormSlider
+      name="confidence"
+      label={intl.formatMessage({ id: "forms.fields.confidence.label" })}
+      width="xl"
+      marks={marks}
+    />
+  );
 };
 
 export default Widget;

+ 2 - 2
dashboard/src/components/dict/DictComponent.tsx

@@ -2,11 +2,11 @@ import { useState, useEffect } from "react";
 import { Affix, Col, Row } from "antd";
 import { Input } from "antd";
 
-import DictSearch from "./DictSearch";
-
 import { useAppSelector } from "../../hooks";
 import { message } from "../../reducers/command";
 
+import DictSearch from "./DictSearch";
+
 const { Search } = Input;
 
 export interface IWidgetDict {

+ 28 - 28
dashboard/src/components/dict/DictContent.tsx

@@ -1,46 +1,46 @@
 import { Col, Row } from "antd";
 
-import WordCard from "./WordCard";
-import CaseList from "./CaseList";
-import DictList from "./DictList";
-
 import type { IAnchorData } from "./DictList";
 import type { IWidgetWordCardData } from "./WordCard";
 import type { ICaseListData } from "./CaseList";
 
+import WordCard from "./WordCard";
+import CaseList from "./CaseList";
+import DictList from "./DictList";
+
 export interface IWidgetDictContentData {
-	dictlist: IAnchorData[];
-	words: IWidgetWordCardData[];
-	caselist: ICaseListData[];
+  dictlist: IAnchorData[];
+  words: IWidgetWordCardData[];
+  caselist: ICaseListData[];
 }
 export interface IApiDictContentData {
-	ok: boolean;
-	message: string;
-	data: IWidgetDictContentData;
+  ok: boolean;
+  message: string;
+  data: IWidgetDictContentData;
 }
 
 interface IWidgetDictContent {
-	data: IWidgetDictContentData;
+  data: IWidgetDictContentData;
 }
 
 const Widget = (prop: IWidgetDictContent) => {
-	return (
-		<>
-			<Row>
-				<Col flex="200px">
-					<DictList data={prop.data.dictlist} />
-				</Col>
-				<Col flex="760px">
-					{prop.data.words.map((it, id) => {
-						return <WordCard key={id} data={it} />;
-					})}
-				</Col>
-				<Col flex="200px">
-					<CaseList data={prop.data.caselist} />
-				</Col>
-			</Row>
-		</>
-	);
+  return (
+    <>
+      <Row>
+        <Col flex="200px">
+          <DictList data={prop.data.dictlist} />
+        </Col>
+        <Col flex="760px">
+          {prop.data.words.map((it, id) => {
+            return <WordCard key={id} data={it} />;
+          })}
+        </Col>
+        <Col flex="200px">
+          <CaseList data={prop.data.caselist} />
+        </Col>
+      </Row>
+    </>
+  );
 };
 
 export default Widget;

+ 29 - 32
dashboard/src/components/dict/DictCreate.tsx

@@ -1,31 +1,30 @@
-import { ProForm } from "@ant-design/pro-components";
-
 import { useIntl } from "react-intl";
+import { ProForm } from "@ant-design/pro-components";
 import { message } from "antd";
 
 import DictEditInner from "./DictEditInner";
 
 export interface IDictFormData {
-	id: number;
-	word: string;
-	type: string;
-	grammar: string;
-	parent: string;
-	meaning: string;
-	note: string;
-	factors: string;
-	factormeaning: string;
-	lang: string;
-	confidence: number;
+  id: number;
+  word: string;
+  type: string;
+  grammar: string;
+  parent: string;
+  meaning: string;
+  note: string;
+  factors: string;
+  factormeaning: string;
+  lang: string;
+  confidence: number;
 }
 
 type IWidgetDictCreate = {
-	studio: string;
-	word?: string;
+  studio: string;
+  word?: string;
 };
 const Widget = (prop: IWidgetDictCreate) => {
-	const intl = useIntl();
-	/*
+  const intl = useIntl();
+  /*
 	const onLangChange = (value: string) => {
 		console.log(`selected ${value}`);
 	};
@@ -34,21 +33,19 @@ const Widget = (prop: IWidgetDictCreate) => {
 		console.log("search:", value);
 	};
 	*/
-	return (
-		<>
-			<ProForm<IDictFormData>
-				onFinish={async (values: IDictFormData) => {
-					// TODO
-					console.log(values);
-					message.success(
-						intl.formatMessage({ id: "flashes.success" })
-					);
-				}}
-			>
-				<DictEditInner word={prop.word} />
-			</ProForm>
-		</>
-	);
+  return (
+    <>
+      <ProForm<IDictFormData>
+        onFinish={async (values: IDictFormData) => {
+          // TODO
+          console.log(values);
+          message.success(intl.formatMessage({ id: "flashes.success" }));
+        }}
+      >
+        <DictEditInner word={prop.word} />
+      </ProForm>
+    </>
+  );
 };
 
 export default Widget;

+ 5 - 5
dashboard/src/components/dict/DictEdit.tsx

@@ -1,13 +1,13 @@
-import { ProForm } from "@ant-design/pro-components";
-
+import { useEffect } from "react";
 import { useIntl } from "react-intl";
+import { ProForm } from "@ant-design/pro-components";
 import { message } from "antd";
 
-import DictEditInner from "./DictEditInner";
-import { IDictFormData } from "./DictCreate";
 import { IApiResponseDict, IDictDataRequest } from "../api/Dict";
 import { get, put } from "../../request";
-import { useEffect } from "react";
+
+import DictEditInner from "./DictEditInner";
+import { IDictFormData } from "./DictCreate";
 
 type IWidgetDictEdit = {
   wordId: number;

+ 1 - 2
dashboard/src/components/dict/DictEditInner.tsx

@@ -1,11 +1,10 @@
+import { useIntl } from "react-intl";
 import {
   ProForm,
   ProFormText,
   ProFormTextArea,
 } from "@ant-design/pro-components";
 
-import { useIntl } from "react-intl";
-
 import LangSelect from "../general/LangSelect";
 import SelectCase from "./SelectCase";
 import Confidence from "./Confidence";

+ 20 - 18
dashboard/src/components/dict/DictList.tsx

@@ -1,29 +1,31 @@
 import { Anchor } from "antd";
+
 const { Link } = Anchor;
+
 export interface IAnchorData {
-	href: string;
-	title: string;
-	children?: IAnchorData[];
+  href: string;
+  title: string;
+  children?: IAnchorData[];
 }
 interface IWidgetDictList {
-	data: IAnchorData[];
+  data: IAnchorData[];
 }
 const Widget = (prop: IWidgetDictList) => {
-	function GetLink(anchors: IAnchorData[]) {
-		return anchors.map((it, id) => {
-			return (
-				<Link key={id} href={it.href} title={it.title}>
-					{it.children ? GetLink(it.children) : ""}
-				</Link>
-			);
-		});
-	}
+  function GetLink(anchors: IAnchorData[]) {
+    return anchors.map((it, id) => {
+      return (
+        <Link key={id} href={it.href} title={it.title}>
+          {it.children ? GetLink(it.children) : ""}
+        </Link>
+      );
+    });
+  }
 
-	return (
-		<>
-			<Anchor offsetTop={50}>{GetLink(prop.data)}</Anchor>
-		</>
-	);
+  return (
+    <>
+      <Anchor offsetTop={50}>{GetLink(prop.data)}</Anchor>
+    </>
+  );
 };
 
 export default Widget;

+ 31 - 29
dashboard/src/components/dict/DictSearch.tsx

@@ -1,43 +1,45 @@
 import { useState, useEffect } from "react";
-import DictContent from "./DictContent";
+
 import type {
-	IWidgetDictContentData,
-	IApiDictContentData,
+  IWidgetDictContentData,
+  IApiDictContentData,
 } from "./DictContent";
 import { get } from "../../request";
 
+import DictContent from "./DictContent";
+
 interface IWidgetDictSearch {
-	word: string | undefined;
+  word: string | undefined;
 }
 
 const Widget = (prop: IWidgetDictSearch) => {
-	const defaultData: IWidgetDictContentData = {
-		dictlist: [],
-		words: [],
-		caselist: [],
-	};
-	const [tableData, setTableData] = useState(defaultData);
+  const defaultData: IWidgetDictContentData = {
+    dictlist: [],
+    words: [],
+    caselist: [],
+  };
+  const [tableData, setTableData] = useState(defaultData);
 
-	useEffect(() => {
-		console.log("useEffect");
-		const url = `/v2/dict?word=${prop.word}`;
-		console.log("url", url);
-		get(url)
-			.then((response) => {
-				const json = response as unknown as IApiDictContentData;
-				console.log("data", json);
-				setTableData(json.data);
-			})
-			.catch((error) => {
-				console.error(error);
-			});
-	}, [prop.word, setTableData]);
+  useEffect(() => {
+    console.log("useEffect");
+    const url = `/v2/dict?word=${prop.word}`;
+    console.log("url", url);
+    get(url)
+      .then((response) => {
+        const json = response as unknown as IApiDictContentData;
+        console.log("data", json);
+        setTableData(json.data);
+      })
+      .catch((error) => {
+        console.error(error);
+      });
+  }, [prop.word, setTableData]);
 
-	return (
-		<>
-			<DictContent data={tableData} />
-		</>
-	);
+  return (
+    <>
+      <DictContent data={tableData} />
+    </>
+  );
 };
 
 export default Widget;

+ 38 - 39
dashboard/src/components/dict/GrammarPop.tsx

@@ -1,51 +1,50 @@
-/* eslint-disable jsx-a11y/anchor-is-valid */
 import { useState } from "react";
 import { Popover } from "antd";
 import { ProCard } from "@ant-design/pro-components";
+import MDEditor from "@uiw/react-md-editor";
 
 import { ApiGetText } from "../../utils";
-import MDEditor from "@uiw/react-md-editor";
 
 interface IWidgetGrammarPop {
-	text: string;
-	gid: string;
+  text: string;
+  gid: string;
 }
 const Widget = (prop: IWidgetGrammarPop) => {
-	const [guide, setGuide] = useState("Loading");
-	const grammarProfix = "guide-grammar-";
-	const handleMouseMouseEnter = () => {
-		console.log("mouseenter", prop.gid);
-		//sessionStorage缓存
-		const value = sessionStorage.getItem(grammarProfix + prop.gid);
-		if (value === null) {
-			fetchData(prop.gid);
-		} else {
-			const sGuide: string = value ? value : "";
-			setGuide(sGuide);
-		}
-	};
-	const userCard = (
-		<>
-			<ProCard style={{ maxWidth: 500, minWidth: 300, margin: 0 }}>
-				<MDEditor.Markdown source={guide} />
-			</ProCard>
-		</>
-	);
-	function fetchData(key: string) {
-		const url = `/guide/zh-cn/${key}`;
-		ApiGetText(url).then((response: String) => {
-			const text = response as unknown as string;
-			sessionStorage.setItem(grammarProfix + key, text);
-			setGuide(text);
-		});
-	}
-	return (
-		<Popover content={userCard} placement="bottom">
-			<a href="#" onMouseEnter={handleMouseMouseEnter}>
-				{prop.text}
-			</a>
-		</Popover>
-	);
+  const [guide, setGuide] = useState("Loading");
+  const grammarProfix = "guide-grammar-";
+  const handleMouseMouseEnter = () => {
+    console.log("mouseenter", prop.gid);
+    //sessionStorage缓存
+    const value = sessionStorage.getItem(grammarProfix + prop.gid);
+    if (value === null) {
+      fetchData(prop.gid);
+    } else {
+      const sGuide: string = value ? value : "";
+      setGuide(sGuide);
+    }
+  };
+  const userCard = (
+    <>
+      <ProCard style={{ maxWidth: 500, minWidth: 300, margin: 0 }}>
+        <MDEditor.Markdown source={guide} />
+      </ProCard>
+    </>
+  );
+  function fetchData(key: string) {
+    const url = `/guide/zh-cn/${key}`;
+    ApiGetText(url).then((response: String) => {
+      const text = response as unknown as string;
+      sessionStorage.setItem(grammarProfix + key, text);
+      setGuide(text);
+    });
+  }
+  return (
+    <Popover content={userCard} placement="bottom">
+      <a href="#" onMouseEnter={handleMouseMouseEnter}>
+        {prop.text}
+      </a>
+    </Popover>
+  );
 };
 
 export default Widget;

+ 1 - 1
dashboard/src/components/dict/SelectCase.tsx

@@ -1,5 +1,5 @@
-import { Cascader } from "antd";
 import { useIntl } from "react-intl";
+import { Cascader } from "antd";
 
 interface CascaderOption {
   value: string | number;

+ 40 - 40
dashboard/src/components/dict/WordCard.tsx

@@ -1,56 +1,56 @@
 /* eslint-disable jsx-a11y/anchor-is-valid */
 import { Typography } from "antd";
-import IWidgetGrammarPop from "./GrammarPop";
-import WordCardByDict from "./WordCardByDict";
 
 import type { IWordByDict } from "./WordCardByDict";
+import IWidgetGrammarPop from "./GrammarPop";
+import WordCardByDict from "./WordCardByDict";
 
 const { Title, Text } = Typography;
 
 export interface IWidgetWordCardData {
-	word: string;
-	factors: string;
-	parents: string;
-	case: string[];
-	anchor: string;
-	dict: IWordByDict[];
+  word: string;
+  factors: string;
+  parents: string;
+  case: string[];
+  anchor: string;
+  dict: IWordByDict[];
 }
 interface IWidgetWordCard {
-	data: IWidgetWordCardData;
+  data: IWidgetWordCardData;
 }
 const Widget = (prop: IWidgetWordCard) => {
-	const caseList = prop.data.case.map((element) => {
-		return element.split("|").map((it, id) => {
-			if (it.slice(0, 1) === "@") {
-				const [showText, keyText] = it.slice(1).split("-");
-				return <IWidgetGrammarPop key={id} gid={keyText} text={showText} />;
-			} else {
-				return <span key={id * 200}>{it}</span>;
-			}
-		});
-	});
-	return (
-		<>
-			<Title level={4} id={prop.data.anchor}>
-				{prop.data.word}
-			</Title>
+  const caseList = prop.data.case.map((element) => {
+    return element.split("|").map((it, id) => {
+      if (it.slice(0, 1) === "@") {
+        const [showText, keyText] = it.slice(1).split("-");
+        return <IWidgetGrammarPop key={id} gid={keyText} text={showText} />;
+      } else {
+        return <span key={id * 200}>{it}</span>;
+      }
+    });
+  });
+  return (
+    <>
+      <Title level={4} id={prop.data.anchor}>
+        {prop.data.word}
+      </Title>
 
-			<div>
-				<Text>{prop.data.factors}</Text>
-			</div>
-			<div>
-				<Text>{prop.data.parents}</Text>
-			</div>
-			<div>
-				<Text>{caseList}</Text>
-			</div>
-			<div>
-				{prop.data.dict.map((it, id) => {
-					return <WordCardByDict key={id} data={it} />;
-				})}
-			</div>
-		</>
-	);
+      <div>
+        <Text>{prop.data.factors}</Text>
+      </div>
+      <div>
+        <Text>{prop.data.parents}</Text>
+      </div>
+      <div>
+        <Text>{caseList}</Text>
+      </div>
+      <div>
+        {prop.data.dict.map((it, id) => {
+          return <WordCardByDict key={id} data={it} />;
+        })}
+      </div>
+    </>
+  );
 };
 
 export default Widget;

+ 27 - 25
dashboard/src/components/dict/WordCardByDict.tsx

@@ -1,39 +1,41 @@
-/* eslint-disable jsx-a11y/anchor-is-valid */
 import { Card } from "antd";
 import { Typography } from "antd";
+
 import IWidgetGrammarPop from "./GrammarPop";
 
 const { Title, Text } = Typography;
 
 export interface IWordByDict {
-	dictname: string;
-	word: string;
-	note: string;
-	anchor: string;
+  dictname: string;
+  word: string;
+  note: string;
+  anchor: string;
 }
 interface IWidgetWordCardByDict {
-	data: IWordByDict;
+  data: IWordByDict;
 }
 const Widget = (prop: IWidgetWordCardByDict) => {
-	return (
-		<Card>
-			<Title level={5} id={prop.data.anchor}>
-				{prop.data.dictname}
-			</Title>
-			<div>
-				<Text>
-					{prop.data.note.split("|").map((it, id) => {
-						if (it.slice(0, 1) === "@") {
-							const [showText, keyText] = it.slice(1).split("-");
-							return <IWidgetGrammarPop key={id} gid={keyText} text={showText} />;
-						} else {
-							return <span key={id * 200}>{it}</span>;
-						}
-					})}
-				</Text>
-			</div>
-		</Card>
-	);
+  return (
+    <Card>
+      <Title level={5} id={prop.data.anchor}>
+        {prop.data.dictname}
+      </Title>
+      <div>
+        <Text>
+          {prop.data.note.split("|").map((it, id) => {
+            if (it.slice(0, 1) === "@") {
+              const [showText, keyText] = it.slice(1).split("-");
+              return (
+                <IWidgetGrammarPop key={id} gid={keyText} text={showText} />
+              );
+            } else {
+              return <span key={id * 200}>{it}</span>;
+            }
+          })}
+        </Text>
+      </div>
+    </Card>
+  );
 };
 
 export default Widget;

+ 1 - 1
dashboard/src/components/general/LangSelect.tsx

@@ -1,5 +1,5 @@
-import { ProFormSelect } from "@ant-design/pro-components";
 import { useIntl } from "react-intl";
+import { ProFormSelect } from "@ant-design/pro-components";
 
 const Widget = () => {
   const intl = useIntl();

+ 30 - 28
dashboard/src/components/group/GroupCreate.tsx

@@ -1,41 +1,43 @@
-import { ProForm, ProFormText } from "@ant-design/pro-components";
 import { useIntl } from "react-intl";
+import { ProForm, ProFormText } from "@ant-design/pro-components";
 import { message } from "antd";
 
 interface IFormData {
-	name: string;
+  name: string;
 }
 
 type IWidgetGroupCreate = {
-	studio: string | undefined;
+  studio: string | undefined;
 };
 const Widget = (param: IWidgetGroupCreate) => {
-	const intl = useIntl();
+  const intl = useIntl();
 
-	return (
-		<ProForm<IFormData>
-			onFinish={async (values: IFormData) => {
-				// TODO
-				console.log(values);
-				message.success(intl.formatMessage({ id: "flashes.success" }));
-			}}
-		>
-			<ProForm.Group>
-				<ProFormText
-					width="md"
-					name="name"
-					required
-					label={intl.formatMessage({ id: "channel.name" })}
-					rules={[
-						{
-							required: true,
-							message: intl.formatMessage({ id: "channel.create.message.noname" }),
-						},
-					]}
-				/>
-			</ProForm.Group>
-		</ProForm>
-	);
+  return (
+    <ProForm<IFormData>
+      onFinish={async (values: IFormData) => {
+        // TODO
+        console.log(values);
+        message.success(intl.formatMessage({ id: "flashes.success" }));
+      }}
+    >
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="name"
+          required
+          label={intl.formatMessage({ id: "channel.name" })}
+          rules={[
+            {
+              required: true,
+              message: intl.formatMessage({
+                id: "channel.create.message.noname",
+              }),
+            },
+          ]}
+        />
+      </ProForm.Group>
+    </ProForm>
+  );
 };
 
 export default Widget;

+ 92 - 87
dashboard/src/components/group/GroupFile.tsx

@@ -2,102 +2,107 @@ import { useIntl } from "react-intl";
 import { useState } from "react";
 import { ProList } from "@ant-design/pro-components";
 import { Space, Tag, Button, Layout } from "antd";
+
 const { Content } = Layout;
 
 const defaultData = [
-	{
-		id: "1",
-		name: "庄春江工作站",
-		tag: [{ title: "可编辑", color: "success" }],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-		description: "IAPT|2022-1-3",
-	},
-	{
-		id: "2",
-		name: "元亨寺·CBETA",
-		tag: [{ title: "可编辑", color: "success" }],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-		description: "我是一条测试的描述",
-	},
-	{
-		id: "3",
-		name: "叶均居士",
-		tag: [{ title: "只读", color: "default" }],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-		description: "我是一条测试的描述",
-	},
-	{
-		id: "4",
-		name: "玛欣德尊者",
-		tag: [{ title: "只读", color: "default" }],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-		description: "我是一条测试的描述",
-	},
+  {
+    id: "1",
+    name: "庄春江工作站",
+    tag: [{ title: "可编辑", color: "success" }],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+    description: "IAPT|2022-1-3",
+  },
+  {
+    id: "2",
+    name: "元亨寺·CBETA",
+    tag: [{ title: "可编辑", color: "success" }],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+    description: "我是一条测试的描述",
+  },
+  {
+    id: "3",
+    name: "叶均居士",
+    tag: [{ title: "只读", color: "default" }],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+    description: "我是一条测试的描述",
+  },
+  {
+    id: "4",
+    name: "玛欣德尊者",
+    tag: [{ title: "只读", color: "default" }],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+    description: "我是一条测试的描述",
+  },
 ];
 type DataItem = typeof defaultData[number];
 type IWidgetGroupFile = {
-	groupid?: string;
+  groupid?: string;
 };
 const Widget = ({ groupid = "" }: IWidgetGroupFile) => {
-	const intl = useIntl(); //i18n
-	const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
+  const intl = useIntl(); //i18n
+  const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
 
-	return (
-		<Content>
-			<Space>{groupid}</Space>
-			<ProList<DataItem>
-				rowKey="id"
-				headerTitle={intl.formatMessage({ id: "group.files" })}
-				dataSource={dataSource}
-				showActions="hover"
-				onDataSourceChange={setDataSource}
-				metas={{
-					title: {
-						dataIndex: "name",
-					},
-					avatar: {
-						dataIndex: "image",
-						editable: false,
-					},
-					description: {
-						dataIndex: "description",
-					},
-					content: {
-						dataIndex: "content",
-						editable: false,
-					},
-					subTitle: {
-						render: (text, row, index, action) => {
-							const showtag = row.tag.map((item, id) => {
-								return (
-									<Tag color={item.color} key={id}>
-										{item.title}
-									</Tag>
-								);
-							});
-							return <Space size={0}>{showtag}</Space>;
-						},
-					},
-					actions: {
-						render: (text, row, index, action) => [
-							<Button
-								onClick={() => {
-									action?.startEditable(row.id);
-								}}
-								key="link"
-							>
-								删除
-							</Button>,
-						],
-					},
-				}}
-				pagination={{
-					showQuickJumper: true,
-					showSizeChanger: true,
-				}}
-			/>
-		</Content>
-	);
+  return (
+    <Content>
+      <Space>{groupid}</Space>
+      <ProList<DataItem>
+        rowKey="id"
+        headerTitle={intl.formatMessage({ id: "group.files" })}
+        dataSource={dataSource}
+        showActions="hover"
+        onDataSourceChange={setDataSource}
+        metas={{
+          title: {
+            dataIndex: "name",
+          },
+          avatar: {
+            dataIndex: "image",
+            editable: false,
+          },
+          description: {
+            dataIndex: "description",
+          },
+          content: {
+            dataIndex: "content",
+            editable: false,
+          },
+          subTitle: {
+            render: (text, row, index, action) => {
+              const showtag = row.tag.map((item, id) => {
+                return (
+                  <Tag color={item.color} key={id}>
+                    {item.title}
+                  </Tag>
+                );
+              });
+              return <Space size={0}>{showtag}</Space>;
+            },
+          },
+          actions: {
+            render: (text, row, index, action) => [
+              <Button
+                onClick={() => {
+                  action?.startEditable(row.id);
+                }}
+                key="link"
+              >
+                删除
+              </Button>,
+            ],
+          },
+        }}
+        pagination={{
+          showQuickJumper: true,
+          showSizeChanger: true,
+        }}
+      />
+    </Content>
+  );
 };
 
 export default Widget;

+ 91 - 84
dashboard/src/components/group/GroupMember.tsx

@@ -2,99 +2,106 @@ import { useIntl } from "react-intl";
 import { useState } from "react";
 import { ProList } from "@ant-design/pro-components";
 import { Space, Tag, Button, Layout } from "antd";
+
 const { Content } = Layout;
 
 const defaultData = [
-	{
-		id: "1",
-		name: "小僧善巧",
-		tag: [{ title: "管理员", color: "success" }],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-	},
-	{
-		id: "2",
-		name: "无语",
-		tag: [{ title: "管理员", color: "success" }],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-	},
-	{
-		id: "3",
-		name: "慧欣",
-		tag: [],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-	},
-	{
-		id: "4",
-		name: "谭博文",
-		tag: [],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-	},
-	{
-		id: "4",
-		name: "豆沙猫",
-		tag: [],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-	},
-	{
-		id: "4",
-		name: "visuddhinanda",
-		tag: [],
-		image: "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
-	},
+  {
+    id: "1",
+    name: "小僧善巧",
+    tag: [{ title: "管理员", color: "success" }],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+  },
+  {
+    id: "2",
+    name: "无语",
+    tag: [{ title: "管理员", color: "success" }],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+  },
+  {
+    id: "3",
+    name: "慧欣",
+    tag: [],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+  },
+  {
+    id: "4",
+    name: "谭博文",
+    tag: [],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+  },
+  {
+    id: "4",
+    name: "豆沙猫",
+    tag: [],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+  },
+  {
+    id: "4",
+    name: "visuddhinanda",
+    tag: [],
+    image:
+      "https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg",
+  },
 ];
 type DataItem = typeof defaultData[number];
 type IWidgetGroupFile = {
-	groupid?: string;
+  groupid?: string;
 };
 const Widget = ({ groupid = "" }: IWidgetGroupFile) => {
-	const intl = useIntl(); //i18n
-	const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
+  const intl = useIntl(); //i18n
+  const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
 
-	return (
-		<Content>
-			<Space>{groupid}</Space>
-			<ProList<DataItem>
-				rowKey="id"
-				headerTitle={intl.formatMessage({ id: "group.member" })}
-				dataSource={dataSource}
-				showActions="hover"
-				onDataSourceChange={setDataSource}
-				metas={{
-					title: {
-						dataIndex: "name",
-					},
-					avatar: {
-						dataIndex: "image",
-						editable: false,
-					},
-					subTitle: {
-						render: (text, row, index, action) => {
-							const showtag = row.tag.map((item, id) => {
-								return (
-									<Tag color={item.color} key={id}>
-										{item.title}
-									</Tag>
-								);
-							});
-							return <Space size={0}>{showtag}</Space>;
-						},
-					},
-					actions: {
-						render: (text, row, index, action) => [
-							<Button
-								onClick={() => {
-									action?.startEditable(row.id);
-								}}
-								key="link"
-							>
-								删除
-							</Button>,
-						],
-					},
-				}}
-			/>
-		</Content>
-	);
+  return (
+    <Content>
+      <Space>{groupid}</Space>
+      <ProList<DataItem>
+        rowKey="id"
+        headerTitle={intl.formatMessage({ id: "group.member" })}
+        dataSource={dataSource}
+        showActions="hover"
+        onDataSourceChange={setDataSource}
+        metas={{
+          title: {
+            dataIndex: "name",
+          },
+          avatar: {
+            dataIndex: "image",
+            editable: false,
+          },
+          subTitle: {
+            render: (text, row, index, action) => {
+              const showtag = row.tag.map((item, id) => {
+                return (
+                  <Tag color={item.color} key={id}>
+                    {item.title}
+                  </Tag>
+                );
+              });
+              return <Space size={0}>{showtag}</Space>;
+            },
+          },
+          actions: {
+            render: (text, row, index, action) => [
+              <Button
+                onClick={() => {
+                  action?.startEditable(row.id);
+                }}
+                key="link"
+              >
+                删除
+              </Button>,
+            ],
+          },
+        }}
+      />
+    </Content>
+  );
 };
 
 export default Widget;

+ 0 - 0
dashboard/src/components/library/article/.txt


+ 0 - 0
dashboard/src/components/library/dict/.txt


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

@@ -3,11 +3,9 @@ import { ProForm, ProFormText } from "@ant-design/pro-components";
 import { message } from "antd";
 import { useNavigate } from "react-router-dom";
 
-import { setTitle } from "../../../reducers/layout";
-import { useAppSelector, useAppDispatch } from "../../../hooks";
+import { useAppDispatch } from "../../../hooks";
 import { IUser, signIn, TO_HOME } from "../../../reducers/current-user";
 import { get, post } from "../../../request";
-import store from "../../../store";
 
 interface IFormData {
   email: string;

+ 184 - 184
dashboard/src/components/studio/EditableTree.tsx

@@ -1,209 +1,209 @@
 import React, { useState } from "react";
+import { useEffect } from "react";
 import { Tree } from "antd";
 import type { DataNode, TreeProps } from "antd/es/tree";
-import { useEffect } from "react";
 
 type TreeNodeData = {
-	key: string;
-	title: string;
-	children: TreeNodeData[];
-	level: number;
+  key: string;
+  title: string;
+  children: TreeNodeData[];
+  level: number;
 };
 export type ListNodeData = {
-	key: string;
-	title: string;
-	level: number;
+  key: string;
+  title: string;
+  level: number;
 };
 
 var tocActivePath: TreeNodeData[] = [];
 function tocGetTreeData(articles: ListNodeData[], active = "") {
-	let treeData = [];
-
-	let treeParents = [];
-	let rootNode: TreeNodeData = {
-		key: "0",
-		title: "root",
-		level: 0,
-		children: [],
-	};
-	treeData.push(rootNode);
-	let lastInsNode: TreeNodeData = rootNode;
-
-	let iCurrLevel = 0;
-	for (let index = 0; index < articles.length; index++) {
-		const element = articles[index];
-
-		let newNode: TreeNodeData = {
-			key: element.key,
-			title: element.title,
-			children: [],
-			level: element.level,
-		};
-		/*
+  let treeData = [];
+
+  let treeParents = [];
+  let rootNode: TreeNodeData = {
+    key: "0",
+    title: "root",
+    level: 0,
+    children: [],
+  };
+  treeData.push(rootNode);
+  let lastInsNode: TreeNodeData = rootNode;
+
+  let iCurrLevel = 0;
+  for (let index = 0; index < articles.length; index++) {
+    const element = articles[index];
+
+    let newNode: TreeNodeData = {
+      key: element.key,
+      title: element.title,
+      children: [],
+      level: element.level,
+    };
+    /*
 		if (active == element.article) {
 			newNode["extraClasses"] = "active";
 		}
 */
-		if (newNode.level > iCurrLevel) {
-			//新的层级比较大,为上一个的子目录
-			treeParents.push(lastInsNode);
-			lastInsNode.children.push(newNode);
-		} else if (newNode.level === iCurrLevel) {
-			//目录层级相同,为平级
-			treeParents[treeParents.length - 1].children.push(newNode);
-		} else {
-			// 小于 挂在上一个层级
-			while (treeParents.length > 1) {
-				treeParents.pop();
-				if (treeParents[treeParents.length - 1].level < newNode.level) {
-					break;
-				}
-			}
-			treeParents[treeParents.length - 1].children.push(newNode);
-		}
-		lastInsNode = newNode;
-		iCurrLevel = newNode.level;
-
-		if (active === element.key) {
-			tocActivePath = [];
-			for (let index = 1; index < treeParents.length; index++) {
-				tocActivePath.push(treeParents[index]);
-			}
-		}
-	}
-	return treeData[0].children;
+    if (newNode.level > iCurrLevel) {
+      //新的层级比较大,为上一个的子目录
+      treeParents.push(lastInsNode);
+      lastInsNode.children.push(newNode);
+    } else if (newNode.level === iCurrLevel) {
+      //目录层级相同,为平级
+      treeParents[treeParents.length - 1].children.push(newNode);
+    } else {
+      // 小于 挂在上一个层级
+      while (treeParents.length > 1) {
+        treeParents.pop();
+        if (treeParents[treeParents.length - 1].level < newNode.level) {
+          break;
+        }
+      }
+      treeParents[treeParents.length - 1].children.push(newNode);
+    }
+    lastInsNode = newNode;
+    iCurrLevel = newNode.level;
+
+    if (active === element.key) {
+      tocActivePath = [];
+      for (let index = 1; index < treeParents.length; index++) {
+        tocActivePath.push(treeParents[index]);
+      }
+    }
+  }
+  return treeData[0].children;
 }
 
 function treeToList(treeNode: TreeNodeData[]): ListNodeData[] {
-	let iTocTreeCurrLevel = 1;
-
-	let arrTocTree: ListNodeData[] = [];
-
-	for (const iterator of treeNode) {
-		getTreeNodeData(iterator);
-	}
-
-	function getTreeNodeData(node: TreeNodeData) {
-		let children = 0;
-		if (typeof node.children != "undefined") {
-			children = node.children.length;
-		}
-		arrTocTree.push({
-			key: node.key,
-			title: node.title,
-			level: iTocTreeCurrLevel,
-		});
-		if (children > 0) {
-			iTocTreeCurrLevel++;
-			for (const iterator of node.children) {
-				getTreeNodeData(iterator);
-			}
-			iTocTreeCurrLevel--;
-		}
-	}
-
-	return arrTocTree;
+  let iTocTreeCurrLevel = 1;
+
+  let arrTocTree: ListNodeData[] = [];
+
+  for (const iterator of treeNode) {
+    getTreeNodeData(iterator);
+  }
+
+  function getTreeNodeData(node: TreeNodeData) {
+    let children = 0;
+    if (typeof node.children != "undefined") {
+      children = node.children.length;
+    }
+    arrTocTree.push({
+      key: node.key,
+      title: node.title,
+      level: iTocTreeCurrLevel,
+    });
+    if (children > 0) {
+      iTocTreeCurrLevel++;
+      for (const iterator of node.children) {
+        getTreeNodeData(iterator);
+      }
+      iTocTreeCurrLevel--;
+    }
+  }
+
+  return arrTocTree;
 }
 interface IWidgetEditableTree {
-	treeData: ListNodeData[];
-	onChange?: Function;
+  treeData: ListNodeData[];
+  onChange?: Function;
 }
 const Widget = (prop: IWidgetEditableTree) => {
-	const data = tocGetTreeData(prop.treeData);
-	console.log("treedata", data);
-	const [gData, setGData] = useState(data);
-	useEffect(() => {
-		const data = tocGetTreeData(prop.treeData);
-		setGData(data);
-	}, [prop]);
-
-	const onDragEnter: TreeProps["onDragEnter"] = (info) => {
-		console.log(info);
-		// expandedKeys 需要受控时设置
-		// setExpandedKeys(info.expandedKeys)
-	};
-
-	const onDrop: TreeProps["onDrop"] = (info) => {
-		console.log(info);
-		const dropKey = info.node.key;
-		const dragKey = info.dragNode.key;
-		const dropPos = info.node.pos.split("-");
-		const dropPosition =
-			info.dropPosition - Number(dropPos[dropPos.length - 1]);
-
-		const loop = (
-			data: DataNode[],
-			key: React.Key,
-			callback: (node: DataNode, i: number, data: DataNode[]) => void
-		) => {
-			for (let i = 0; i < data.length; i++) {
-				if (data[i].key === key) {
-					return callback(data[i], i, data);
-				}
-				if (data[i].children) {
-					loop(data[i].children!, key, callback);
-				}
-			}
-		};
-		const data = [...gData];
-
-		// Find dragObject
-		let dragObj: DataNode;
-		loop(data, dragKey, (item, index, arr) => {
-			arr.splice(index, 1);
-			dragObj = item;
-		});
-
-		if (!info.dropToGap) {
-			// Drop on the content
-			loop(data, dropKey, (item) => {
-				item.children = item.children || [];
-				// where to insert 示例添加到头部,可以是随意位置
-				item.children.unshift(dragObj);
-			});
-		} else if (
-			((info.node as any).props.children || []).length > 0 && // Has children
-			(info.node as any).props.expanded && // Is expanded
-			dropPosition === 1 // On the bottom gap
-		) {
-			loop(data, dropKey, (item) => {
-				item.children = item.children || [];
-				// where to insert 示例添加到头部,可以是随意位置
-				item.children.unshift(dragObj);
-				// in previous version, we use item.children.push(dragObj) to insert the
-				// item to the tail of the children
-			});
-		} else {
-			let ar: DataNode[] = [];
-			let i: number;
-			loop(data, dropKey, (_item, index, arr) => {
-				ar = arr;
-				i = index;
-			});
-			if (dropPosition === -1) {
-				ar.splice(i!, 0, dragObj!);
-			} else {
-				ar.splice(i! + 1, 0, dragObj!);
-			}
-		}
-		setGData(data);
-		if (typeof prop.onChange !== "undefined") {
-			prop.onChange(treeToList(data));
-		}
-	};
-
-	return (
-		<>
-			<Tree
-				rootClassName="draggable-tree"
-				draggable
-				blockNode
-				onDragEnter={onDragEnter}
-				onDrop={onDrop}
-				treeData={gData}
-			/>
-		</>
-	);
+  const data = tocGetTreeData(prop.treeData);
+  console.log("treedata", data);
+  const [gData, setGData] = useState(data);
+  useEffect(() => {
+    const data = tocGetTreeData(prop.treeData);
+    setGData(data);
+  }, [prop]);
+
+  const onDragEnter: TreeProps["onDragEnter"] = (info) => {
+    console.log(info);
+    // expandedKeys 需要受控时设置
+    // setExpandedKeys(info.expandedKeys)
+  };
+
+  const onDrop: TreeProps["onDrop"] = (info) => {
+    console.log(info);
+    const dropKey = info.node.key;
+    const dragKey = info.dragNode.key;
+    const dropPos = info.node.pos.split("-");
+    const dropPosition =
+      info.dropPosition - Number(dropPos[dropPos.length - 1]);
+
+    const loop = (
+      data: DataNode[],
+      key: React.Key,
+      callback: (node: DataNode, i: number, data: DataNode[]) => void
+    ) => {
+      for (let i = 0; i < data.length; i++) {
+        if (data[i].key === key) {
+          return callback(data[i], i, data);
+        }
+        if (data[i].children) {
+          loop(data[i].children!, key, callback);
+        }
+      }
+    };
+    const data = [...gData];
+
+    // Find dragObject
+    let dragObj: DataNode;
+    loop(data, dragKey, (item, index, arr) => {
+      arr.splice(index, 1);
+      dragObj = item;
+    });
+
+    if (!info.dropToGap) {
+      // Drop on the content
+      loop(data, dropKey, (item) => {
+        item.children = item.children || [];
+        // where to insert 示例添加到头部,可以是随意位置
+        item.children.unshift(dragObj);
+      });
+    } else if (
+      ((info.node as any).props.children || []).length > 0 && // Has children
+      (info.node as any).props.expanded && // Is expanded
+      dropPosition === 1 // On the bottom gap
+    ) {
+      loop(data, dropKey, (item) => {
+        item.children = item.children || [];
+        // where to insert 示例添加到头部,可以是随意位置
+        item.children.unshift(dragObj);
+        // in previous version, we use item.children.push(dragObj) to insert the
+        // item to the tail of the children
+      });
+    } else {
+      let ar: DataNode[] = [];
+      let i: number;
+      loop(data, dropKey, (_item, index, arr) => {
+        ar = arr;
+        i = index;
+      });
+      if (dropPosition === -1) {
+        ar.splice(i!, 0, dragObj!);
+      } else {
+        ar.splice(i! + 1, 0, dragObj!);
+      }
+    }
+    setGData(data);
+    if (typeof prop.onChange !== "undefined") {
+      prop.onChange(treeToList(data));
+    }
+  };
+
+  return (
+    <>
+      <Tree
+        rootClassName="draggable-tree"
+        draggable
+        blockNode
+        onDragEnter={onDragEnter}
+        onDrop={onDrop}
+        treeData={gData}
+      />
+    </>
+  );
 };
 
 export default Widget;

+ 2 - 2
dashboard/src/components/template/Quote.tsx

@@ -1,7 +1,7 @@
-import { ProCard } from "@ant-design/pro-components";
 import { Button, Popover } from "antd";
-import { SearchOutlined, CopyOutlined } from "@ant-design/icons";
 import { Typography } from "antd";
+import { SearchOutlined, CopyOutlined } from "@ant-design/icons";
+import { ProCard } from "@ant-design/pro-components";
 
 const { Text, Link } = Typography;
 

+ 1 - 0
dashboard/src/components/template/SentEdit/EditInfo.tsx

@@ -1,5 +1,6 @@
 import { Typography } from "antd";
 import { Space } from "antd";
+
 import User from "../../auth/User";
 import TimeShow from "../../general/TimeShow";
 import { ISentence } from "../SentEdit";

+ 1 - 0
dashboard/src/components/template/SentEdit/SentCell.tsx

@@ -1,4 +1,5 @@
 import { useState } from "react";
+
 import { ISentence } from "../SentEdit";
 import SentEditMenu from "./SentEditMenu";
 import SentCellEditable from "./SentCellEditable";

+ 4 - 2
dashboard/src/components/template/SentEdit/SentCellEditable.tsx

@@ -1,11 +1,13 @@
+import { useState } from "react";
+import { useIntl } from "react-intl";
 import { Button, message, Typography } from "antd";
 import { SaveOutlined } from "@ant-design/icons";
 import TextArea from "antd/lib/input/TextArea";
-import { useState } from "react";
-import { useIntl } from "react-intl";
+
 import { put } from "../../../request";
 import { ISentenceRequest, ISentenceResponse } from "../../api/Corpus";
 import { ISentence } from "../SentEdit";
+
 const { Text } = Typography;
 
 interface ISentCellEditable {

+ 1 - 0
dashboard/src/components/template/SentEdit/SentContent.tsx

@@ -1,6 +1,7 @@
 import { ISentence } from "../SentEdit";
 import SentCell from "./SentCell";
 import { WbwSentCtl } from "../WbwSent";
+
 interface IWidgetSentContent {
   origin?: ISentence[];
   translation?: ISentence[];

+ 0 - 1
dashboard/src/components/template/SentEdit/SentMenu.tsx

@@ -1,7 +1,6 @@
 import { useState } from "react";
 import { Button, Dropdown } from "antd";
 import { MoreOutlined } from "@ant-design/icons";
-
 import type { MenuProps } from "antd";
 
 const onClick: MenuProps["onClick"] = ({ key }) => {

+ 1 - 1
dashboard/src/components/template/SentEdit/SentTabButton.tsx

@@ -1,5 +1,6 @@
 import { useIntl } from "react-intl";
 import { Badge, Dropdown } from "antd";
+import type { MenuProps } from "antd";
 import {
   OneToOneOutlined,
   LinkOutlined,
@@ -11,7 +12,6 @@ import {
   ISite,
   refresh as refreshLayout,
 } from "../../../reducers/open-article";
-import type { MenuProps } from "antd";
 
 const handleButtonClick = (e: React.MouseEvent<HTMLButtonElement>) => {
   console.log("click left button", e);

+ 0 - 2
dashboard/src/components/template/SentEdit/SuggestionAdd.tsx

@@ -1,10 +1,8 @@
 import { Button } from "antd";
 import { useState } from "react";
-import { Typography } from "antd";
 import { PlusOutlined } from "@ant-design/icons";
 
 import { ISentence } from "../SentEdit";
-import SentEditMenu from "./SentEditMenu";
 import SentCellEditable from "./SentCellEditable";
 
 interface ISentCell {

+ 1 - 0
dashboard/src/components/template/SentEdit/SuggestionList.tsx

@@ -1,4 +1,5 @@
 import { useEffect, useState } from "react";
+
 import { get } from "../../../request";
 import { ISuggestionListResponse } from "../../api/Suggestion";
 import { IChannel } from "../../channel/Channel";

+ 1 - 0
dashboard/src/components/template/SentEdit/SuggestionTabs.tsx

@@ -1,6 +1,7 @@
 import { useState } from "react";
 import { RadioChangeEvent, Space } from "antd";
 import { Radio } from "antd";
+
 import { ISentence } from "../SentEdit";
 import { SuggestionIcon } from "../../../assets/icon";
 import SuggestionAdd from "./SuggestionAdd";

+ 3 - 2
dashboard/src/components/template/SentRead.tsx

@@ -1,7 +1,6 @@
 import { useEffect, useRef, useState } from "react";
 import { Tooltip, Button } from "antd";
-import MdView from "./MdView";
-import { ISentence } from "./SentEdit";
+
 import { useAppSelector } from "../../hooks";
 import {
   onChangeKey,
@@ -10,6 +9,8 @@ import {
 } from "../../reducers/setting";
 import { GetUserSetting } from "../auth/setting/default";
 import { TCodeConvertor } from "./utilities";
+import { ISentence } from "./SentEdit";
+import MdView from "./MdView";
 
 interface IWidgetSentReadFrame {
   origin?: ISentence[];

+ 3 - 2
dashboard/src/components/template/Term.tsx

@@ -1,7 +1,8 @@
-import { ProCard } from "@ant-design/pro-components";
 import { Button, Popover } from "antd";
-import { SearchOutlined } from "@ant-design/icons";
 import { Typography } from "antd";
+import { SearchOutlined } from "@ant-design/icons";
+import { ProCard } from "@ant-design/pro-components";
+
 import TermCreate, { IWidgetDictCreate } from "../term/TermCreate";
 import { command } from "../../reducers/command";
 import store from "../../store";

+ 0 - 3
dashboard/src/components/template/Wbw/WbwDetailAdvance.tsx

@@ -1,4 +1,3 @@
-import { useIntl } from "react-intl";
 import { Input } from "antd";
 
 import { IWbw } from "./WbwWord";
@@ -8,8 +7,6 @@ interface IWidget {
   onChange?: Function;
 }
 const Widget = ({ data, onChange }: IWidget) => {
-  const intl = useIntl();
-
   const onWordChange = (
     e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
   ) => {

+ 0 - 2
dashboard/src/components/template/Wbw/WbwDetailNote.tsx

@@ -1,4 +1,3 @@
-import { useIntl } from "react-intl";
 import { Input } from "antd";
 
 import { IWbw } from "./WbwWord";
@@ -10,7 +9,6 @@ interface IWidget {
   onChange?: Function;
 }
 const Widget = ({ data, onChange }: IWidget) => {
-  const intl = useIntl();
   const onTextChange = (
     e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
   ) => {

+ 7 - 6
dashboard/src/components/template/Wbw/WbwWord.tsx

@@ -1,4 +1,11 @@
 import { useState, useEffect, useRef } from "react";
+
+import { useAppSelector } from "../../../hooks";
+import { add, wordIndex } from "../../../reducers/inline-dict";
+import { get } from "../../../request";
+import store from "../../../store";
+
+import { IApiResponseDictList } from "../../api/Dict";
 import WbwCase from "./WbwCase";
 import { bookMarkColor } from "./WbwDetailBookMark";
 import WbwFactorMeaning from "./WbwFactorMeaning";
@@ -8,11 +15,6 @@ import WbwPali from "./WbwPali";
 import "./wbw.css";
 import WbwPara from "./WbwPara";
 import WbwPage from "./WbwPage";
-import { useAppSelector } from "../../../hooks";
-import { add, wordList, wordIndex } from "../../../reducers/inline-dict";
-import { get } from "../../../request";
-import { IApiResponseDictList, IDictDataRequest } from "../../api/Dict";
-import store from "../../../store";
 
 export type TFieldName =
   | "word"
@@ -95,7 +97,6 @@ const Widget = ({
   const [wordData, setWordData] = useState(data);
   const [fieldDisplay, setFieldDisplay] = useState(fields);
   const intervalRef = useRef<number | null>(null); //防抖计时器句柄
-  const inlineWordList = useAppSelector(wordList);
   const inlineWordIndex = useAppSelector(wordIndex);
 
   useEffect(() => {

+ 1 - 0
dashboard/src/components/template/WbwSent.tsx

@@ -1,4 +1,5 @@
 import { useEffect, useState } from "react";
+
 import { useAppSelector } from "../../hooks";
 import { mode } from "../../reducers/article-mode";
 import WbwWord, { IWbw, IWbwFields } from "./Wbw/WbwWord";

+ 1 - 0
dashboard/src/components/template/Wd.tsx

@@ -1,4 +1,5 @@
 import { useState } from "react";
+
 import { command } from "../../reducers/command";
 import store from "../../store";
 import { IWidgetDict } from "../dict/DictComponent";

+ 2 - 0
dashboard/src/components/template/utilities.ts

@@ -1,4 +1,5 @@
 import React from "react";
+
 import MdTpl from "./MdTpl";
 import { WdCtl } from "./Wd";
 import { Divider } from "antd";
@@ -6,6 +7,7 @@ import { roman_to_my, my_to_roman } from "../code/my";
 import { roman_to_si } from "../code/si";
 import { roman_to_thai } from "../code/thai";
 import { roman_to_taitham } from "../code/tai-tham";
+
 export type TCodeConvertor =
   | "none"
   | "roman"

+ 0 - 6
dashboard/src/load.ts

@@ -9,12 +9,6 @@ import { get, IErrorResponse } from "./request";
 //import { GRPC_HOST,  grpc_metadata } from "./request";
 import store from "./store";
 
-interface IUserResponse {
-  ok: boolean;
-  message: string;
-  data: IUser;
-}
-
 export interface ISiteInfoResponse {
   title: string;
 }

+ 5 - 0
dashboard/src/locales/zh-Hans/buttons.ts

@@ -2,6 +2,7 @@ const items = {
   "buttons.submit": "提交",
   "buttons.create": "新建",
   "buttons.edit": "编辑",
+  "buttons.translate": "翻译",
   "buttons.read": "阅读",
   "buttons.wbw": "逐词解析",
   "buttons.delete": "删除",
@@ -13,6 +14,10 @@ const items = {
   "buttons.save": "保存",
   "buttons.save.publish": "保存并公开",
   "buttons.cancel": "取消",
+  "buttons.setting": "设置",
+  "buttons.sign-in": "登录",
+  "buttons.sign-out": "退出登录",
+  "buttons.welcome": "欢迎遨游法的海洋",
 };
 
 export default items;

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

@@ -27,6 +27,7 @@ const items = {
   "columns.library.convertor.title": "巴利字符转换器",
   "columns.library.palihandbook.title": "语法手册",
   "columns.library.statistics.title": "单词统计",
+  "columns.library.blog.label": "个人空间",
   "columns.studio.title": "译经楼",
   "columns.studio.palicanon.title": "圣典",
   "columns.studio.recent.title": "最近编辑",

+ 35 - 34
dashboard/src/pages/library/anthology/show.tsx

@@ -1,46 +1,47 @@
 import { useParams } from "react-router-dom";
 import { Layout, Affix, Col, Row } from "antd";
+
 import AnthologyDetail from "../../../components/article/AnthologyDetail";
 
 const { Content, Header } = Layout;
 
 const Widget = () => {
-	// TODO
-	const { id, tags } = useParams(); //url 参数
-	let aid = id ? id : "";
-	let channel = tags ? tags : "";
+  // TODO
+  const { id, tags } = useParams(); //url 参数
+  let aid = id ? id : "";
+  let channel = tags ? tags : "";
 
-	const pageMaxWidth = "1260px";
-	return (
-		<Layout>
-			<Affix offsetTop={0}>
-				<Header style={{ backgroundColor: "gray", height: "3.5em" }}>
-					<Col flex="auto"></Col>
-					<Col flex={pageMaxWidth}>
-						<div>
-							{aid}@{channel}
-						</div>
-					</Col>
-					<Col flex="auto"></Col>
-				</Header>
-			</Affix>
+  const pageMaxWidth = "1260px";
+  return (
+    <Layout>
+      <Affix offsetTop={0}>
+        <Header style={{ backgroundColor: "gray", height: "3.5em" }}>
+          <Col flex="auto"></Col>
+          <Col flex={pageMaxWidth}>
+            <div>
+              {aid}@{channel}
+            </div>
+          </Col>
+          <Col flex="auto"></Col>
+        </Header>
+      </Affix>
 
-			<Content>
-				<Row>
-					<Col flex="auto"></Col>
-					<Col flex={pageMaxWidth}>
-						<Row>
-							<Col span="18">
-								<AnthologyDetail aid={aid} />
-							</Col>
-							<Col span="6"></Col>
-						</Row>
-					</Col>
-					<Col flex="auto"></Col>
-				</Row>
-			</Content>
-		</Layout>
-	);
+      <Content>
+        <Row>
+          <Col flex="auto"></Col>
+          <Col flex={pageMaxWidth}>
+            <Row>
+              <Col span="18">
+                <AnthologyDetail aid={aid} />
+              </Col>
+              <Col span="6"></Col>
+            </Row>
+          </Col>
+          <Col flex="auto"></Col>
+        </Row>
+      </Content>
+    </Layout>
+  );
 };
 
 export default Widget;

+ 1 - 0
dashboard/src/pages/library/blog/anthology.tsx

@@ -1,4 +1,5 @@
 import { useParams } from "react-router-dom";
+
 import AnthologyList from "../../../components/article/AnthologyList";
 import BlogNav from "../../../components/blog/BlogNav";
 

+ 1 - 0
dashboard/src/pages/library/blog/course.tsx

@@ -1,4 +1,5 @@
 import { useParams } from "react-router-dom";
+
 import BlogNav from "../../../components/blog/BlogNav";
 
 const Widget = () => {

+ 1 - 0
dashboard/src/pages/library/blog/overview.tsx

@@ -1,6 +1,7 @@
 import { useParams } from "react-router-dom";
 import { Row, Col } from "antd";
 import { Affix } from "antd";
+
 import BlogNav from "../../../components/blog/BlogNav";
 import Profile from "../../../components/blog/Profile";
 import AuthorTimeLine from "../../../components/blog/TimeLine";

+ 1 - 0
dashboard/src/pages/library/blog/term.tsx

@@ -1,4 +1,5 @@
 import { useParams } from "react-router-dom";
+
 import BlogNav from "../../../components/blog/BlogNav";
 
 const Widget = () => {

+ 1 - 0
dashboard/src/pages/library/blog/translation.tsx

@@ -1,4 +1,5 @@
 import { useParams } from "react-router-dom";
+
 import BlogNav from "../../../components/blog/BlogNav";
 
 const Widget = () => {

+ 1 - 0
dashboard/src/pages/library/course/course.tsx

@@ -1,5 +1,6 @@
 import { Link } from "react-router-dom";
 import { useParams } from "react-router-dom";
+
 const Widget = () => {
 	// TODO
 	const { courseid } = useParams(); //url 参数

+ 1 - 0
dashboard/src/pages/library/course/index.tsx

@@ -1,4 +1,5 @@
 import { Outlet } from "react-router-dom";
+
 import HeadBar from "../../../components/library/HeadBar";
 import FooterBar from "../../../components/library/FooterBar";
 

+ 3 - 2
dashboard/src/pages/library/dict/show.tsx

@@ -1,11 +1,12 @@
+import { useState } from "react";
 import { useParams } from "react-router-dom";
 import { useNavigate } from "react-router-dom";
 import { Layout, Affix, Col, Row } from "antd";
 import { Input } from "antd";
 
-import { useState } from "react";
 import DictSearch from "../../../components/dict/DictSearch";
-const { Content, Header } = Layout;
+
+const { Content } = Layout;
 const { Search } = Input;
 
 const Widget = () => {

+ 0 - 1
dashboard/src/pages/library/palicanon/chapter.tsx

@@ -1,6 +1,5 @@
 import { useParams } from "react-router-dom";
 import { useNavigate } from "react-router-dom";
-
 import { Layout } from "antd";
 
 import BookViewer, { IParagraph } from "../../../components/corpus/BookViewer";

+ 40 - 42
dashboard/src/pages/studio/analysis/list.tsx

@@ -1,5 +1,4 @@
 import { useParams } from "react-router-dom";
-import { useIntl } from "react-intl";
 
 import { useState, useEffect } from "react";
 import { Line } from "@ant-design/plots";
@@ -9,50 +8,49 @@ import moment from "moment";
 
 const localizer = momentLocalizer(moment); // or globalizeLocalizer
 const Widget = () => {
-	const intl = useIntl(); //i18n
-	const { studioname } = useParams(); //url 参数
-	const [data, setData] = useState([]);
+  const { studioname } = useParams(); //url 参数
+  const [data, setData] = useState([]);
 
-	useEffect(() => {
-		asyncFetch();
-	}, []);
+  useEffect(() => {
+    asyncFetch();
+  }, []);
 
-	const myEventsList = [{ start: "2022-10-1", end: "2022-10-2" }];
-	const asyncFetch = () => {
-		fetch(
-			"https://gw.alipayobjects.com/os/bmw-prod/1d565782-dde4-4bb6-8946-ea6a38ccf184.json"
-		)
-			.then((response) => response.json())
-			.then((json) => setData(json))
-			.catch((error) => {
-				console.log("fetch data failed", error);
-			});
-	};
-	const config = {
-		data,
-		xField: "Date",
-		yField: "scales",
-		xAxis: {
-			tickCount: 5,
-		},
-		slider: {
-			start: 0.1,
-			end: 0.5,
-		},
-	};
-	return (
-		<>
-			{studioname}
-			<Line {...config} />
+  const myEventsList = [{ start: "2022-10-1", end: "2022-10-2" }];
+  const asyncFetch = () => {
+    fetch(
+      "https://gw.alipayobjects.com/os/bmw-prod/1d565782-dde4-4bb6-8946-ea6a38ccf184.json"
+    )
+      .then((response) => response.json())
+      .then((json) => setData(json))
+      .catch((error) => {
+        console.log("fetch data failed", error);
+      });
+  };
+  const config = {
+    data,
+    xField: "Date",
+    yField: "scales",
+    xAxis: {
+      tickCount: 5,
+    },
+    slider: {
+      start: 0.1,
+      end: 0.5,
+    },
+  };
+  return (
+    <>
+      {studioname}
+      <Line {...config} />
 
-			<Calendar
-				localizer={localizer}
-				events={myEventsList}
-				startAccessor="start"
-				endAccessor="end"
-			/>
-		</>
-	);
+      <Calendar
+        localizer={localizer}
+        events={myEventsList}
+        startAccessor="start"
+        endAccessor="end"
+      />
+    </>
+  );
 };
 
 export default Widget;

+ 6 - 7
dashboard/src/pages/studio/anthology/edit.tsx

@@ -1,6 +1,6 @@
+import { useState } from "react";
 import { useParams } from "react-router-dom";
 import { useIntl } from "react-intl";
-
 import {
   ProForm,
   ProFormText,
@@ -8,16 +8,15 @@ import {
 } from "@ant-design/pro-components";
 import { Card, Col, message, Row } from "antd";
 
-import EditableTree from "../../../components/studio/EditableTree";
-import type { ListNodeData } from "../../../components/studio/EditableTree";
-import LangSelect from "../../../components/general/LangSelect";
-import PublicitySelect from "../../../components/studio/PublicitySelect";
+import { get, put } from "../../../request";
 import {
   IAnthologyDataRequest,
   IAnthologyResponse,
 } from "../../../components/api/Article";
-import { get, put } from "../../../request";
-import { useState } from "react";
+import EditableTree from "../../../components/studio/EditableTree";
+import type { ListNodeData } from "../../../components/studio/EditableTree";
+import LangSelect from "../../../components/general/LangSelect";
+import PublicitySelect from "../../../components/studio/PublicitySelect";
 import GoBack from "../../../components/studio/GoBack";
 
 interface IFormData {

+ 1 - 1
dashboard/src/pages/studio/article/edit.tsx

@@ -1,3 +1,4 @@
+import { useState } from "react";
 import { useParams } from "react-router-dom";
 import { useIntl } from "react-intl";
 import {
@@ -15,7 +16,6 @@ import {
 import LangSelect from "../../../components/general/LangSelect";
 import PublicitySelect from "../../../components/studio/PublicitySelect";
 import GoBack from "../../../components/studio/GoBack";
-import { useState } from "react";
 
 interface IFormData {
   uid: string;

+ 8 - 10
dashboard/src/pages/studio/article/index.tsx

@@ -7,16 +7,14 @@ import { styleStudioContent } from "../style";
 const { Content } = Layout;
 
 const Widget = () => {
-	return (
-		<Layout>
-			<Layout>
-				<LeftSider selectedKeys="article" />
-				<Content style={styleStudioContent}>
-					<Outlet />
-				</Content>
-			</Layout>
-		</Layout>
-	);
+  return (
+    <Layout>
+      <LeftSider selectedKeys="article" />
+      <Content style={styleStudioContent}>
+        <Outlet />
+      </Content>
+    </Layout>
+  );
 };
 
 export default Widget;

+ 3 - 2
dashboard/src/pages/studio/channel/edit.tsx

@@ -1,18 +1,19 @@
+import { useState } from "react";
+import { useIntl } from "react-intl";
 import { useParams } from "react-router-dom";
 import {
   ProForm,
   ProFormText,
   ProFormTextArea,
 } from "@ant-design/pro-components";
-import { useIntl } from "react-intl";
 import { Card, message, Space } from "antd";
+
 import { IApiResponseChannel } from "../../../components/api/Channel";
 import { get, put } from "../../../request";
 import ChannelTypeSelect from "../../../components/channel/ChannelTypeSelect";
 import LangSelect from "../../../components/general/LangSelect";
 import PublicitySelect from "../../../components/studio/PublicitySelect";
 import GoBack from "../../../components/studio/GoBack";
-import { useState } from "react";
 
 interface IFormData {
   name: string;

Vissa filer visades inte eftersom för många filer har ändrats