proofread.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <template>
  2. <div class="edito_youmei">
  3. <header_local />
  4. <div class="onlineTextMain">
  5. <div class="flex1">
  6. <div class="btn-grounp">
  7. 文章内容
  8. <el-button
  9. type="primary"
  10. style="margin-top: 0.3em; margin-right: 0.5em"
  11. @click="clearError"
  12. :icon="Delete"
  13. >
  14. 清空
  15. </el-button>
  16. <el-button
  17. type="primary"
  18. style="float: right; margin-top: 0.3em; margin-right: 0.5em"
  19. class="jiaoyan"
  20. @click="exportFun"
  21. >
  22. 导 出
  23. </el-button>
  24. <el-button
  25. type="primary"
  26. style="float: right; margin-top: 0.3em; margin-right: 0.5em"
  27. class="jiaoyan"
  28. @click="getServer"
  29. >
  30. 校 验
  31. </el-button>
  32. </div>
  33. <div class="Fle">
  34. <Editor v-model:rawText="rawText" />
  35. </div>
  36. </div>
  37. <div class="flex2">
  38. <div class="btn-grounp">
  39. 校对结果
  40. <div class="errTypes">
  41. <div
  42. @click="tabSelect[0] = !tabSelect[0]"
  43. :class="{ errType: true, checkboxLabel0: tabSelect[0] }"
  44. >
  45. 严重错误 {{ errorTotle.serious }}
  46. </div>
  47. <div
  48. @click="tabSelect[1] = !tabSelect[1]"
  49. :class="{ errType: true, checkboxLabel1: tabSelect[1] }"
  50. >
  51. 一般错误 {{ errorTotle.same }}
  52. </div>
  53. <div
  54. @click="tabSelect[2] = !tabSelect[2]"
  55. :class="{ errType: true, checkboxLabel2: tabSelect[2] }"
  56. >
  57. 疑错 {{ errorTotle.distrust }}
  58. </div>
  59. <div
  60. @click="tabSelect[3] = !tabSelect[3]"
  61. :class="{ errType: true, checkboxLabel3: tabSelect[3] }"
  62. >
  63. 自定义错误 {{ errorTotle.custom }}
  64. </div>
  65. </div>
  66. </div>
  67. <el-tabs tab-position="top" v-model="tabAct">
  68. <el-tab-pane :label="'未处理:' + untreatedError.length" :name="0">
  69. <proofread-errors
  70. listName="untreatedError"
  71. :lastAtom="lastAtom"
  72. :list="untreatedError"
  73. :tabSelect="tabSelect"
  74. @selectErrorFunc="selectErrorFunc"
  75. @replaceText="replaceText"
  76. @ignoreText="ignoreText"
  77. @deleteText="deleteText"
  78. ></proofread-errors>
  79. </el-tab-pane>
  80. <el-tab-pane :label="'已忽略:' + ignoreError.length" :name="1">
  81. <proofread-errors
  82. listName="ignoreError"
  83. :list="ignoreError"
  84. :tabSelect="tabSelect"
  85. @resumeText="resumeText"
  86. ></proofread-errors>
  87. </el-tab-pane>
  88. <el-tab-pane :label="'已替换:' + replaceError.length" :name="2">
  89. <proofread-errors
  90. listName="replaceError"
  91. :list="replaceError"
  92. :tabSelect="tabSelect"
  93. @resumeText="resumeText"
  94. ></proofread-errors>
  95. </el-tab-pane>
  96. <el-tab-pane :label="'已删除:' + deleteError.length" :name="3">
  97. <proofread-errors
  98. listName="deleteError"
  99. :list="deleteError"
  100. :tabSelect="tabSelect"
  101. @resumeText="resumeText"
  102. ></proofread-errors>
  103. </el-tab-pane>
  104. </el-tabs>
  105. </div>
  106. </div>
  107. </div>
  108. </template>
  109. <script setup>
  110. import config from '@/config/index';
  111. import header_local from './components/header.vue';
  112. import Editor from '@/components/editor.vue';
  113. import { Delete } from '@element-plus/icons-vue';
  114. import { ref } from 'vue';
  115. import errType from '../../../config/errtype';
  116. import { check } from '../../api/index';
  117. import proofreadErrors from './components/proofread_errors.vue';
  118. const rawText = ref('');
  119. const tabSelect = ref([true, true, true, true]);
  120. const untreatedError = ref([]);
  121. const ignoreError = ref([]);
  122. const replaceError = ref([]);
  123. const deleteError = ref([]);
  124. const errorTotle = ref({
  125. serious: 0,
  126. same: 0,
  127. distrust: 0,
  128. custom: 0,
  129. });
  130. const tabAct = ref(0);
  131. let lastAtom = ref(-1);
  132. // 清理数据
  133. function clearError() {
  134. rawText.value = '';
  135. untreatedError.value = [];
  136. ignoreError.value = [];
  137. replaceError.value = [];
  138. deleteError.value = [];
  139. }
  140. // 获取数据
  141. function getServer() {
  142. if (!window.fetch) return;
  143. errorTotle.value = {
  144. serious: 0,
  145. same: 0,
  146. distrust: 0,
  147. custom: 0,
  148. };
  149. untreatedError.value = [];
  150. ignoreError.value = [];
  151. replaceError.value = [];
  152. deleteError.value = [];
  153. check({
  154. data: {
  155. text: rawText.value || config.base.default_text || '',
  156. },
  157. }).then(res => {
  158. const list = res.checklist || [];
  159. let rawTextF = res.rawText || '';
  160. for (let i = 0; i < list.length; i++) {
  161. const v = list[i];
  162. const r = new RegExp(`data-umpos="${v.colorPosition}"`, 'g');
  163. let rText = `data-umpos="${
  164. v.colorPosition
  165. }" style="border-bottom: 3px solid rgb(${errType[v.um_error_level]});`;
  166. if (i == 0) {
  167. rText += `background-color: rgba(${errType[v.um_error_level]}, 0.5)`;
  168. lastAtom.value = v.colorPosition;
  169. }
  170. rText += `"`;
  171. rawTextF = rawTextF.replace(r, rText);
  172. if (v.um_error_level == 1) errorTotle.value.serious++;
  173. if (v.um_error_level == 2) errorTotle.value.same++;
  174. if (v.um_error_level == 3) errorTotle.value.distrust++;
  175. if (v.um_error_level == 4) errorTotle.value.custom++;
  176. }
  177. rawText.value = rawTextF;
  178. untreatedError.value = list;
  179. ignoreError.value = [];
  180. replaceError.value = [];
  181. deleteError.value = [];
  182. });
  183. }
  184. // 选中的文案
  185. function selectErrorFunc(data) {
  186. const newele = getEle(data.colorPosition);
  187. if (newele === null) return;
  188. let oldele = null;
  189. if (lastAtom.value != -1) {
  190. oldele = document.querySelector(`[data-umpos='${lastAtom.value}']`);
  191. oldele.style.backgroundColor = '';
  192. }
  193. newele.style.backgroundColor = `rgba(${errType[data.um_error_level]}, 0.5)`;
  194. lastAtom.value = data.colorPosition;
  195. }
  196. // 替换文案
  197. function replaceText(data) {
  198. const newele = getEle(data.item.colorPosition);
  199. if (newele === null) return;
  200. const fDoc = document.querySelector('.Fle .text');
  201. const outerHTML = newele.outerHTML || '';
  202. newele.outerHTML = `<span data-umpos="${data.item.colorPosition}">${
  203. data.item.suggest[data.index]
  204. }</span>`;
  205. rawText.value = fDoc.innerHTML;
  206. removeList(data.name, data.pindex, data.item);
  207. replaceError.value.push({ ...data.item, outerHTML });
  208. }
  209. // 忽略
  210. function ignoreText(data) {
  211. const newele = getEle(data.item.colorPosition);
  212. if (newele === null) return;
  213. const fDoc = document.querySelector('.Fle .text');
  214. const outerHTML = newele.outerHTML || '';
  215. newele.outerHTML = `<span data-umpos="${data.item.colorPosition}">${newele.innerHTML}</span>`;
  216. rawText.value = fDoc.innerHTML;
  217. removeList(data.name, data.pindex, data.item);
  218. ignoreError.value.push({ ...data.item, outerHTML });
  219. }
  220. // 删除
  221. function deleteText(data) {
  222. const newele = getEle(data.item.colorPosition);
  223. if (newele === null) return;
  224. const fDoc = document.querySelector('.Fle .text');
  225. const outerHTML = newele.outerHTML || '';
  226. newele.outerHTML = `<span data-umpos="${data.item.colorPosition}"></span>`;
  227. rawText.value = fDoc.innerHTML;
  228. removeList(data.name, data.pindex, data.item);
  229. deleteError.value.push({ ...data.item, outerHTML });
  230. }
  231. // 恢复
  232. function resumeText(data) {
  233. const newele = getEle(data.item.colorPosition);
  234. if (newele === null) return;
  235. const fDoc = document.querySelector('.Fle .text');
  236. newele.outerHTML = data.item.outerHTML;
  237. rawText.value = fDoc.innerHTML;
  238. removeList(data.name, data.pindex, data.item);
  239. untreatedError.value.push(data.item);
  240. }
  241. // remove list
  242. function removeList(key, index, item) {
  243. const C = lastAtom.value !== item.colorPosition;
  244. if (key === 'deleteError') {
  245. deleteError.value.splice(index, 1);
  246. if (C) return;
  247. deleteError.value[0] && selectErrorFunc(deleteError.value[0]);
  248. return;
  249. }
  250. if (key === 'ignoreError') {
  251. ignoreError.value.splice(index, 1);
  252. if (C) return;
  253. ignoreError.value[0] && selectErrorFunc(ignoreError.value[0]);
  254. return;
  255. }
  256. if (key === 'replaceError') {
  257. replaceError.value.splice(index, 1);
  258. if (C) return;
  259. replaceError.value[0] && selectErrorFunc(replaceError.value[0]);
  260. return;
  261. }
  262. if (key === 'untreatedError') {
  263. untreatedError.value.splice(index, 1);
  264. if (C) return;
  265. untreatedError.value[0] && selectErrorFunc(untreatedError.value[0]);
  266. return;
  267. }
  268. }
  269. // 获得选中的元素
  270. function getEle(colorPosition) {
  271. const newele = document.querySelector(`[data-umpos='${colorPosition}']`);
  272. if (!newele) return null;
  273. return newele;
  274. }
  275. // 导出word
  276. function exportFun() {
  277. /**
  278. * wps: kswps
  279. * docx: msword
  280. */
  281. let blob = new Blob([`<body>${rawText.value}</body>`], {
  282. type: 'application/kswps',
  283. });
  284. let url = URL.createObjectURL(blob);
  285. let link = document.createElement('a');
  286. link.setAttribute('href', url);
  287. link.setAttribute('download', `智能校对.docx`);
  288. link.click();
  289. }
  290. </script>
  291. <style scoped>
  292. .onlineTextMain {
  293. padding: 1em;
  294. display: flex;
  295. }
  296. .onlineTextMain > div {
  297. flex: 3;
  298. margin-right: 0.5em;
  299. border: 1px solid #eee;
  300. height: calc(100vh - 106px);
  301. }
  302. .onlineTextMain .flex2 {
  303. flex: 2;
  304. overflow: hidden;
  305. }
  306. .onlineTextMain > div:last-child {
  307. margin-right: 0;
  308. }
  309. .btn-grounp {
  310. color: #666;
  311. background-color: #f9fafc;
  312. line-height: 2.8em;
  313. padding-left: 0.5em;
  314. border-bottom: 1px solid #eee;
  315. }
  316. .flex1 {
  317. line-height: 1.5em;
  318. min-height: 1.5em;
  319. }
  320. .flex1 span {
  321. display: inline-block;
  322. }
  323. .Fle {
  324. height: calc(100vh-226px);
  325. }
  326. .errTypes {
  327. display: flex;
  328. padding: 0 0 1em 0;
  329. }
  330. .errType {
  331. flex: 1;
  332. font-size: 12px;
  333. margin-right: 3px;
  334. border: 1px solid;
  335. padding: 0 0.5em;
  336. text-align: center;
  337. border-radius: 5px;
  338. line-height: 2.5em;
  339. cursor: pointer;
  340. color: #909090;
  341. background-color: hsla(0, 0%, 56.5%, 0.1);
  342. }
  343. .errTypes .checkboxLabel0 {
  344. color: rgb(255, 68, 99);
  345. border-color: rgb(255, 68, 99);
  346. background-color: rgba(255, 68, 99, 0.1);
  347. }
  348. .errTypes .checkboxLabel1 {
  349. color: rgb(145, 155, 218);
  350. border-color: rgb(145, 155, 218);
  351. background-color: rgba(145, 155, 218, 0.1);
  352. }
  353. .errTypes .checkboxLabel2 {
  354. color: rgb(255, 152, 0);
  355. border-color: rgb(255, 152, 0);
  356. background-color: rgba(255, 152, 0, 0.1);
  357. }
  358. .errTypes .checkboxLabel3 {
  359. color: rgb(156, 133, 255);
  360. border-color: rgb(156, 133, 255);
  361. background-color: rgba(156, 133, 255, 0.1);
  362. }
  363. </style>
  364. <style>
  365. .edito_youmei .el-popper.is-light,
  366. .edito_youmei .el-popper .el-popper__arrow::before {
  367. background-color: #2974ff !important;
  368. color: #fff;
  369. border-color: #2974ff !important;
  370. }
  371. .el-poper .selectSpan {
  372. border-bottom: 3px solid #fff;
  373. }
  374. </style>