index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import {Button, Input, message, Modal} from 'antd';
  2. import React, {useEffect, useState} from 'react';
  3. import {PageHeaderWrapper} from '@ant-design/pro-layout';
  4. import {useSingleState} from 'nice-hooks';
  5. import Quill from '@/pages/Memory/components/Quill';
  6. import UpdateForm from '@/pages/Memory/components/UpdateForm';
  7. import {TableListItem} from '../MemoryList/data.d';
  8. import service from './service';
  9. import styles from './index.less';
  10. import {DownOutlined, ExclamationCircleOutlined} from '@ant-design/icons/lib';
  11. import {getRandom, getTextFromHtml} from '@/utils/utils';
  12. const {TextArea} = Input;
  13. enum Step {
  14. MAIN = 0,
  15. MEMORY = 1,
  16. ADD = 2,
  17. }
  18. interface State {
  19. remindCount: number;
  20. step: Step;
  21. showBack?: boolean;
  22. }
  23. const practice = "practice";
  24. const TableList: React.FC<{}> = () => {
  25. const [btnDisable, setBtnDisable] = useState(false);
  26. const [showMore, setShowMore] = useState(false);
  27. const [backPractice, setBackPractice] = useState("");
  28. let counting = false;
  29. const {confirm} = Modal;
  30. const [state, setState] = useSingleState<State>({
  31. remindCount: 0,
  32. step: Step.MAIN, // 0 主页,1 复习,2 添加
  33. showBack: false,
  34. });
  35. const [memory, setMemory] = useSingleState<TableListItem>({
  36. back: '',
  37. front: '',
  38. id: undefined,
  39. period: 0,
  40. remindTime: new Date(),
  41. tag: '',
  42. updateTime: '',
  43. userId: 0,
  44. });
  45. const isPracticeFocus = () => {
  46. // @ts-ignore
  47. return document.activeElement.id === practice;
  48. }
  49. /**
  50. * 查询用户待提醒数量
  51. * @param fields
  52. */
  53. const countRemind = async () => {
  54. // 如果上一个查询还没有结束,则直接返回
  55. // console.log(counting);
  56. if (counting) {
  57. return;
  58. }
  59. counting = true;
  60. const hide = message.loading('正在查询');
  61. try {
  62. const res = await service.countRemind({});
  63. hide();
  64. if (res.success) {
  65. setState({remindCount: parseInt(res.data, 10)});
  66. }
  67. counting = false;
  68. } catch (error) {
  69. hide();
  70. message.error('查询异常!');
  71. counting = false;
  72. }
  73. };
  74. /**
  75. * 清空memory
  76. */
  77. const clearMemory = () => {
  78. setMemory({
  79. id: undefined,
  80. front: '',
  81. back: '',
  82. });
  83. };
  84. /**
  85. * 处理返回值
  86. * @param res
  87. */
  88. const memoryRes = (res: any) => {
  89. // 只要有返回值,就使按钮可见
  90. setBtnDisable(false);
  91. if (res.success) {
  92. setMemory({...memory, ...res.data});
  93. } else {
  94. setState({step: Step.MAIN});
  95. }
  96. };
  97. const memorySuccess = async (factorInt: number) => {
  98. setBtnDisable(true);
  99. memoryRes(await service.memorySuccess({id: memory.id, factor: factorInt * getRandom()}));
  100. };
  101. const findNextLine = async () => {
  102. const res = await service.findNext({});
  103. if (res.success) {
  104. setMemory({...memory, ...res.data});
  105. } else {
  106. setState({step: Step.MAIN});
  107. }
  108. };
  109. // 绑定快捷键
  110. const bindShortKey = () => {
  111. document.onkeydown = (ev) => {
  112. // 在input有焦点的时候
  113. if (isPracticeFocus()) {
  114. // 按esc
  115. if (ev.key.toLowerCase() === 'escape') {
  116. // @ts-ignore
  117. document.getElementById(practice).blur();
  118. }
  119. // 其他情况
  120. return;
  121. }
  122. if (ev.key === 'i' && state.step === Step.MEMORY && !isPracticeFocus()) {
  123. ev.preventDefault();
  124. // @ts-ignore
  125. document.getElementById(practice).focus();
  126. }
  127. // 空格显示背面和默认选择
  128. if (ev.key === ' ' && state.step === Step.MEMORY) {
  129. ev.preventDefault(); // 关闭浏览器快捷键
  130. if (state.showBack === false) {
  131. setState({showBack: true});
  132. } else {
  133. memorySuccess(2);
  134. }
  135. } else if (ev.key === ' ' && state.step === Step.MAIN) {
  136. // 空格刷新
  137. // 如果待复习大于0,则直接进去
  138. if (state.remindCount > 0) {
  139. setState({step: Step.MEMORY});
  140. } else {
  141. // 刷新一下,再判断是否应该去复习
  142. countRemind();
  143. if (state.remindCount > 0) {
  144. setState({step: Step.MEMORY});
  145. }
  146. }
  147. } else if (ev.key.toLowerCase() === 'e') {
  148. // 快捷 E,进入编辑、新建页面
  149. setState({step: Step.ADD});
  150. } else if (ev.key.toLowerCase() === 'escape') {
  151. // esc键回到主页
  152. setState({step: Step.MAIN});
  153. }
  154. };
  155. };
  156. // 判断背面是否是空
  157. const isBackEmpty=()=>{
  158. return getTextFromHtml(memory.back).length === 0 && memory.back.indexOf('img') < 0;
  159. }
  160. useEffect(() => {
  161. countRemind();
  162. setTimeout(() => {
  163. countRemind();
  164. }, 1000 * 60 * 60); // 每隔一个小时查询一次
  165. bindShortKey();
  166. }, []);
  167. useEffect(() => {
  168. // 关闭背面,清空 memory
  169. setState({showBack: false});
  170. clearMemory();
  171. if (state.step === Step.MAIN) {
  172. // 进入统计页面
  173. countRemind();
  174. } else if (state.step === Step.MEMORY) {
  175. findNextLine();
  176. }
  177. }, [state.step]);
  178. useEffect(() => {
  179. // 判断是否为主页面刷新
  180. if (memory.front.length === 0) {
  181. return;
  182. }
  183. // 进入复习页面后
  184. // 如果没有文字,并且也没有图片
  185. if (isBackEmpty()) {
  186. setState({showBack: true});
  187. } else {
  188. setState({showBack: false});
  189. }
  190. // 隐藏更多按钮
  191. setShowMore(false);
  192. setBackPractice("")
  193. }, [memory.front]);
  194. return (
  195. <PageHeaderWrapper title={false}>
  196. {state.step === Step.MAIN ? (
  197. <div>
  198. <div>
  199. 待复习:<b>{state.remindCount}</b>
  200. </div>
  201. <p/>
  202. <Button
  203. type="primary"
  204. size="large"
  205. onClick={() => {
  206. if (state.remindCount <= 0) {
  207. message.info('已经复习完了哟!');
  208. return;
  209. }
  210. setState({step: Step.MEMORY});
  211. }}
  212. >
  213. 复习
  214. </Button>
  215. <p/>
  216. <Button type="primary" size="large" onClick={() => setState({step: Step.ADD})}>
  217. 添加
  218. </Button>
  219. </div>
  220. ) : null}
  221. {state.step === Step.MEMORY ? (
  222. <div>
  223. 正面:
  224. <p/>
  225. <Quill theme="bubble" readonly onChange={() => {
  226. }} value={memory.front}/>
  227. <p/>
  228. {state.showBack ? (
  229. <div>
  230. {
  231. !isBackEmpty() ? (
  232. <div>
  233. 反面:
  234. <p/>
  235. <Quill theme="bubble" readonly onChange={() => {
  236. }} value={memory.back}/>
  237. <p/>
  238. </div>
  239. ) : null}
  240. <div className={styles.divBottom}>
  241. <Button
  242. type="primary"
  243. disabled={btnDisable}
  244. onClick={() => {
  245. memorySuccess(0.5);
  246. }}
  247. >
  248. 生疏
  249. </Button>
  250. <Button
  251. type="primary"
  252. disabled={btnDisable}
  253. onClick={() => {
  254. memorySuccess(2);
  255. }}
  256. >
  257. <b>&nbsp;&nbsp;一般&nbsp;&nbsp;</b>
  258. </Button>
  259. <Button
  260. type="primary"
  261. disabled={btnDisable}
  262. onClick={() => {
  263. memorySuccess(3);
  264. }}
  265. >
  266. 简单
  267. </Button>
  268. <Button
  269. type="text"
  270. onClick={() => {
  271. setShowMore(!showMore);
  272. }}
  273. >
  274. <DownOutlined/>
  275. </Button>
  276. {showMore ? (
  277. <div>
  278. <div>
  279. <Button
  280. type="primary"
  281. disabled={btnDisable}
  282. onClick={async () => {
  283. setBtnDisable(true);
  284. memoryRes(await service.delay(memory));
  285. }}
  286. >
  287. 搁置
  288. </Button>
  289. <Button
  290. type="primary"
  291. disabled={btnDisable}
  292. onClick={async () => {
  293. setBtnDisable(true);
  294. memoryRes(await service.restart(memory));
  295. }}
  296. >
  297. 重新复习
  298. </Button>
  299. <Button
  300. type="primary"
  301. disabled={btnDisable}
  302. onClick={async () => {
  303. confirm({
  304. title: '确定删除吗?',
  305. icon: <ExclamationCircleOutlined/>,
  306. content: '删除后无法恢复',
  307. okText: '确定',
  308. okType: 'danger',
  309. cancelText: '取消',
  310. async onOk() {
  311. setBtnDisable(true);
  312. memoryRes(await service.delete(memory));
  313. },
  314. onCancel() {
  315. console.log('Cancel');
  316. },
  317. });
  318. }}
  319. >
  320. 删除
  321. </Button>
  322. <Button
  323. type="primary"
  324. disabled={btnDisable}
  325. onClick={async () => {
  326. setBtnDisable(true);
  327. memoryRes(await service.noMemory(memory));
  328. }}
  329. >
  330. 不再复习
  331. </Button>
  332. </div>
  333. <div>
  334. <Button
  335. type="primary"
  336. onClick={() => {
  337. setState({step: Step.ADD});
  338. }}
  339. >
  340. 编辑
  341. </Button>
  342. <Button
  343. type="primary"
  344. onClick={() => {
  345. setState({step: Step.MAIN});
  346. }}
  347. >
  348. 关闭
  349. </Button>
  350. </div>
  351. </div>
  352. ) : null}
  353. </div>
  354. </div>
  355. ) : (
  356. <div>
  357. <Button
  358. type="primary"
  359. onClick={() => {
  360. setState({showBack: true});
  361. }}
  362. >
  363. 显示反面
  364. </Button>
  365. </div>
  366. )}
  367. {/* 答案演练板 */}
  368. <br/>
  369. <TextArea
  370. id={practice}
  371. autoSize
  372. value={backPractice}
  373. onChange={(e) => setBackPractice(e.target.value)}
  374. placeholder="答案演练板"
  375. />
  376. </div>
  377. ) : null}
  378. {state.step === Step.ADD ? (
  379. <UpdateForm
  380. onCancel={() => {
  381. bindShortKey();
  382. setState({step: Step.MAIN});
  383. }}
  384. modalVisible={state.step === Step.ADD}
  385. values={memory}
  386. />
  387. ) : null}
  388. </PageHeaderWrapper>
  389. );
  390. };
  391. export default TableList;