Răsfoiți Sursa

:sparkles:添加relation并保存

visuddhinanda 2 ani în urmă
părinte
comite
653f851426

+ 8 - 0
dashboard/src/components/template/Wbw/WbwDetail.tsx

@@ -62,6 +62,9 @@ const Widget = ({ data, onClose, onSave }: IWidget) => {
       case "case":
         mData.case = { value: value, status: 7 };
         break;
+      case "relation":
+        mData.relation = { value: value, status: 7 };
+        break;
       case "confidence":
         mData.confidence = parseFloat(value);
         break;
@@ -104,6 +107,11 @@ const Widget = ({ data, onClose, onSave }: IWidget) => {
                     console.log("WbwDetailBasic onchange", e);
                     fieldChanged(e.field, e.value);
                   }}
+                  onRelationAdd={() => {
+                    if (typeof onClose !== "undefined") {
+                      onClose();
+                    }
+                  }}
                 />
               </div>
             ),

+ 21 - 3
dashboard/src/components/template/Wbw/WbwDetailBasic.tsx

@@ -55,10 +55,16 @@ export const getParentInDict = (
 
 interface IWidget {
   data: IWbw;
-  onChange?: Function;
   showRelation?: boolean;
+  onChange?: Function;
+  onRelationAdd?: Function;
 }
-const Widget = ({ data, showRelation = true, onChange }: IWidget) => {
+const Widget = ({
+  data,
+  showRelation = true,
+  onChange,
+  onRelationAdd,
+}: IWidget) => {
   const [form] = Form.useForm();
   const intl = useIntl();
   const [items, setItems] = useState<string[]>([]);
@@ -276,7 +282,19 @@ const Widget = ({ data, showRelation = true, onChange }: IWidget) => {
             key="relation"
             style={{ display: showRelation ? "block" : "none" }}
           >
-            <WbwDetailRelation data={data} />
+            <WbwDetailRelation
+              data={data}
+              onChange={(e: IWbwField) => {
+                if (typeof onChange !== "undefined") {
+                  onChange(e);
+                }
+              }}
+              onAdd={() => {
+                if (typeof onRelationAdd !== "undefined") {
+                  onRelationAdd();
+                }
+              }}
+            />
           </Panel>
         </Collapse>
       </Form>

+ 122 - 7
dashboard/src/components/template/Wbw/WbwDetailRelation.tsx

@@ -1,20 +1,56 @@
-import { List, Space } from "antd";
+import { Button, List, Select, Space } from "antd";
 import { useEffect, useState } from "react";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+
+import { useAppSelector } from "../../../hooks";
+import { getRelation } from "../../../reducers/relation";
+import { getTerm } from "../../../reducers/term-vocabulary";
 import { IWbw } from "./WbwWord";
+import { useIntl } from "react-intl";
+import store from "../../../store";
+import { add, relationAddParam } from "../../../reducers/relation-add";
+
+interface IOptions {
+  value: string;
+  label: JSX.Element;
+}
 
 interface IRelation {
   sour_id: string;
   sour_spell: string;
   dest_id: string;
   dest_spell: string;
-  relation: string;
+  relation?: string;
 }
 interface IWidget {
   data: IWbw;
   onChange?: Function;
+  onAdd?: Function;
 }
-const WbwParent2Widget = ({ data, onChange }: IWidget) => {
-  const [relation, setRelation] = useState<IRelation[]>();
+const WbwDetailRelationWidget = ({ data, onChange, onAdd }: IWidget) => {
+  const intl = useIntl();
+  const [relation, setRelation] = useState<IRelation[]>([]);
+  const [options, setOptions] = useState<IOptions[]>();
+  const terms = useAppSelector(getTerm);
+  const relations = useAppSelector(getRelation);
+
+  const addParam = useAppSelector(relationAddParam);
+  useEffect(() => {
+    if (
+      addParam?.command === "apply" &&
+      addParam.src_sn === data.sn.join("-") &&
+      addParam.target_spell
+    ) {
+      const newRelation: IRelation = {
+        sour_id: `${data.book}-${data.para}-` + data.sn.join("-"),
+        sour_spell: data.word.value,
+        dest_id: `${addParam.book}-${addParam.para}-` + addParam.target_id,
+        dest_spell: addParam.target_spell,
+      };
+      setRelation([...relation, newRelation]);
+    }
+  }, [addParam?.command]);
+
   useEffect(() => {
     if (typeof data.relation === "undefined") {
       return;
@@ -23,15 +59,94 @@ const WbwParent2Widget = ({ data, onChange }: IWidget) => {
     setRelation(arrRelation);
   }, [data.relation]);
 
+  useEffect(() => {
+    const caseEnd = data.case?.value.split("$");
+    if (typeof caseEnd === "undefined") {
+      return;
+    }
+    const mRelation = relations
+      ?.filter(
+        (value) =>
+          value.case === caseEnd[caseEnd.length - 1].replaceAll(".", "")
+      )
+      .map((item) => {
+        const localName = terms?.find(
+          (term) => term.word === item.name
+        )?.meaning;
+        return {
+          value: item.name,
+          label: (
+            <Space>
+              {item.name}
+              {localName}
+            </Space>
+          ),
+        };
+      });
+    setOptions(mRelation);
+  }, [data.case?.value, relations, terms]);
   return (
     <List
       itemLayout="vertical"
       size="small"
+      header={
+        <Button
+          type="dashed"
+          icon={<PlusOutlined />}
+          onClick={() => {
+            if (typeof onAdd !== "undefined") {
+              onAdd();
+            }
+            store.dispatch(
+              add({
+                book: data.book,
+                para: data.para,
+                src_sn: data.sn.join("-"),
+                command: "add",
+              })
+            );
+          }}
+        >
+          {intl.formatMessage({ id: "buttons.add" })}
+        </Button>
+      }
       dataSource={relation}
-      renderItem={(item) => (
+      renderItem={(item, index) => (
         <List.Item>
           <Space>
-            {item.dest_spell}-{item.relation}
+            <Button
+              type="text"
+              icon={<DeleteOutlined />}
+              onClick={() => {
+                let arrRelation: IRelation[] = [...relation];
+                arrRelation.splice(index, 1);
+                setRelation(arrRelation);
+                if (typeof onChange !== "undefined") {
+                  onChange({
+                    field: "relation",
+                    value: JSON.stringify(arrRelation),
+                  });
+                }
+              }}
+            />
+            {item.dest_spell}
+            <Select
+              defaultValue={item.relation}
+              style={{ width: 180 }}
+              onChange={(value: string) => {
+                console.log(`selected ${value}`);
+                let arrRelation: IRelation[] = [...relation];
+                arrRelation[index].relation = value;
+                setRelation(arrRelation);
+                if (typeof onChange !== "undefined") {
+                  onChange({
+                    field: "relation",
+                    value: JSON.stringify(arrRelation),
+                  });
+                }
+              }}
+              options={options}
+            />
           </Space>
         </List.Item>
       )}
@@ -39,4 +154,4 @@ const WbwParent2Widget = ({ data, onChange }: IWidget) => {
   );
 };
 
-export default WbwParent2Widget;
+export default WbwDetailRelationWidget;

+ 39 - 6
dashboard/src/components/template/Wbw/WbwPali.tsx

@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
 import { Popover, Typography } from "antd";
 import {
   TagTwoTone,
@@ -17,6 +17,8 @@ import CommentBox from "../../comment/CommentBox";
 import PaliText from "./PaliText";
 import store from "../../../store";
 import { command } from "../../../reducers/command";
+import { useAppSelector } from "../../../hooks";
+import { add, relationAddParam } from "../../../reducers/relation-add";
 
 const { Paragraph } = Typography;
 interface IWidget {
@@ -25,10 +27,41 @@ interface IWidget {
   onSave?: Function;
 }
 const WbwPaliWidget = ({ data, display, onSave }: IWidget) => {
-  const [click, setClicked] = useState(false);
+  const [popOpen, setPopOpen] = useState(false);
   const [paliColor, setPaliColor] = useState("unset");
   const [isHover, setIsHover] = useState(false);
   const [hasComment, setHasComment] = useState(data.hasComment);
+  /**
+   * 处理 relation 链接事件
+   * 点击连接或取消后,打开弹窗
+   */
+  const addParam = useAppSelector(relationAddParam);
+  useEffect(() => {
+    if (
+      (addParam?.command === "apply" || addParam?.command === "cancel") &&
+      addParam.src_sn === data.sn.join("-") &&
+      addParam.book === data.book &&
+      addParam.para === data.para
+    ) {
+      setPopOpen(true);
+      store.dispatch(
+        add({
+          book: data.book,
+          para: data.para,
+          src_sn: data.sn.join("-"),
+          command: "finish",
+        })
+      );
+    }
+  }, [
+    addParam?.book,
+    addParam?.command,
+    addParam?.para,
+    addParam?.src_sn,
+    data.book,
+    data.para,
+    data.sn,
+  ]);
 
   const handleClickChange = (open: boolean) => {
     if (open) {
@@ -36,7 +69,7 @@ const WbwPaliWidget = ({ data, display, onSave }: IWidget) => {
     } else {
       setPaliColor("unset");
     }
-    setClicked(open);
+    setPopOpen(open);
   };
 
   const wbwDetail = (
@@ -44,12 +77,12 @@ const WbwPaliWidget = ({ data, display, onSave }: IWidget) => {
       data={data}
       onClose={() => {
         setPaliColor("unset");
-        setClicked(false);
+        setPopOpen(false);
       }}
       onSave={(e: IWbw) => {
         if (typeof onSave !== "undefined") {
           onSave(e);
-          setClicked(false);
+          setPopOpen(false);
           setPaliColor("unset");
         }
       }}
@@ -190,7 +223,7 @@ const WbwPaliWidget = ({ data, display, onSave }: IWidget) => {
           content={wbwDetail}
           placement="bottom"
           trigger="click"
-          open={click}
+          open={popOpen}
           onOpenChange={handleClickChange}
         >
           {paliWord}

+ 67 - 0
dashboard/src/components/template/Wbw/WbwRelationAdd.tsx

@@ -0,0 +1,67 @@
+import { Button, Space } from "antd";
+import { useEffect, useState } from "react";
+import { useAppSelector } from "../../../hooks";
+import { add, relationAddParam } from "../../../reducers/relation-add";
+import store from "../../../store";
+import { IWbw } from "./WbwWord";
+interface IWidget {
+  data: IWbw;
+}
+const Widget = ({ data }: IWidget) => {
+  const [show, setShow] = useState(false);
+  const addParam = useAppSelector(relationAddParam);
+  useEffect(() => {
+    if (addParam?.command === "add") {
+      setShow(true);
+    } else {
+      setShow(false);
+    }
+  }, [addParam?.command]);
+
+  return (
+    <div style={{ position: "absolute", marginTop: "-24px" }}>
+      {show ? (
+        <Space>
+          <Button
+            onClick={() => {
+              if (typeof addParam === "undefined") {
+                return;
+              }
+              store.dispatch(
+                add({
+                  book: addParam.book,
+                  para: addParam.para,
+                  src_sn: addParam?.src_sn,
+                  target_id: `${data.book}-${data.para}-` + data.sn.join("-"),
+                  target_spell: data.word.value,
+                  command: "apply",
+                })
+              );
+            }}
+          >
+            add
+          </Button>
+          <Button
+            onClick={() => {
+              if (typeof addParam === "undefined") {
+                return;
+              }
+              store.dispatch(
+                add({
+                  book: addParam.book,
+                  para: addParam.para,
+                  src_sn: addParam.src_sn,
+                  command: "cancel",
+                })
+              );
+            }}
+          >
+            cancel
+          </Button>
+        </Space>
+      ) : undefined}
+    </div>
+  );
+};
+
+export default Widget;

+ 5 - 0
dashboard/src/components/template/Wbw/WbwWord.tsx

@@ -16,6 +16,7 @@ import WbwPali from "./WbwPali";
 import "./wbw.css";
 import WbwPara from "./WbwPara";
 import WbwPage from "./WbwPage";
+import WbwRelationAdd from "./WbwRelationAdd";
 
 export type TFieldName =
   | "word"
@@ -101,6 +102,7 @@ const WbwWordWidget = ({
   const [wordData, setWordData] = useState(data);
   const [fieldDisplay, setFieldDisplay] = useState(fields);
   const [newFactors, setNewFactors] = useState<string>();
+  const [showRelationTool, setShowRelationTool] = useState(false);
   const intervalRef = useRef<number | null>(null); //防抖计时器句柄
   const inlineWordIndex = useAppSelector(wordIndex);
 
@@ -163,6 +165,7 @@ const WbwWordWidget = ({
         className={`wbw_word ${display} ${wbwCtl} ${wbwAnchor} `}
         style={styleWbw}
         onMouseEnter={() => {
+          setShowRelationTool(true);
           if (intervalRef.current === null) {
             //开始计时,计时结束查字典
             intervalRef.current = window.setInterval(
@@ -174,8 +177,10 @@ const WbwWordWidget = ({
         }}
         onMouseLeave={() => {
           stopLookup();
+          setShowRelationTool(false);
         }}
       >
+        {showRelationTool ? <WbwRelationAdd data={data} /> : undefined}
         <WbwPali
           key="pali"
           data={wordData}

+ 4 - 7
dashboard/src/components/template/WbwSent.tsx

@@ -19,6 +19,7 @@ interface IWbwXml {
   parent?: WbwElement<string>;
   pg?: WbwElement<string>;
   parent2?: WbwElement<string>;
+  rela?: WbwElement<string>;
   lock?: boolean;
   bmt?: WbwElement<string>;
   bmc?: WbwElement<number>;
@@ -108,7 +109,7 @@ export const WbwSentCtl = ({
                   return {
                     pali: item.word,
                     real: item.real,
-                    id: `${book}-${para}-${e.sn[0]}`,
+                    id: `${book}-${para}-` + e.sn.join("-"),
                     type: item.type,
                     gramma: item.type,
                     mean: item.meaning
@@ -119,15 +120,11 @@ export const WbwSentCtl = ({
                       : undefined,
                     org: item.factors,
                     om: item.factorMeaning,
-                    case: item.case
-                      ? {
-                          value: item.case?.value,
-                          status: item.case?.status,
-                        }
-                      : undefined,
+                    case: item.case,
                     parent: item.parent,
                     pg: item.grammar2,
                     parent2: item.parent2,
+                    rela: item.relation,
                     lock: item.locked,
                     bmt: item.bookMarkText,
                     bmc: item.bookMarkColor,

+ 20 - 0
dashboard/src/load.ts

@@ -12,6 +12,8 @@ import { get as getLang } from "./locales";
 import store from "./store";
 import { ITerm, push } from "./reducers/term-vocabulary";
 import { push as nissayaEndingPush } from "./reducers/nissaya-ending-vocabulary";
+import { IRelation, IRelationListResponse } from "./pages/admin/relation/list";
+import { pushRelation } from "./reducers/relation";
 
 export interface ISiteInfoResponse {
   title: string;
@@ -101,6 +103,7 @@ const init = () => {
       }
     }
   );
+
   get<ITermResponse>(
     `/v2/term-vocabulary?view=community&lang=` + getLang()
   ).then((json) => {
@@ -108,6 +111,7 @@ const init = () => {
       store.dispatch(push(json.data.rows));
     }
   });
+
   //获取nissaya ending 表
   get<INissayaEndingResponse>(`/v2/nissaya-ending-vocabulary?lang=my`).then(
     (json) => {
@@ -117,6 +121,22 @@ const init = () => {
       }
     }
   );
+
+  //获取 relation 表
+  get<IRelationListResponse>(`/v2/relation?limit=1000`).then((json) => {
+    if (json.ok) {
+      const items: IRelation[] = json.data.rows.map((item, id) => {
+        return {
+          id: item.id,
+          name: item.name,
+          case: item.case,
+          to: item.to,
+        };
+      });
+      store.dispatch(pushRelation(items));
+    }
+  });
+
   //获取用户选择的主题
   const theme = localStorage.getItem("theme");
   if (theme === "dark") {

+ 38 - 0
dashboard/src/reducers/relation-add.ts

@@ -0,0 +1,38 @@
+/**
+ * 查字典,添加术语命令
+ */
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import type { RootState } from "../store";
+
+export interface IRelationParam {
+  book: number;
+  para: number;
+  src_sn?: string;
+  target_id?: string;
+  target_spell?: string;
+  command: "add" | "apply" | "cancel" | "finish";
+}
+interface IState {
+  param?: IRelationParam;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "relation-add",
+  initialState,
+  reducers: {
+    add: (state, action: PayloadAction<IRelationParam>) => {
+      state.param = action.payload;
+    },
+  },
+});
+
+export const { add } = slice.actions;
+
+export const relationAddParam = (
+  state: RootState
+): IRelationParam | undefined => state.relationAdd.param;
+
+export default slice.reducer;

+ 30 - 0
dashboard/src/reducers/relation.ts

@@ -0,0 +1,30 @@
+/**
+ * 从服务器获取的术语表
+ */
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+import { IRelation } from "../pages/admin/relation/list";
+
+import type { RootState } from "../store";
+
+interface IState {
+  relations?: IRelation[];
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "relation",
+  initialState,
+  reducers: {
+    pushRelation: (state, action: PayloadAction<IRelation[]>) => {
+      state.relations = action.payload;
+    },
+  },
+});
+
+export const { pushRelation } = slice.actions;
+
+export const getRelation = (state: RootState): IRelation[] | undefined =>
+  state.relation.relations;
+
+export default slice.reducer;

+ 4 - 0
dashboard/src/store.ts

@@ -15,6 +15,8 @@ import themeReducer from "./reducers/theme";
 import acceptPrReducer from "./reducers/accept-pr";
 import termVocabularyReducer from "./reducers/term-vocabulary";
 import nissayaEndingVocabularyReducer from "./reducers/nissaya-ending-vocabulary";
+import relationReducer from "./reducers/relation";
+import relationAddReducer from "./reducers/relation-add";
 
 const store = configureStore({
   reducer: {
@@ -33,6 +35,8 @@ const store = configureStore({
     acceptPr: acceptPrReducer,
     termVocabulary: termVocabularyReducer,
     nissayaEndingVocabulary: nissayaEndingVocabularyReducer,
+    relation: relationReducer,
+    relationAdd: relationAddReducer,
   },
 });