TermCommunity.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import {
  2. Badge,
  3. Card,
  4. Dropdown,
  5. MenuProps,
  6. Popover,
  7. Space,
  8. Typography,
  9. } from "antd";
  10. import { DownOutlined } from "@ant-design/icons";
  11. import { useState, useEffect } from "react";
  12. import { useIntl } from "react-intl";
  13. import { get } from "../../request";
  14. import { IUser } from "../auth/User";
  15. import { ITermListResponse } from "../api/Term";
  16. const { Title, Link, Text } = Typography;
  17. interface IItem<R> {
  18. value: R;
  19. score: number;
  20. }
  21. interface IWord {
  22. meaning: IItem<string>[];
  23. note: IItem<string>[];
  24. editor: IItem<IUser>[];
  25. }
  26. interface IWidget {
  27. word: string | undefined;
  28. }
  29. const TermCommunityWidget = ({ word }: IWidget) => {
  30. const intl = useIntl();
  31. const [show, setShow] = useState(false);
  32. const [wordData, setWordData] = useState<IWord>();
  33. const minScore = 100; //分数阈值。低于这个分数只显示在弹出菜单中
  34. useEffect(() => {
  35. if (typeof word === "undefined") {
  36. return;
  37. }
  38. const url = `/v2/terms?view=word&word=${word}&exp=1`;
  39. console.log("url", url);
  40. get<ITermListResponse>(url)
  41. .then((json) => {
  42. if (json.ok === false) {
  43. return;
  44. }
  45. let meaning = new Map<string, number>();
  46. let note = new Map<string, number>();
  47. let editorId = new Map<string, number>();
  48. let editor = new Map<string, IUser>();
  49. for (const it of json.data.rows) {
  50. let score: number | undefined;
  51. let currScore = 100;
  52. if (it.exp) {
  53. //分数计算
  54. currScore = Math.floor(it.exp / 3600);
  55. }
  56. if (it.meaning) {
  57. score = meaning.get(it.meaning);
  58. meaning.set(it.meaning, score ? score + currScore : currScore);
  59. }
  60. if (it.note) {
  61. score = note.get(it.note);
  62. const noteScore = it.note.length;
  63. note.set(it.note, score ? score + noteScore : noteScore);
  64. }
  65. if (it.editor) {
  66. score = editorId.get(it.editor.id);
  67. editorId.set(it.editor.id, score ? score + currScore : currScore);
  68. editor.set(it.editor.id, it.editor);
  69. }
  70. }
  71. let _data: IWord = {
  72. meaning: [],
  73. note: [],
  74. editor: [],
  75. };
  76. meaning.forEach((value, key, map) => {
  77. if (key && key.length > 0) {
  78. _data.meaning.push({ value: key, score: value });
  79. }
  80. });
  81. _data.meaning.sort((a, b) => b.score - a.score);
  82. note.forEach((value, key, map) => {
  83. if (key && key.length > 0) {
  84. _data.note.push({ value: key, score: value });
  85. }
  86. });
  87. _data.note.sort((a, b) => b.score - a.score);
  88. editorId.forEach((value, key, map) => {
  89. const currEditor = editor.get(key);
  90. if (currEditor) {
  91. _data.editor.push({ value: currEditor, score: value });
  92. }
  93. });
  94. _data.editor.sort((a, b) => b.score - a.score);
  95. setWordData(_data);
  96. if (_data.editor.length > 0) {
  97. setShow(true);
  98. }
  99. })
  100. .catch((error) => {
  101. console.error(error);
  102. });
  103. }, [word, setWordData]);
  104. const isShow = (score: number, index: number) => {
  105. const Ms = 500,
  106. Rd = 5,
  107. minScore = 15;
  108. const minOrder = Math.log(score) / Math.log(Math.pow(Ms, 1 / Rd));
  109. if (index < minOrder && score > minScore) {
  110. return true;
  111. } else {
  112. return false;
  113. }
  114. };
  115. const meaningLow = wordData?.meaning.filter(
  116. (value, index: number) => !isShow(value.score, index)
  117. );
  118. const meaningExtra = meaningLow?.map((item, id) => {
  119. return <span key={id}>{item.value}</span>;
  120. });
  121. const mainCollaboratorNum = 3; //默认显示的协作者数量,其余的在更多中显示
  122. const collaboratorRender = (name: string, id: number, score: number) => {
  123. return (
  124. <Space key={id}>
  125. {name}
  126. <Badge color="geekblue" size="small" count={score} />
  127. </Space>
  128. );
  129. };
  130. const items: MenuProps["items"] = wordData?.editor
  131. .filter((value, index) => index >= mainCollaboratorNum)
  132. .map((item, id) => {
  133. return {
  134. key: id,
  135. label: collaboratorRender(item.value.nickName, id, item.score),
  136. };
  137. });
  138. const more = wordData ? (
  139. wordData.editor.length > mainCollaboratorNum ? (
  140. <Dropdown menu={{ items }}>
  141. <Link>
  142. <Space>
  143. {intl.formatMessage({
  144. id: `buttons.more`,
  145. })}
  146. <DownOutlined />
  147. </Space>
  148. </Link>
  149. </Dropdown>
  150. ) : undefined
  151. ) : undefined;
  152. return show ? (
  153. <Card>
  154. <Title level={5} id={`community`}>
  155. {"社区术语"}
  156. </Title>
  157. <div key="meaning">
  158. <Space style={{ flexWrap: "wrap" }}>
  159. <Text strong>{"意思:"}</Text>
  160. {wordData?.meaning
  161. .filter((value, index: number) => isShow(value.score, index))
  162. .map((item, id) => {
  163. return (
  164. <Space key={id}>
  165. {item.value}
  166. <Badge color="geekblue" size="small" count={item.score} />
  167. </Space>
  168. );
  169. })}
  170. {meaningLow && meaningLow.length > 0 ? (
  171. <Popover content={<Space>{meaningExtra}</Space>} placement="bottom">
  172. <Link>
  173. <Space>
  174. {intl.formatMessage({
  175. id: `buttons.more`,
  176. })}
  177. <DownOutlined />
  178. </Space>
  179. </Link>
  180. </Popover>
  181. ) : undefined}
  182. </Space>
  183. </div>
  184. <div key="note">
  185. <Space style={{ flexWrap: "wrap" }}>
  186. <Text strong>{"note:"}</Text>
  187. {wordData?.note
  188. .filter((value) => value.score >= minScore)
  189. .map((item, id) => {
  190. return (
  191. <Space key={id}>
  192. {item.value}
  193. <Badge color="geekblue" size="small" count={item.score} />
  194. </Space>
  195. );
  196. })}
  197. </Space>
  198. </div>
  199. <div key="collaborator">
  200. <Space style={{ flexWrap: "wrap" }}>
  201. <Text strong>{"贡献者:"}</Text>
  202. {wordData?.editor
  203. .filter((value, index) => index < mainCollaboratorNum)
  204. .map((item, id) => {
  205. return collaboratorRender(item.value.nickName, id, item.score);
  206. })}
  207. {more}
  208. </Space>
  209. </div>
  210. </Card>
  211. ) : (
  212. <></>
  213. );
  214. };
  215. export default TermCommunityWidget;