2025-03-21 13:09:08 +08:00

180 lines
8.0 KiB
TypeScript

import React, {useMemo} from "react";
import {RightOutlined} from "@ant-design/icons";
import {Popover, Space} from "antd";
import {css} from "@emotion/css";
import {DropdownMenu} from "@/components/popover/dropdown-menu";
import {
ProofreadStateEnum,
ProofreadTypeEnum,
TCorrectedContent,
} from "@/service/types/document";
import {showToast} from "@/components/messages/Modal.tsx";
import {whitelist} from "@/service/api/lexicon.ts";
import {useSetState} from "ahooks";
import {
IconAccept,
IconDotMore2,
IconIgnore,
IconPlus,
IconReview,
SvgIcon
} from "./icons.tsx";
export type ActionKey = 'click' | 'accept' | 'acceptAll' | 'ignore' | 'ignoreAll' | 'addToLexicon' | 'review' | 'redo'
type ProofreadItemProps = {
selected?: boolean;
className?: string | undefined;
previewMode?: boolean;
last?: boolean;
onAction: (key: ActionKey) => void
it: TCorrectedContent
index?: number;
}
const ActionToolTip = (props: {open?:boolean; text: string; children: React.ReactNode }) => (
<Popover
open={props.open}
overlayClassName="black-bg"
content={props.text}
>{props.children}</Popover>)
function getDescription(it: TCorrectedContent) {
// 获取类型数据
// const typeData = getTypeData(it.type)!;
const str = [`${it.typeData?.text}`]
if (it.typeData?.type === 'black' || it.type == ProofreadTypeEnum.sensitive) {
str.push('请复核',it.tag == 'r' && !/^[*]+$/.test(it.text) ?`,将选用"${it.text}"`:'')
} else if (it.tag == 'r') {
str.push(`建议选用"${it.text}"`)
} else {
str.push('建议' + (it.tag == 'd' ? '删除' : '新增'))
}
return str.join('');
// return typeData?.text ? typeData?.text + ',' : ''}{props.it.tag == 'r' ? `选用"${props.it.text}"` : (
// it.tag == 'd' ? '删除' : '新增'
}
export const ProofreadItem: React.FC<ProofreadItemProps> = (props) => {
const requireReview = useMemo(() => {
// 敏感词或者要拦截的黑名单
return props.it.type == ProofreadTypeEnum.sensitive || props.it.type == ProofreadTypeEnum.blackWordBlock || (
props.it.type == ProofreadTypeEnum.blackWord
&& props.it.tag == 'r'
&& /^[*]+$/.test(props.it.text)
);
}, [props.it])
// 当前类别颜色
const colorStyle = css`--proofread-color: ${props.it.typeData?.color || '#000000'}`
const [state, setState] = useSetState({
addToLexiconVisible: true
})
// 添加到词库
const handleAddToLexicon = () => {
whitelist.create({
word: props.it.origin
}).then(() => {
showToast('加入白名单成功', 'success');
// 隐藏添加按钮
setState({
addToLexiconVisible: false
})
}).catch((e) => {
console.log(e);
showToast('加入白名单失败', 'warning')
})
}
// const handleUndo = async () => {
// showToast('撤销成功')
// }
const AddToLexiconButton = ({disabled}:{disabled:boolean}) => {
const AddButton = <button disabled={disabled} className="btn btn-add-to-lexicon" onClick={handleAddToLexicon}>
<SvgIcon component={IconPlus}/>
</button>;
return (props.selected && state.addToLexiconVisible && props.it.type != ProofreadTypeEnum.blackWord) ?
disabled ? AddButton : (<ActionToolTip text="加入白名单">
{AddButton}
</ActionToolTip>) : null
}
return (<>
<div
onClick={() => props.onAction('click')}
className={`proofread-item ${props.it.isAccept == ProofreadStateEnum.Default?'':'processed'} proofread-action-${props.it.action} proofread-item-${props.it.id} ${props.className} ${colorStyle} item ${props.selected ? 'select' : ''}`}>
<div className="info">
<div className="text d-flex align-center">
<span className="origin">{
props.it.tag == 'i' ? props.it.text : (
props.it.origin == ' ' ? '空格' : props.it.origin
)
}</span>
{
props.it.type == ProofreadTypeEnum.blackWord ?
<span className="action-text"></span> : (props.it.typeData?.type == 'sensitive' ?
<span className="action-text"></span> : (props.it.tag == 'r'
? <>
<span className="arrow"><RightOutlined/></span>
<span className="replaced">{props.it.text}</span>
</>
: <span className="action-text">{props.it.tag == 'd' ? '删除' : '新增'}</span>))
}
</div>
<div className="description">
{getDescription(props.it)}
</div>
</div>
{!props.previewMode && <div className="action align-center">
{props.selected && (props.it.isAccept == ProofreadStateEnum.Default) && <Space align={'center'}>
{!requireReview && <ActionToolTip text="采纳">
<button className="btn btn-accept" onClick={() => props.onAction('accept')}>
<SvgIcon component={IconAccept}/>
</button>
</ActionToolTip>}
{requireReview && <ActionToolTip text="复核">
<button className="btn btn-review" onClick={() => props.onAction('review')}>
<SvgIcon component={IconReview}/>
</button>
</ActionToolTip>}
<ActionToolTip text="忽略">
<button className="btn btn-odd btn-ignore" onClick={() => props.onAction('ignore')}>
<SvgIcon component={IconIgnore}/>
</button>
</ActionToolTip>
<AddToLexiconButton disabled={requireReview}/>
<DropdownMenu
theme="dark"
items={!requireReview ? [
{key: 'acceptAll', label: '采纳全部相同结果', onClick: () => props.onAction('acceptAll')},
{type: 'divider'},
{key: 'accept', label: '忽略全部相同结果', onClick: () => props.onAction('ignoreAll')},
] : [{key: 'accept', label: '忽略全部相同结果', onClick: () => props.onAction('ignoreAll')}]}
>
<div className="more-action"><SvgIcon component={IconDotMore2} size={12}/></div>
</DropdownMenu>
</Space>}
{props.it.isAccept != ProofreadStateEnum.Default &&
<div className="processed-wrapper align-center">
{props.it.isAccept == ProofreadStateEnum.Accept && <div className="state-accept align-center">
<SvgIcon component={IconAccept}/>
<span className="ml-2"></span>
</div>}
{props.it.isAccept == ProofreadStateEnum.Review &&
<div className="state-accept state-review align-center">
<SvgIcon component={IconReview}/>
<span className="ml-2"></span>
</div>}
{props.it.isAccept == ProofreadStateEnum.Ignore && <div className="state-ignore align-center">
<SvgIcon component={IconIgnore}/>
<span className="ml-2"></span>
</div>}
</div>}
</div>}
</div>
{props.selected && !props.last && <div className={'selected-line'}></div>}
</>)
}