Просмотр исходного кода

Merge pull request #1229 from visuddhinanda/agile

ralation 表 排序 添加 term 栏目
visuddhinanda 2 лет назад
Родитель
Сommit
0d869eaf9d

+ 7 - 3
dashboard/src/Router.tsx

@@ -35,7 +35,9 @@ import LibraryCourse from "./pages/library/course";
 import LibraryCourseList from "./pages/library/course/list";
 import LibraryCourseShow from "./pages/library/course/course";
 
-import LibraryTerm from "./pages/library/term/show";
+import LibraryTerm from "./pages/library/term";
+import LibraryTermShow from "./pages/library/term/show";
+import LibraryTermList from "./pages/library/term/list";
 import LibraryDict from "./pages/library/dict";
 import LibraryDictShow from "./pages/library/dict/show";
 import LibraryAnthology from "./pages/library/anthology";
@@ -180,8 +182,10 @@ const Widget = () => {
           <Route path="list" element={<LibraryCourseList />}></Route>
           <Route path="show/:id" element={<LibraryCourseShow />}></Route>
         </Route>
-
-        <Route path="term/:word" element={<LibraryTerm />} />
+        <Route path="term" element={<LibraryTerm />}>
+          <Route path="list/:word" element={<LibraryTermList />}></Route>
+          <Route path="show/:id" element={<LibraryTermShow />}></Route>
+        </Route>
 
         <Route path="dict" element={<LibraryDict />}>
           <Route path=":word" element={<LibraryDictShow />} />

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

@@ -22,6 +22,7 @@ export interface ITermDataResponse {
   meaning: string;
   other_meaning: string;
   note: string;
+  html?: string;
   channal: string;
   channel?: IChannel;
   studio: IStudio;

+ 8 - 2
dashboard/src/components/dict/SearchVocabulary.tsx

@@ -13,10 +13,16 @@ interface ValueType {
 }
 interface IWidget {
   value?: string;
+  api?: string;
   onSearch?: Function;
   onSplit?: Function;
 }
-const SearchVocabularyWidget = ({ value, onSplit, onSearch }: IWidget) => {
+const SearchVocabularyWidget = ({
+  value,
+  api = "vocabulary",
+  onSplit,
+  onSearch,
+}: IWidget) => {
   const [options, setOptions] = useState<ValueType[]>([]);
   const [fetching, setFetching] = useState(false);
 
@@ -71,7 +77,7 @@ const SearchVocabularyWidget = ({ value, onSplit, onSearch }: IWidget) => {
       return;
     }
 
-    get<IVocabularyListResponse>(`/v2/vocabulary?view=key&key=${value}`)
+    get<IVocabularyListResponse>(`/v2/${api}?view=key&key=${value}`)
       .then((json) => {
         const words: ValueType[] = json.data.rows.map((item) => {
           return renderItem(item.word, item.count, item.meaning);

+ 4 - 2
dashboard/src/components/template/MdView.tsx

@@ -4,15 +4,17 @@ const { Text } = Typography;
 
 interface IWidget {
   html?: string;
+  placeholder?: string;
   wordWidget?: boolean;
   convertor?: TCodeConvertor;
 }
 const Widget = ({
-  html = "<div></div>",
+  html,
   wordWidget = false,
+  placeholder,
   convertor,
 }: IWidget) => {
-  const jsx = XmlToReact(html, wordWidget, convertor);
+  const jsx = html ? XmlToReact(html, wordWidget, convertor) : placeholder;
   return <Text>{jsx}</Text>;
 };
 

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

@@ -44,7 +44,6 @@ export function XmlToReact(
         }
       }
     }
-    console.log("attr", output);
     return output;
   }
 

+ 20 - 0
dashboard/src/components/term/TermItem.tsx

@@ -0,0 +1,20 @@
+import { Typography } from "antd";
+import { ITermDataResponse } from "../api/Term";
+import MdView from "../template/MdView";
+
+const { Title, Text, Paragraph } = Typography;
+
+interface IWidget {
+  data?: ITermDataResponse;
+}
+const Widget = ({ data }: IWidget) => {
+  return (
+    <Paragraph>
+      <Title level={4}>{data?.meaning}</Title>
+      <Text>{data?.other_meaning}</Text>
+      <MdView html={data?.html} />
+    </Paragraph>
+  );
+};
+
+export default Widget;

+ 66 - 0
dashboard/src/components/term/TermSearch.tsx

@@ -0,0 +1,66 @@
+import { useState, useEffect } from "react";
+import { Col, List, Row } from "antd";
+
+import { get } from "../../request";
+import {
+  ITermDataResponse,
+  ITermListResponse,
+  ITermResponse,
+} from "../api/Term";
+import TermItem from "./TermItem";
+
+interface IWidget {
+  word?: string;
+  wordId?: string;
+  compact?: boolean;
+}
+const TermSearchWidget = ({ word, wordId, compact = false }: IWidget) => {
+  const [tableData, setTableData] = useState<ITermDataResponse[]>();
+
+  useEffect(() => {
+    if (typeof word === "undefined" && typeof wordId === "undefined") {
+      return;
+    }
+    if (word) {
+      const url = `/v2/terms?view=word&word=${word}`;
+      get<ITermListResponse>(url)
+        .then((json) => {
+          setTableData(json.data.rows);
+        })
+        .catch((error) => {
+          console.error(error);
+        });
+    } else if (wordId) {
+      const url = `/v2/terms/${wordId}`;
+      get<ITermResponse>(url)
+        .then((json) => {
+          setTableData([json.data]);
+        })
+        .catch((error) => {
+          console.error(error);
+        });
+    }
+  }, [word, wordId]);
+
+  return (
+    <Row>
+      <Col flex="200px">{compact ? <></> : <></>}</Col>
+      <Col flex="760px">
+        <List
+          itemLayout="vertical"
+          size="large"
+          dataSource={tableData}
+          header={word}
+          renderItem={(item) => (
+            <List.Item>
+              <TermItem data={item} />
+            </List.Item>
+          )}
+        />
+      </Col>
+      <Col flex="200px"></Col>
+    </Row>
+  );
+};
+
+export default TermSearchWidget;

+ 74 - 0
dashboard/src/components/term/TermShow.tsx

@@ -0,0 +1,74 @@
+import { useEffect, useState } from "react";
+import { Layout, Affix, Col, Row } from "antd";
+
+import SearchVocabulary from "../dict/SearchVocabulary";
+import TermSearch from "./TermSearch";
+
+const { Content } = Layout;
+
+interface IWidget {
+  word?: string;
+  wordId?: string;
+  compact?: boolean;
+  onSearch?: Function;
+}
+const TermShowWidget = ({
+  word,
+  wordId,
+  compact = false,
+  onSearch,
+}: IWidget) => {
+  const [split, setSplit] = useState<string>();
+  const [wordSearch, setWordSearch] = useState<string>();
+  const [container, setContainer] = useState<HTMLDivElement | null>(null);
+
+  useEffect(() => {
+    setWordSearch(word?.toLowerCase());
+  }, [word]);
+  const dictSearch = (value: string, isFactor?: boolean) => {
+    console.log("onSearch", value);
+    const word = value.toLowerCase();
+    setWordSearch(word);
+    if (typeof onSearch !== "undefined") {
+      onSearch(value, isFactor);
+    }
+  };
+  return (
+    <div ref={setContainer}>
+      <Affix offsetTop={0} target={compact ? () => container : undefined}>
+        <div
+          style={{
+            backgroundColor: "rgba(100,100,100,0.3)",
+            backdropFilter: "blur(5px)",
+          }}
+        >
+          <Row style={{ paddingTop: "0.5em", paddingBottom: "0.5em" }}>
+            {compact ? <></> : <Col flex="auto"></Col>}
+            <Col flex="560px">
+              <SearchVocabulary
+                value={word}
+                onSearch={dictSearch}
+                onSplit={(word: string | undefined) => {
+                  console.log("onSplit", word);
+                  setSplit(word);
+                }}
+              />
+            </Col>
+            {compact ? <></> : <Col flex="auto"></Col>}
+          </Row>
+        </div>
+      </Affix>
+      <Content style={{ minHeight: 700 }}>
+        <Row>
+          {compact ? <></> : <Col flex="auto"></Col>}
+          <Col flex="1260px">
+            <TermSearch word={wordSearch} wordId={wordId} compact={compact} />
+          </Col>
+          {compact ? <></> : <Col flex="auto"></Col>}
+        </Row>
+      </Content>
+    </div>
+  );
+};
+
+export default TermShowWidget;

+ 17 - 13
dashboard/src/pages/admin/nissaya-ending/list.tsx

@@ -20,9 +20,10 @@ import NissayaEndingEdit from "../../../components/admin/relation/NissayaEndingE
 import { LangValueEnum } from "../../../components/general/LangSelect";
 import { NissayaCardModal } from "../../../components/general/NissayaCard";
 import DataImport from "../../../components/admin/relation/DataImport";
-import { CaseValueEnum } from "../relation/list";
+import { CaseValueEnum, getSorterUrl } from "../relation/list";
 import TermModal from "../../../components/term/TermModal";
 import { ITermDataResponse } from "../../../components/api/Term";
+import TimeShow from "../../../components/general/TimeShow";
 
 export interface INissayaEndingRequest {
   id?: string;
@@ -60,7 +61,7 @@ export interface INissayaEnding {
   count?: number;
   termId?: string;
   termChannel?: string;
-  updatedAt?: number;
+  updated_at?: string;
   createdAt?: number;
 }
 const Widget = () => {
@@ -129,6 +130,7 @@ const Widget = () => {
             dataIndex: "ending",
             key: "ending",
             search: false,
+            sorter: true,
             render: (text, row, index, action) => {
               return (
                 <Space>
@@ -177,6 +179,7 @@ const Widget = () => {
             }),
             dataIndex: "relation",
             key: "relation",
+            sorter: true,
           },
           {
             title: intl.formatMessage({
@@ -196,19 +199,21 @@ const Widget = () => {
             width: 100,
             search: false,
             dataIndex: "count",
-            sorter: (a, b) => (a.count && b.count ? a.count - b.count : 0),
+            sorter: true,
           },
           {
             title: intl.formatMessage({
-              id: "forms.fields.created-at.label",
+              id: "forms.fields.updated-at.label",
             }),
-            key: "created-at",
+            key: "updated_at",
             width: 100,
             search: false,
-            dataIndex: "createdAt",
+            dataIndex: "updated_at",
             valueType: "date",
-            sorter: (a, b) =>
-              a.createdAt && b.createdAt ? a.createdAt - b.createdAt : 0,
+            sorter: true,
+            render: (text, row, index, action) => {
+              return <TimeShow time={row.updated_at} showIcon={false} />;
+            },
           },
           {
             title: intl.formatMessage({ id: "buttons.option" }),
@@ -283,13 +288,13 @@ const Widget = () => {
           if (filter.lang) {
             url += `&lang=${filter.lang.join()}`;
           }
+          url += getSorterUrl(sorter);
+
           console.log("url", url);
           const res = await get<INissayaEndingListResponse>(url);
           const items: INissayaEnding[] = res.data.rows.map((item, id) => {
-            const date = new Date(item.created_at ? item.created_at : 0);
-            const date2 = new Date(item.updated_at ? item.updated_at : 0);
             return {
-              sn: id + 1,
+              sn: offset + id + 1,
               id: item.id,
               ending: item.ending,
               lang: item.lang,
@@ -298,8 +303,7 @@ const Widget = () => {
               count: item.count,
               termId: item.term_id,
               termChannel: item.term_channel,
-              createdAt: date.getTime(),
-              updatedAt: date2.getTime(),
+              updated_at: item.updated_at,
             };
           });
           return {

+ 31 - 24
dashboard/src/pages/admin/relation/list.tsx

@@ -19,6 +19,24 @@ import RelationEdit from "../../../components/admin/relation/RelationEdit";
 import DataImport from "../../../components/admin/relation/DataImport";
 import { useAppSelector } from "../../../hooks";
 import { getTerm } from "../../../reducers/term-vocabulary";
+import { SortOrder } from "antd/lib/table/interface";
+import TimeShow from "../../../components/general/TimeShow";
+
+export const getSorterUrl = (sorter?: Record<string, SortOrder>): string => {
+  let url: string = "";
+  for (const key in sorter) {
+    if (Object.prototype.hasOwnProperty.call(sorter, key)) {
+      const element = sorter[key];
+      const dir = element === "ascend" ? "asc" : "desc";
+      let orderby = key;
+      if (orderby === "updatedAt") {
+        orderby = "updated_at";
+      }
+      url = `&order=${orderby}&dir=${dir}`;
+    }
+  }
+  return url;
+};
 
 export const CaseValueEnum = () => {
   const intl = useIntl();
@@ -100,8 +118,8 @@ export interface IRelation {
   fromCase?: string[];
   fromSpell?: string;
   to?: string[];
-  updatedAt?: number;
-  createdAt?: number;
+  updated_at?: string;
+  created_at?: string;
 }
 const Widget = () => {
   const intl = useIntl(); //i18n
@@ -163,7 +181,7 @@ const Widget = () => {
             }),
             dataIndex: "name",
             key: "name",
-            sorter: (a, b) => (a < b ? 1 : -1),
+            sorter: true,
             render: (text, row, index, action) => {
               return (
                 <RelationEdit
@@ -237,13 +255,15 @@ const Widget = () => {
             title: intl.formatMessage({
               id: "forms.fields.updated-at.label",
             }),
-            key: "updated-at",
+            key: "updated_at",
             width: 100,
             search: false,
-            dataIndex: "updatedAt",
+            dataIndex: "updated_at",
             valueType: "date",
-            sorter: (a, b) =>
-              a.updatedAt && b.updatedAt ? a.updatedAt - b.updatedAt : 0,
+            sorter: true,
+            render: (text, row, index, action) => {
+              return <TimeShow time={row.updated_at} showIcon={false} />;
+            },
           },
           {
             title: intl.formatMessage({ id: "buttons.option" }),
@@ -301,24 +321,11 @@ const Widget = () => {
           if (filter.case) {
             url += `&case=${filter.case.join()}`;
           }
-          if (sorter) {
-            for (const key in sorter) {
-              if (Object.prototype.hasOwnProperty.call(sorter, key)) {
-                const element = sorter[key];
-                const dir = element === "ascend" ? "asc" : "desc";
-                let orderby = key;
-                if (orderby === "updatedAt") {
-                  orderby = "updated_at";
-                }
-                url += `&order=${orderby}&dir=${dir}`;
-              }
-            }
-          }
+          url += getSorterUrl(sorter);
+
           console.log("url", url);
           const res = await get<IRelationListResponse>(url);
           const items: IRelation[] = res.data.rows.map((item, id) => {
-            const date = new Date(item.created_at ? item.created_at : 0);
-            const date2 = new Date(item.updated_at ? item.updated_at : 0);
             return {
               sn: offset + id + 1,
               id: item.id,
@@ -326,8 +333,8 @@ const Widget = () => {
               case: item.case,
               from: item.from,
               to: item.to,
-              createdAt: date.getTime(),
-              updatedAt: date2.getTime(),
+              created_at: item.created_at,
+              updated_at: item.updated_at,
             };
           });
           console.log("relation", items);

+ 8 - 8
dashboard/src/pages/library/dict/index.tsx

@@ -5,14 +5,14 @@ import HeadBar from "../../../components/library/HeadBar";
 import FooterBar from "../../../components/library/FooterBar";
 
 const Widget = () => {
-	// TODO
-	return (
-		<Layout>
-			<HeadBar selectedKeys="dict" />
-			<Outlet />
-			<FooterBar />
-		</Layout>
-	);
+  // TODO
+  return (
+    <Layout>
+      <HeadBar selectedKeys="dict" />
+      <Outlet />
+      <FooterBar />
+    </Layout>
+  );
 };
 
 export default Widget;

+ 18 - 0
dashboard/src/pages/library/term/index.tsx

@@ -0,0 +1,18 @@
+import { Outlet } from "react-router-dom";
+import { Layout } from "antd";
+
+import HeadBar from "../../../components/library/HeadBar";
+import FooterBar from "../../../components/library/FooterBar";
+
+const Widget = () => {
+  // TODO
+  return (
+    <Layout>
+      <HeadBar selectedKeys="term" />
+      <Outlet />
+      <FooterBar />
+    </Layout>
+  );
+};
+
+export default Widget;

+ 10 - 0
dashboard/src/pages/library/term/list.tsx

@@ -0,0 +1,10 @@
+import { useParams } from "react-router-dom";
+
+import TermShow from "../../../components/term/TermShow";
+
+const Widget = () => {
+  const { word } = useParams(); //url 参数
+  return <TermShow word={word} />;
+};
+
+export default Widget;

+ 3 - 16
dashboard/src/pages/library/term/show.tsx

@@ -1,22 +1,9 @@
-import { Space } from "antd";
 import { useParams } from "react-router-dom";
-
-import HeadBar from "../../../components/library/HeadBar";
-import FooterBar from "../../../components/library/FooterBar";
+import TermShow from "../../../components/term/TermShow";
 
 const Widget = () => {
-	// TODO
-	const { word } = useParams(); //url 参数
-	return (
-		<div>
-			<HeadBar />
-			<div>术语百科 单词-{word}</div>
-			<div>
-				<Space>主显示区</Space>
-			</div>
-			<FooterBar />
-		</div>
-	);
+  const { id } = useParams(); //url 参数
+  return <TermShow wordId={id} />;
 };
 
 export default Widget;