| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- import {
- Badge,
- Button,
- Card,
- Dropdown,
- type MenuProps,
- Popover,
- Skeleton,
- Space,
- Typography,
- } from "antd";
- import { DownOutlined } from "@ant-design/icons";
- import { useState, useEffect, useCallback } from "react";
- import { useIntl } from "react-intl";
- import { get } from "../../request";
- import type { IApiResponseDictList } from "../../api/Dict";
- import type { IUser } from "../auth/User";
- import GrammarPop from "./GrammarPop";
- import MdView from "../template/MdView";
- import MyCreate from "./MyCreate";
- const { Title, Link, Text } = Typography;
- interface IItem<R> {
- value: R;
- score: number;
- }
- interface IWord {
- grammar: IItem<string>[];
- parent: IItem<string>[];
- note: IItem<string>[];
- meaning: IItem<string>[];
- factors: IItem<string>[];
- editor: IItem<IUser>[];
- }
- interface IWidget {
- word: string | undefined;
- }
- const CommunityWidget = ({ word }: IWidget) => {
- const intl = useIntl();
- const [loaded, setLoaded] = useState(false);
- const [loading, setLoading] = useState(false);
- const [wordData, setWordData] = useState<IWord>();
- const [showCreate, setShowCreate] = useState(false);
- const [_myRefresh, setMyRefresh] = useState(false);
- const minScore = 100; //分数阈值。低于这个分数只显示在弹出菜单中
- const dictLoad = useCallback(async (input: string) => {
- setLoading(true);
- const url = `/v2/userdict?view=community&word=${input}`;
- console.info("dict community url", url);
- get<IApiResponseDictList>(url)
- .then((json) => {
- if (json.ok === false) {
- console.log("dict community", json.message);
- return;
- }
- console.debug("dict community", json.data);
- const meaning = new Map<string, number>();
- const grammar = new Map<string, number>();
- const parent = new Map<string, number>();
- const note = new Map<string, number>();
- const editorId = new Map<string, number>();
- const editor = new Map<string, IUser>();
- for (const it of json.data.rows) {
- let score: number | undefined;
- if (it.exp) {
- //分数计算
- let conf = it.confidence / 100;
- if (it.confidence <= 1) {
- conf = 1;
- }
- const currScore = Math.floor((it.exp / 3600) * conf);
- if (it.mean) {
- score = meaning.get(it.mean);
- meaning.set(it.mean, score ? score + currScore : currScore);
- }
- if (it.type || it.grammar) {
- const strCase = it.type + "$" + it.grammar;
- score = grammar.get(strCase);
- grammar.set(strCase, score ? score + currScore : currScore);
- }
- if (it.parent) {
- score = parent.get(it.parent);
- parent.set(it.parent, score ? score + currScore : currScore);
- }
- if (it.note) {
- score = note.get(it.note);
- note.set(it.note, score ? score + currScore : currScore);
- }
- if (it.editor) {
- score = editorId.get(it.editor.id);
- editorId.set(it.editor.id, score ? score + currScore : currScore);
- editor.set(it.editor.id, it.editor);
- }
- }
- }
- const _data: IWord = {
- grammar: [],
- parent: [],
- note: [],
- meaning: [],
- factors: [],
- editor: [],
- };
- meaning.forEach((value, key, _map) => {
- if (key && key.length > 0) {
- _data.meaning.push({ value: key, score: value });
- }
- });
- _data.meaning.sort((a, b) => b.score - a.score);
- grammar.forEach((value, key, _map) => {
- if (key && key.length > 0) {
- _data.grammar.push({ value: key, score: value });
- }
- });
- _data.grammar.sort((a, b) => b.score - a.score);
- parent.forEach((value, key, _map) => {
- if (key && key.length > 0) {
- _data.parent.push({ value: key, score: value });
- }
- });
- _data.parent.sort((a, b) => b.score - a.score);
- note.forEach((value, key, _map) => {
- if (key && key.length > 0) {
- _data.note.push({ value: key, score: value });
- }
- });
- _data.note.sort((a, b) => b.score - a.score);
- editorId.forEach((value, key, _map) => {
- const currEditor = editor.get(key);
- if (currEditor) {
- _data.editor.push({ value: currEditor, score: value });
- }
- });
- _data.editor.sort((a, b) => b.score - a.score);
- setWordData(_data);
- if (_data.editor.length > 0) {
- setLoaded(true);
- } else {
- setLoaded(false);
- }
- })
- .finally(() => setLoading(false))
- .catch((error) => {
- console.error(error);
- });
- }, []);
- useEffect(() => {
- if (typeof word === "undefined") {
- return;
- }
- dictLoad(word);
- }, [word, setWordData, dictLoad]);
- const isShow = (score: number, index: number) => {
- const Ms = 500,
- Rd = 5,
- minScore = 15;
- const minOrder = Math.log(score) / Math.log(Math.pow(Ms, 1 / Rd));
- if (index < minOrder && score > minScore) {
- return true;
- } else {
- return false;
- }
- };
- const meaningLow = wordData?.meaning.filter(
- (value, index: number) => !isShow(value.score, index)
- );
- const meaningExtra = meaningLow?.map((item, id) => {
- return <span key={id}>{item.value}</span>;
- });
- const mainCollaboratorNum = 3; //默认显示的协作者数量,其余的在更多中显示
- const collaboratorRender = (name: string, id: number, score: number) => {
- return (
- <Space key={id}>
- {name}
- <Badge color="geekblue" size="small" count={score} />
- </Space>
- );
- };
- const items: MenuProps["items"] = wordData?.editor
- .filter((_value, index) => index >= mainCollaboratorNum)
- .map((item, id) => {
- return {
- key: id,
- label: collaboratorRender(item.value.nickName, id, item.score),
- };
- });
- const more = wordData ? (
- wordData.editor.length > mainCollaboratorNum ? (
- <Dropdown menu={{ items }}>
- <Link>
- <Space>
- {intl.formatMessage({
- id: `buttons.more`,
- })}
- <DownOutlined />
- </Space>
- </Link>
- </Dropdown>
- ) : undefined
- ) : undefined;
- return (
- <Card>
- <Title level={5} id={`community`}>
- {"社区字典"}
- </Title>
- {loading ? (
- <Skeleton />
- ) : loaded ? (
- <div>
- <div key="meaning">
- <Space style={{ flexWrap: "wrap" }}>
- <Text strong>{"意思:"}</Text>
- {wordData?.meaning
- .filter((value, index: number) => isShow(value.score, index))
- .map((item, id) => {
- return (
- <Space key={id}>
- {item.value}
- <Badge color="geekblue" size="small" count={item.score} />
- </Space>
- );
- })}
- {meaningLow && meaningLow.length > 0 ? (
- <Popover
- content={<Space>{meaningExtra}</Space>}
- placement="bottom"
- >
- <Link>
- <Space>
- {intl.formatMessage({
- id: `buttons.more`,
- })}
- <DownOutlined />
- </Space>
- </Link>
- </Popover>
- ) : undefined}
- </Space>
- </div>
- <div key="grammar">
- <Space style={{ flexWrap: "wrap" }}>
- <Text strong>{"语法:"}</Text>
- {wordData?.grammar
- .filter((value) => value.score >= minScore)
- .map((item, id) => {
- const grammar = item.value.split("$");
- const grammarGuide = grammar.map((item, id) => {
- const strCase = item.replaceAll(".", "");
- return strCase.length > 0 ? (
- <GrammarPop
- key={id}
- gid={strCase}
- text={intl.formatMessage({
- id: `dict.fields.type.${strCase}.label`,
- defaultMessage: strCase,
- })}
- />
- ) : undefined;
- });
- return (
- <Space key={id}>
- <Space
- style={{
- backgroundColor: "rgba(0.5,0.5,0.5,0.2)",
- borderRadius: 5,
- paddingLeft: 5,
- paddingRight: 5,
- }}
- >
- {grammarGuide}
- </Space>
- <Badge color="geekblue" size="small" count={item.score} />
- </Space>
- );
- })}
- </Space>
- </div>
- <div key="base">
- <Space style={{ flexWrap: "wrap" }}>
- <Text strong>{"词干:"}</Text>
- {wordData?.parent
- .filter((value) => value.score >= minScore)
- .map((item, id) => {
- return (
- <Space key={id}>
- {item.value}
- <Badge color="geekblue" size="small" count={item.score} />
- </Space>
- );
- })}
- </Space>
- </div>
- <div key="collaborator">
- <Space style={{ flexWrap: "wrap" }}>
- <Text strong>{"贡献者:"}</Text>
- {wordData?.editor
- .filter((_value, index) => index < mainCollaboratorNum)
- .map((item, id) => {
- return collaboratorRender(
- item.value.nickName,
- id,
- item.score
- );
- })}
- {more}
- </Space>
- </div>
- <div key="note">
- <Text strong>{"注释:"}</Text>
- <div>
- {wordData?.note
- .filter((value) => value.score >= minScore)
- .slice(0, 1)
- .map((item, id) => {
- return <MdView html={item.value} key={id} />;
- })}
- </div>
- </div>
- </div>
- ) : showCreate ? (
- <MyCreate
- word={word}
- onSave={() => {
- setMyRefresh(true);
- if (word) {
- dictLoad(word);
- }
- }}
- />
- ) : (
- <>
- <Button type="link" onClick={() => setShowCreate(true)}>
- 新建
- </Button>
- </>
- )}
- </Card>
- );
- };
- export default CommunityWidget;
|