TermEdit.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import { useIntl } from "react-intl";
  2. import {
  3. ProForm,
  4. ProFormCheckbox,
  5. ProFormDependency,
  6. type ProFormInstance,
  7. ProFormSelect,
  8. ProFormSwitch,
  9. ProFormText,
  10. } from "@ant-design/pro-components";
  11. import LangSelect from "../general/LangSelect";
  12. import ChannelSelect from "../channel/ChannelSelect";
  13. import {
  14. Alert,
  15. AutoComplete,
  16. Button,
  17. Form,
  18. Input,
  19. message,
  20. Space,
  21. Tag,
  22. } from "antd";
  23. import { useEffect, useRef, useState } from "react";
  24. import type {
  25. ITermCreateResponse,
  26. ITermDataRequest,
  27. ITermListResponse,
  28. ITermResponse,
  29. } from "../../api/Term";
  30. import { get, post, put } from "../../request";
  31. import MDEditor from "@uiw/react-md-editor";
  32. import { useAppSelector } from "../../hooks";
  33. import { currentUser as _currentUser } from "../../reducers/current-user";
  34. import store from "../../store";
  35. import { push } from "../../reducers/term-vocabulary";
  36. interface ValueType {
  37. key?: string;
  38. label: React.ReactNode;
  39. value: string | number;
  40. }
  41. interface IWidget {
  42. id?: string;
  43. word?: string;
  44. tags?: string[];
  45. studioName?: string;
  46. channelId?: string;
  47. parentChannelId?: string;
  48. parentStudioId?: string;
  49. community?: boolean;
  50. onUpdate?: Function;
  51. }
  52. const TermEditWidget = ({
  53. id,
  54. word,
  55. tags,
  56. channelId,
  57. studioName,
  58. parentChannelId,
  59. parentStudioId,
  60. community = false,
  61. onUpdate,
  62. }: IWidget) => {
  63. const intl = useIntl();
  64. const [meaningOptions, setMeaningOptions] = useState<ValueType[]>([]);
  65. const [readonly, setReadonly] = useState(false);
  66. const [isSaveAs, setIsSaveAs] = useState(false);
  67. const [currChannel, setCurrChannel] = useState<ValueType[]>([]);
  68. const user = useAppSelector(_currentUser);
  69. const [form] = Form.useForm<ITerm>();
  70. const formRef = useRef<ProFormInstance | undefined>(undefined);
  71. useEffect(() => {
  72. if (word) {
  73. const url = `/v2/terms?view=word&word=${word}`;
  74. console.info("api request", url);
  75. get<ITermListResponse>(url).then((json) => {
  76. const meaning = json.data.rows.map((item) => item.meaning);
  77. const meaningMap = new Map<string, number>();
  78. for (const it of meaning) {
  79. const count = meaningMap.get(it);
  80. if (typeof count === "undefined") {
  81. meaningMap.set(it, 1);
  82. } else {
  83. meaningMap.set(it, count + 1);
  84. }
  85. }
  86. const meaningList: ValueType[] = [];
  87. meaningMap.forEach((value, key, _map) => {
  88. meaningList.push({
  89. value: key,
  90. label: (
  91. <Space
  92. style={{ display: "flex", justifyContent: "space-between" }}
  93. >
  94. {key}
  95. <Tag>{value}</Tag>
  96. </Space>
  97. ),
  98. });
  99. });
  100. setMeaningOptions(meaningList);
  101. });
  102. }
  103. }, [word]);
  104. let channelDisable = false;
  105. if (community) {
  106. channelDisable = true;
  107. }
  108. if (readonly) {
  109. channelDisable = true;
  110. }
  111. if (id) {
  112. channelDisable = true;
  113. }
  114. return (
  115. <>
  116. {community ? (
  117. <Alert
  118. message="该资源为社区数据,您可以修改并保存到一个您有修改权限的版本中。"
  119. type="info"
  120. closable
  121. action={
  122. <Button disabled size="small" type="text">
  123. 详情
  124. </Button>
  125. }
  126. />
  127. ) : readonly ? (
  128. <Alert
  129. message="该资源为只读,如果需要修改,请联络拥有者分配权限。或者您可以在下面的版本选择中选择另一个版本,将该术语保存到一个您有修改权限的版本中。"
  130. type="warning"
  131. closable
  132. action={
  133. <Button disabled size="small" type="text">
  134. 详情
  135. </Button>
  136. }
  137. />
  138. ) : undefined}
  139. <ProForm<ITerm>
  140. form={form}
  141. formRef={formRef}
  142. autoFocusFirstInput={true}
  143. onFinish={async (values: ITerm) => {
  144. console.log("term submit", values);
  145. if (
  146. typeof values.word === "undefined" ||
  147. typeof values.meaning === "undefined"
  148. ) {
  149. return;
  150. }
  151. let copy_channel = "";
  152. if (values.copy_channel && values.copy_channel.length > 0) {
  153. copy_channel = values.copy_channel[values.copy_channel.length - 1];
  154. }
  155. const newValue = {
  156. id: values.id,
  157. word: values.word,
  158. tag: values.tag,
  159. meaning: values.meaning,
  160. other_meaning: values.meaning2?.join(),
  161. note: values.note,
  162. channel: values.save_as ? copy_channel : values.channelId,
  163. parent_channel_id: parentChannelId,
  164. studioName: studioName,
  165. studioId: parentStudioId,
  166. language: values.save_as ? values.copy_lang : values.lang,
  167. pr: values.save_as ? values.pr : undefined,
  168. };
  169. console.log("value", newValue);
  170. let res: ITermResponse;
  171. if (typeof values.id === "undefined" || community || values.save_as) {
  172. const url = `/v2/terms?community_summary=1`;
  173. console.info("api request", url, newValue);
  174. res = await post<ITermDataRequest, ITermResponse>(url, newValue);
  175. } else {
  176. const url = `/v2/terms/${values.id}?community_summary=1`;
  177. console.info("api request", url, newValue);
  178. res = await put<ITermDataRequest, ITermResponse>(url, newValue);
  179. }
  180. console.debug("api response", res);
  181. if (res.ok) {
  182. message.success("提交成功");
  183. store.dispatch(
  184. push({ word: res.data.word, meaning: res.data.meaning })
  185. );
  186. if (typeof onUpdate !== "undefined") {
  187. onUpdate(res.data);
  188. }
  189. } else {
  190. message.error(res.message);
  191. }
  192. return true;
  193. }}
  194. request={async () => {
  195. let url: string;
  196. let data: ITerm = {
  197. word: word ? word : "",
  198. tag: tags?.join(),
  199. meaning: "",
  200. meaning2: [],
  201. note: "",
  202. lang: "",
  203. copy_channel: [],
  204. };
  205. if (typeof id !== "undefined") {
  206. // 如果是编辑,就从服务器拉取数据。
  207. url = "/v2/terms/" + id;
  208. console.info("TermEdit is edit api request", url);
  209. const res = await get<ITermResponse>(url);
  210. console.debug("TermEdit is edit api response", res);
  211. if (res.ok) {
  212. let meaning2: string[] = [];
  213. if (res.data.other_meaning) {
  214. meaning2 = res.data.other_meaning.split(",");
  215. }
  216. let realChannelId: string | undefined = "";
  217. if (parentStudioId) {
  218. if (user?.id === parentStudioId) {
  219. if (community) {
  220. realChannelId = "";
  221. } else {
  222. realChannelId = res.data.channel?.id;
  223. }
  224. } else {
  225. realChannelId = parentChannelId;
  226. }
  227. } else {
  228. if (res.data.channel) {
  229. realChannelId = res.data.channel?.id;
  230. setCurrChannel([
  231. {
  232. label: res.data.channel?.name,
  233. value: res.data.channel?.id,
  234. },
  235. ]);
  236. }
  237. }
  238. let copyToChannel: string[] = [];
  239. if (parentChannelId) {
  240. if (user?.roles?.includes("basic")) {
  241. copyToChannel = [parentChannelId];
  242. } else {
  243. copyToChannel = [""];
  244. }
  245. }
  246. data = {
  247. id: res.data.guid,
  248. word: res.data.word,
  249. tag: res.data.tag,
  250. meaning: res.data.meaning,
  251. meaning2: meaning2,
  252. note: res.data.note ? res.data.note : "",
  253. lang: res.data.language,
  254. channelId: realChannelId,
  255. copy_channel: copyToChannel,
  256. };
  257. if (res.data.role === "reader" || res.data.role === "unknown") {
  258. setReadonly(true);
  259. }
  260. }
  261. } else if (typeof parentChannelId !== "undefined") {
  262. /**
  263. * 在channel新建
  264. * basic:仅保存在这个版本
  265. * pro: 默认studio通用
  266. */
  267. url = `/v2/terms?view=create-by-channel&channel=${parentChannelId}&word=${word}`;
  268. console.info("api request 在channel新建", url);
  269. const res = await get<ITermCreateResponse>(url);
  270. console.debug("api response", res);
  271. let channelId = "";
  272. let copyToChannel: string[] = [];
  273. if (user?.roles?.includes("basic")) {
  274. channelId = parentChannelId;
  275. copyToChannel = [parentChannelId];
  276. } else {
  277. channelId = user?.id === parentStudioId ? "" : parentChannelId;
  278. copyToChannel = [res.data.studio.id, parentChannelId];
  279. }
  280. data = {
  281. word: word ? word : "",
  282. tag: tags?.join(),
  283. meaning: "",
  284. meaning2: [],
  285. note: "",
  286. lang: res.data.language,
  287. channelId: channelId,
  288. copy_channel: copyToChannel,
  289. };
  290. } else if (typeof studioName !== "undefined") {
  291. //在studio新建
  292. url = `/v2/terms?view=create-by-studio&studio=${studioName}&word=${word}`;
  293. console.debug("在 studio 新建", url);
  294. }
  295. return data;
  296. }}
  297. >
  298. <ProForm.Group>
  299. <ProFormText width="md" name="id" hidden />
  300. <ProFormText
  301. width="md"
  302. name="word"
  303. initialValue={word}
  304. required
  305. label={intl.formatMessage({
  306. id: "term.fields.word.label",
  307. })}
  308. rules={[
  309. {
  310. required: true,
  311. },
  312. ]}
  313. fieldProps={{
  314. showCount: true,
  315. maxLength: 128,
  316. }}
  317. />
  318. <ProFormText
  319. width="md"
  320. name="tag"
  321. tooltip={intl.formatMessage({
  322. id: "term.fields.description.tooltip",
  323. })}
  324. label={intl.formatMessage({
  325. id: "term.fields.description.label",
  326. })}
  327. />
  328. </ProForm.Group>
  329. <ProForm.Group>
  330. <ProForm.Item
  331. name="meaning"
  332. label={intl.formatMessage({
  333. id: "term.fields.meaning.label",
  334. })}
  335. rules={[
  336. {
  337. required: true,
  338. },
  339. ]}
  340. >
  341. <AutoComplete
  342. options={meaningOptions}
  343. onChange={(_value: any) => {}}
  344. maxLength={128}
  345. >
  346. <Input width="md" allowClear showCount={true} />
  347. </AutoComplete>
  348. </ProForm.Item>
  349. <ProFormSelect
  350. width="md"
  351. name="meaning2"
  352. label={intl.formatMessage({
  353. id: "term.fields.meaning2.label",
  354. })}
  355. fieldProps={{
  356. mode: "tags",
  357. tokenSeparators: [",", ","],
  358. }}
  359. placeholder="Please select other meanings"
  360. rules={[
  361. {
  362. type: "array",
  363. },
  364. ]}
  365. />
  366. </ProForm.Group>
  367. <ProForm.Group>
  368. <ProFormSelect
  369. name="channelId"
  370. allowClear
  371. label="版本(已经建立的术语,版本不可修改。可以选择另存为复制到另一个版本。)"
  372. width="md"
  373. placeholder={intl.formatMessage({
  374. id: "term.general-in-studio",
  375. })}
  376. disabled={channelDisable}
  377. options={[
  378. {
  379. value: "",
  380. label: intl.formatMessage({
  381. id: "term.general-in-studio",
  382. }),
  383. disabled:
  384. user?.id !== parentStudioId || user?.roles?.includes("basic"),
  385. },
  386. {
  387. value: parentChannelId ?? channelId,
  388. label: "仅用于此版本",
  389. disabled: !community && readonly,
  390. },
  391. ...currChannel,
  392. ]}
  393. />
  394. <ProFormDependency name={["channelId"]}>
  395. {({ channelId }) => {
  396. const hasChannel = channelId
  397. ? channelId === ""
  398. ? false
  399. : true
  400. : false;
  401. return (
  402. <LangSelect
  403. disabled={hasChannel || channelDisable}
  404. required={!hasChannel}
  405. />
  406. );
  407. }}
  408. </ProFormDependency>
  409. </ProForm.Group>
  410. <ProForm.Group>
  411. <Form.Item
  412. style={{ width: "100%" }}
  413. name="note"
  414. label={intl.formatMessage({ id: "forms.fields.note.label" })}
  415. >
  416. <MDEditor />
  417. </Form.Item>
  418. </ProForm.Group>
  419. <ProForm.Group>
  420. <ProFormSwitch
  421. name="save_as"
  422. label="另存为"
  423. fieldProps={{
  424. onChange: (
  425. checked: boolean,
  426. _event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  427. ) => {
  428. setIsSaveAs(checked);
  429. },
  430. }}
  431. />
  432. </ProForm.Group>
  433. <ProForm.Group style={{ display: isSaveAs ? "block" : "none" }}>
  434. <ChannelSelect
  435. channelId={channelId}
  436. parentChannelId={parentChannelId}
  437. parentStudioId={parentStudioId}
  438. width="md"
  439. name="copy_channel"
  440. placeholder={intl.formatMessage({
  441. id: "term.general-in-studio",
  442. })}
  443. allowClear={user?.roles?.includes("basic") ? false : true}
  444. tooltip={intl.formatMessage({
  445. id: "term.fields.channel.tooltip",
  446. })}
  447. label={intl.formatMessage({
  448. id: "term.fields.channel.label",
  449. })}
  450. />
  451. <ProFormDependency name={["copy_channel"]}>
  452. {({ copy_channel }) => {
  453. const hasChannel = copy_channel
  454. ? copy_channel.length === 0 || copy_channel[0] === ""
  455. ? false
  456. : true
  457. : false;
  458. return (
  459. <LangSelect
  460. name="copy_lang"
  461. disabled={hasChannel}
  462. required={isSaveAs && !hasChannel}
  463. />
  464. );
  465. }}
  466. </ProFormDependency>
  467. </ProForm.Group>
  468. <ProForm.Group style={{ display: isSaveAs ? "block" : "none" }}>
  469. <ProFormCheckbox disabled name="pr">
  470. 同时提交修改建议
  471. </ProFormCheckbox>
  472. </ProForm.Group>
  473. </ProForm>
  474. </>
  475. );
  476. };
  477. export default TermEditWidget;