home.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. <template>
  2. <div>
  3. <el-container style="user-select: none; height: 100vh;">
  4. <el-header height="45px" style="background-color: #fafafa; padding: 0 10px;">
  5. <soft-header ref="headerRef" @update-soft="updateSoft()"
  6. @export-file="exportFile"></soft-header>
  7. </el-header>
  8. <el-main ref="el-main" style="background-color: #fafafa;">
  9. <template>
  10. <div class="content-top">
  11. <div>
  12. <el-radio-group v-model="pinyinType" @input="radioChange">
  13. <el-radio-button :label="1">文字注音</el-radio-button>
  14. <el-radio-button :label="2">文件批量注音</el-radio-button>
  15. </el-radio-group>
  16. <vxe-button type="text" status="info" icon="vxe-icon-info-circle-fill" @click="$message({message:'文字注音限制长度10万,超长文本请使用文件注音', type:'warning'})"></vxe-button>
  17. </div>
  18. <el-button-group v-if="pinyinType == 2">
  19. <el-button type="primary" size="mini" icon="el-icon-document"
  20. @click="pickFile()">添加文件</el-button>
  21. <el-button type="primary" size="mini" icon="el-icon-folder"
  22. @click="pickDir()">添加文件夹</el-button>
  23. <el-button type="primary" size="mini" icon="el-icon-delete"
  24. @click="clearList()">清空列表</el-button>
  25. </el-button-group>
  26. <el-button v-else type="info" size="mini"
  27. @click="txt1='';txtHtml='';">清空内容</el-button>
  28. </div>
  29. <div style="padding: 15px 20px 0 20px; height: calc(100% - 160px);">
  30. <el-row :gutter="20" style="height: 100%;" v-if="pinyinType == 1">
  31. <el-col :span="12" style="height: 100%;">
  32. <el-input style="height: 100%; font-size: 20px;" maxlength="100000" :show-word-limit="true" type="textarea" placeholder="请输入需要注音的文字内容" v-model="txt1" @input="inputChange"></el-input>
  33. </el-col>
  34. <el-col :span="12" style="height: 100%;position: relative;">
  35. <el-input class="outtext" :class="tipsShow ? 'red-border' : ''" v-if="pinBuild == 3" type="textarea" readonly v-model="txtHtml"></el-input>
  36. <div class="outarea" :class="tipsShow ? 'red-border' : ''" v-html="txtHtml" v-else></div>
  37. <p class="pin-tips" v-if="tipsShow">非会员注音限制在20个以内</p>
  38. </el-col>
  39. </el-row>
  40. <div v-if="pinyinType == 2 && fileList.length == 0" class="upload-area">
  41. <div class="file-area" @click="pickFile()" id="drag-table">
  42. <img src="../assets/image/upload.png" style="width: 220px;"/>
  43. <p style="font-size: 16px;">点击添加TXT/DOCX文件或拖拽TXT/DOCX文件到此</p>
  44. </div>
  45. </div>
  46. <div style="height: 100%;" v-if="pinyinType == 2 && fileList.length > 0">
  47. <p class="title">
  48. <i class="el-icon-notebook-2"></i>
  49. 文件列表
  50. <span style="color: #F22C40; float: right;cursor: pointer;"
  51. @click="$message({message:'超长文本结构注音时,分割成每10万字导出一份文档', type:'warning'})">{{ loadingTips }}</span>
  52. </p>
  53. <div class="table-scroll" id="drag-table">
  54. <vxe-table
  55. show-overflow
  56. class="img-table"
  57. max-height="100%"
  58. empty-text="没有更多数据了!"
  59. :row-config="{isHover: true}"
  60. :edit-config="{trigger: 'click', mode: 'cell'}"
  61. :data="fileList"
  62. :scroll-y="{enabled: true}">
  63. <vxe-column field="name" title="文件名"></vxe-column>
  64. <vxe-column field="size" title="大小" width="120"></vxe-column>
  65. <vxe-column field="progress" title="进度" width="180">
  66. <template #default="{ row }">
  67. <el-progress :text-inside="true" :stroke-width="20" :percentage="row.percent"></el-progress>
  68. </template>
  69. </vxe-column>
  70. <vxe-column title="操作" width="80">
  71. <template #default="{ row, rowIndex }">
  72. <i class="el-icon-delete cur-pointer" @click="delFile(rowIndex)"></i>
  73. </template>
  74. </vxe-column>
  75. </vxe-table>
  76. </div>
  77. </div>
  78. </div>
  79. <div style="padding: 0 20px;">
  80. <el-row type="flex" style="padding-top: 10px;">
  81. <div class="set-item">
  82. <span class="set-title">注音结构:</span>
  83. <el-select v-model="pinBuild" size="small" style="width:100px;" @change="dataChange">
  84. <el-option label="上下结构" :value="1"></el-option>
  85. <el-option label="左右结构" :value="2"></el-option>
  86. <el-option label="只要拼音" :value="3"></el-option>
  87. </el-select>
  88. </div>
  89. <div class="set-item">
  90. <span class="set-title">拼音风格:</span>
  91. <el-select v-model="pinSetting.toneType" size="small" style="width:100px;" @change="dataChange">
  92. <el-option label="带声调" value="-"></el-option>
  93. <el-option label="不带声调" value="none"></el-option>
  94. <el-option label="数字后缀" value="num"></el-option>
  95. </el-select>
  96. </div>
  97. <div class="set-item" v-if="pinBuild == 3">
  98. <span class="set-title">输出内容:</span>
  99. <el-select v-model="pinSetting.pattern" size="small" style="width:100px;" @change="dataChange">
  100. <el-option label="完整拼音" value="-"></el-option>
  101. <el-option label="声母" value="initial"></el-option>
  102. <el-option label="韵母" value="final"></el-option>
  103. <el-option label="韵头" value="finalHead"></el-option>
  104. <el-option label="韵腹" value="finalBody"></el-option>
  105. <el-option label="韵尾" value="finalTail"></el-option>
  106. <el-option label="音调" value="num"></el-option>
  107. <el-option label="首字母" value="first"></el-option>
  108. </el-select>
  109. </div>
  110. <div class="set-item">
  111. <span class="set-title">导出格式:</span>
  112. <el-select v-model="exportFormat" size="small" style="width:100px;" >
  113. <el-option label="docx" value="docx"></el-option>
  114. <el-option label="txt" value="txt" v-if="pinBuild == 3"></el-option>
  115. </el-select>
  116. <vxe-button type="text" status="info" icon="vxe-icon-info-circle-fill" @click="$message({message:'上下/左右结构注音时,只能输出docx文件', type:'warning'})"></vxe-button>
  117. </div>
  118. </el-row>
  119. <el-row type="flex" style="padding: 10px 0;">
  120. <div class="set-item" v-if="pinBuild == 3">
  121. <span class="set-title">模式:</span>
  122. <el-select v-model="pinSetting.mode" size="small" style="width:100px;" @change="dataChange">
  123. <el-option label="普通模式" value="-"></el-option>
  124. <el-option label="姓氏模式" value="surname"></el-option>
  125. </el-select>
  126. </div>
  127. <div class="set-item" v-if="pinBuild == 3">
  128. <span class="set-title">非汉字:</span>
  129. <el-select v-model="pinSetting.nonZh" size="small" style="width:140px;" @change="dataChange">
  130. <el-option label="以空格间隔输出" value="-"></el-option>
  131. <el-option label="移除非汉字字符" value="removed"></el-option>
  132. <el-option label="字符串紧凑输出" value="consecutive"></el-option>
  133. </el-select>
  134. </div>
  135. <div class="set-item">
  136. <span class="set-title">分隔符:</span>
  137. <el-input type="text" maxlength="4" :show-word-limit="true" v-model="separator" size="small" placeholder="默认为空格" style="width:125px;" @input="dataChange"></el-input>
  138. </div>
  139. <div class="set-item">
  140. <span class="set-title">保存目录:</span>
  141. <el-input :title="handleData.newPath" ref="upload-input" @focus="pickPath" placeholder="请选择输出目录" size="small" v-model="handleData.newPath" readonly style="width:180px;" prefix-icon="el-icon-folder"></el-input>
  142. <el-popover placement="bottom" popper-class="popper-open" trigger="hover" content="打开保存目录">
  143. <i class="el-icon-folder-opened" slot="reference" style="padding-left: 5px; cursor: pointer; font-size: 22px; vertical-align: middle;" @click="openFolder()"></i>
  144. </el-popover>
  145. </div>
  146. <el-button type="danger" class="export-btn" @click="exportFile()" :loading="loading">导出注音</el-button>
  147. </el-row>
  148. </div>
  149. </template>
  150. </el-main>
  151. <el-footer height="48px">
  152. <!-- 更新 -->
  153. <soft-update ref="updateRef" :showDowload="dowloadModel"
  154. :dowloadFinish="finishModel"></soft-update>
  155. </el-footer>
  156. </el-container>
  157. </div>
  158. </template>
  159. <script>
  160. import os from 'os'
  161. import fs from 'fs'
  162. import pathMod from 'path';
  163. import softUpdate from './update.vue';
  164. import softHeader from './header.vue';
  165. import electronApi from '@/utils/electronApi';
  166. import pjson from '/package.json'
  167. import { pinyin, html } from 'pinyin-pro';
  168. import htmlDocx from 'html-docx-js/dist/html-docx';
  169. import mammoth from 'mammoth';
  170. let separator = '';
  171. if (os.platform == 'linux') {
  172. separator = '/'
  173. } else {
  174. separator = '\\'
  175. }
  176. export default {
  177. name: 'landing-page',
  178. components: {
  179. softUpdate,
  180. softHeader
  181. },
  182. data() {
  183. return {
  184. productName: pjson.softInfo.softName,
  185. imgUrl: this.$api.imgUrl,
  186. imgSrc: '',
  187. fileList: [],
  188. downloadDir: '', // 默认下载目录
  189. handleData: {
  190. pathType: 2,
  191. newPath: os.userInfo().homedir + separator + "Downloads", // 新路径
  192. },
  193. dowloadModel: false,
  194. finishModel: false,
  195. version: '0.0.1',
  196. isStart: false, // 是否开始转换
  197. isUpdate: false, // 是否有更新
  198. softdownloadUrl: os.userInfo().homedir + separator + "Downloads", // 下载路径
  199. popupAdvList: [],
  200. loading: false,
  201. loadingTips: '长度超10万字,点击查看注意事项',
  202. tempPath: '',
  203. procMap: {},
  204. execLimit: 2,
  205. exportLoading: false,
  206. txt1: '',
  207. txtHtml: '',
  208. pinSetting: {
  209. pattern: '-',
  210. toneType: '-',
  211. mode: '-',
  212. nonZh: 'consecutive',
  213. separator: ' ',
  214. },
  215. separator: '',
  216. pinBuild: 1,
  217. exportFormat: 'docx',
  218. pinyinType: 1,
  219. tipsShow: false,
  220. };
  221. },
  222. async mounted() {
  223. this.tempPath = os.tmpdir();
  224. this.$refs.updateRef.updateSoft(true);
  225. let homedir = os.userInfo().homedir;
  226. if (fs.existsSync(homedir + separator + "Desktop")) {
  227. this.downloadDir = homedir + separator + "Desktop"
  228. } else {
  229. this.downloadDir = homedir + separator + "Downloads"
  230. }
  231. this.handleData.newPath = this.downloadDir;
  232. // 打开浏览器
  233. const {
  234. shell
  235. } = require('electron');
  236. const links = document.querySelectorAll('a[href]');
  237. links.forEach(link => {
  238. link.addEventListener('click', e => {
  239. const url = link.getAttribute('href');
  240. e.preventDefault();
  241. shell.openExternal(url);
  242. });
  243. });
  244. },
  245. methods: {
  246. radioChange(e){
  247. if(e == 2){
  248. setTimeout(() => {
  249. // 加载拖拽监听事件
  250. this.initDrop();
  251. }, 500)
  252. }
  253. },
  254. // 加载拖拽监听事件
  255. initDrop() {
  256. const dragWrapper = document.getElementById("drag-table");
  257. let domArr = [dragWrapper];
  258. domArr.map(item => {
  259. //添加拖拽事件监听器
  260. item.addEventListener("drop", (e) => {
  261. //阻止默认行为
  262. e.preventDefault();
  263. //获取文件列表
  264. let files = e.dataTransfer.files;
  265. let pathArr = [];
  266. for (let i = 0; i < files.length; i++) {
  267. pathArr.push(files[i].path);
  268. }
  269. this.pushFileToList(pathArr);
  270. })
  271. //阻止拖拽结束事件默认行为
  272. item.addEventListener("dragover", (e) => {
  273. e.preventDefault();
  274. })
  275. })
  276. },
  277. // 删除列表文件
  278. delFile(index){
  279. this.$confirm('确认删除此文件吗?', '提示', {
  280. confirmButtonText: '确定',
  281. cancelButtonText: '取消',
  282. type: 'warning'
  283. }).then(() => {
  284. this.fileList.splice(index, 1);
  285. if (this.fileList.length ==0) {
  286. this.clearList();
  287. }
  288. }).catch(() => {
  289. });
  290. },
  291. // 选择目录
  292. pickPath() {
  293. this.$refs['upload-input'].blur();
  294. electronApi.call('pickDir', []).then((path) => {
  295. if (path) {
  296. this.handleData.newPath = path;
  297. this.downloadDir = path;
  298. }
  299. });
  300. },
  301. // 打开自定义下载目录
  302. openFolder() {
  303. let path = this.downloadDir;
  304. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  305. path = this.downloadDir + separator + pjson.softInfo.softName;
  306. } else {
  307. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  308. path = this.downloadDir + separator + pjson.softInfo.softName;
  309. }
  310. electronApi.call('showItemInfolder', [path + '\\tty.tty'])
  311. },
  312. // 选择新路径
  313. getFolder(e) {
  314. this.handleData.newPath = e.path;
  315. this.downloadDir = e.path;
  316. },
  317. openVip() {
  318. this.$refs.headerRef.openVip();
  319. },
  320. updateSoft() {
  321. this.$refs.updateRef.updateSoft();
  322. },
  323. // 清除列表
  324. clearList() {
  325. this.fileList = [];
  326. },
  327. async pickFile() {
  328. const spinLoad = this.$loading();
  329. await electronApi.call('pickFile', ['txt,docx', true]).then(async (files) => {
  330. this.pushFileToList(files);
  331. spinLoad.close();
  332. });
  333. },
  334. async pickDir() {
  335. const spinLoad = this.$loading();
  336. await electronApi.call('pickDir', [os.userInfo().homedir]).then((path) => {
  337. this.dealDir(path);
  338. spinLoad.close();
  339. });
  340. },
  341. dealDir(currentDirPath) {
  342. if (currentDirPath) {
  343. let files = fs.readdirSync(currentDirPath);
  344. for (let i = 0; i < files.length; i++) {
  345. let name = files[i];
  346. var filePath = pathMod.join(currentDirPath, name);
  347. var stat = fs.statSync(filePath);
  348. if (stat.isFile()) {
  349. this.pushFileToList([filePath]);
  350. } else if (stat.isDirectory()) {
  351. this.dealDir(filePath)
  352. }
  353. }
  354. }
  355. },
  356. pushFileToList(files) {
  357. files.map(file => {
  358. let filename = file.substr(file.lastIndexOf(separator) + 1);
  359. let size = fs.statSync(file).size;
  360. let fileInfo = {
  361. name: filename,
  362. size: this.$utils.handleSize(size),
  363. path: file,
  364. type: filename.lastIndexOf('.') == -1 ? '' : filename.substr(filename.lastIndexOf('.') + 1),
  365. percent: 0,
  366. };
  367. if (!fileInfo.type) {
  368. this.$message({message: '不是有效的文件! - ' + file, type: 'warning'});
  369. } else {
  370. let flag = true;
  371. let format = ["TXT", "DOCX"];
  372. if (format.indexOf(fileInfo.type.toUpperCase()) == -1) {
  373. this.$message({message: '不支持该文件格式! - ' + file, type: 'warning'});
  374. } else {
  375. for (let m = 0; m < this.fileList.length; m++) {
  376. if (this.fileList[m].name == fileInfo.name && this.fileList[m].path == fileInfo.path) {
  377. flag = false;
  378. }
  379. }
  380. if (flag) {
  381. this.fileList.push(fileInfo);
  382. } else {
  383. this.$message({message: '该文件已经在队列中 - ' + file, type: 'warning'});
  384. }
  385. }
  386. }
  387. })
  388. },
  389. // 更改设置
  390. inputChange(val){
  391. this.pinSetting.separator = this.separator == '' ? ' ' : this.separator;
  392. if (!this.$refs.headerRef.authority.isAuthority) {
  393. if(val.length > 20){
  394. this.tipsShow = true;
  395. val = val.slice(0, 20);
  396. }else{
  397. this.tipsShow = false;
  398. }
  399. }else{
  400. this.tipsShow = false;
  401. }
  402. if(this.pinBuild == 3){ // 只要拼音
  403. this.txtHtml = pinyin(val, this.pinSetting);
  404. }else{
  405. let htmls = this.dealHtml(html(val, {toneType : this.pinSetting.toneType, wrapNonChinese: true}));
  406. if(this.pinBuild == 2){ // 左右结构
  407. let regex4 = /<ruby><span class="py-chinese-item">|<\/span><rp>|<\/rp><rt class="py-pinyin-item">|<\/rt><rp>|<\/rp><\/ruby>/g;
  408. htmls = htmls.replace(regex4, '');
  409. }
  410. this.txtHtml = htmls;
  411. }
  412. },
  413. dataChange(){
  414. if(this.pinBuild != 3 && this.exportFormat == 'txt'){
  415. this.exportFormat = 'docx';
  416. }
  417. this.inputChange(this.txt1);
  418. },
  419. // 导出
  420. async exportFile(flag) {
  421. this.loading = true;
  422. if (fs.existsSync(this.downloadDir + separator + pjson.softInfo.softName)) {
  423. this.handleData.newPath = this.downloadDir + separator + pjson.softInfo.softName;
  424. } else {
  425. fs.mkdirSync(this.downloadDir + separator + pjson.softInfo.softName);
  426. this.handleData.newPath = this.downloadDir + separator + pjson.softInfo.softName;
  427. }
  428. if(this.pinyinType == 1){ // 文字注音
  429. if(this.txt1.length <= 0){
  430. this.$message({message: '请输入需要注音的文字', type: 'warning'});
  431. this.loading = false;
  432. return false;
  433. }
  434. let htmlContent = this.txtHtml;
  435. let buffer;
  436. if(this.pinBuild == 3){ // 只要拼音
  437. buffer = htmlContent;
  438. }else{
  439. let blob = htmlDocx.asBlob(htmlContent);
  440. buffer = Buffer.from(await blob.arrayBuffer());
  441. }
  442. let filePath = this.handleData.newPath + "\\" + "注音文档" + this.$utils.formatFileTime() + '.' + this.exportFormat;
  443. fs.writeFile(filePath, buffer, (err) => {
  444. if (err){
  445. this.loading = false;
  446. throw err;
  447. }
  448. setTimeout(() => {
  449. this.loading = false;
  450. this.$message({message: "注音完成!", type: 'success'});
  451. electronApi.call('showItemInfolder', [filePath]);
  452. }, 1000)
  453. });
  454. }else{ // 文件批量注音
  455. if(this.fileList.length <= 0){
  456. this.$message({message: '请选择需要注音的TXT/DOCX文档', type: 'warning'});
  457. this.loading = false;
  458. return false;
  459. }else{
  460. let fileList = this.fileList;
  461. let authority = this.$refs.headerRef.authority;
  462. if (!authority.isAuthority && !flag) {
  463. this.$refs.headerRef.memberModel = true;
  464. this.$refs.headerRef.isClick = true;
  465. this.loading = false;
  466. return false;
  467. } else {
  468. this.$refs.headerRef.memberModel = false;
  469. }
  470. //percent
  471. let taskArr = [];
  472. let newPath = '';
  473. setTimeout(async() => {
  474. for (let i = 0; i < fileList.length; i++) {
  475. let item = fileList[i];
  476. let lastIndex = item.path.lastIndexOf('.');
  477. let suffix = this.exportFormat;
  478. newPath = this.handleData.newPath + separator + item.name.slice(0, item.name.lastIndexOf('.')) + '.' + suffix;
  479. // 处理文件
  480. let task = this.dealFile(i, item, newPath);
  481. if (task) {
  482. taskArr.push(task);
  483. }
  484. if ((i + 1) % this.execLimit == 0) {
  485. await Promise.all(taskArr).then(result => {
  486. taskArr = [];
  487. }).catch(err => {
  488. console.log('err' + i, err)
  489. })
  490. }
  491. }
  492. if (taskArr.length > 0) {
  493. await Promise.all(taskArr).then(result => {
  494. taskArr = [];
  495. }).catch(err => {
  496. // 错误文件添加到服务中
  497. console.log('err', err)
  498. })
  499. }
  500. // 打开文件夹
  501. if (fileList.length > 0) {
  502. this.loading = false;
  503. this.$message({message: "恭喜你,任务已完成!", type: 'success'});
  504. electronApi.call('showItemInfolder', [this.handleData.newPath + '\\xy.txt'])
  505. }
  506. }, 300)
  507. }
  508. setTimeout(() => {
  509. this.loading = false;
  510. }, 30000)
  511. }
  512. },
  513. showPercent(num, index, item){
  514. item.percent = num;
  515. this.fileList.splice(index, 1, item);
  516. },
  517. // 格式化html
  518. dealHtml(htmls){
  519. let regex1 = new RegExp('py-non-chinese-item"> </span>', "g");
  520. let regex2 = new RegExp('py-non-chinese-item">\n</span>', "g");
  521. let regex3 = new RegExp('</span><span class="py-result-item">', "g");
  522. htmls = htmls.replace(regex1, 'py-non-chinese-item">&nbsp;</span>')
  523. .replace(regex2, 'py-non-chinese-item">\n</span><br>')
  524. .replace(regex3, '</span>'+this.pinSetting.separator+'<span class="py-result-item">');
  525. return htmls;
  526. },
  527. async blockFile(index, item, textArr){
  528. for(let i = 0; i < textArr.length; i++){
  529. let htmls = this.dealHtml(html(textArr[i], {toneType : this.pinSetting.toneType, wrapNonChinese: true}));
  530. if(this.pinBuild == 2){ // 左右结构
  531. let regex4 = /<ruby><span class="py-chinese-item">|<\/span><rp>|<\/rp><rt class="py-pinyin-item">|<\/rt><rp>|<\/rp><\/ruby>/g;
  532. htmls = htmls.replace(regex4, '');
  533. }
  534. let blob = htmlDocx.asBlob(htmls);
  535. let buffer = Buffer.from(await blob.arrayBuffer());
  536. this.showPercent(70, index, item);
  537. let filePath = this.handleData.newPath + separator + item.name.slice(0, item.name.lastIndexOf('.')) + '(第' + Number(i+1) + '部分).' + this.exportFormat;
  538. fs.writeFile(filePath, buffer, (err) => {
  539. if (err){
  540. this.loading = false;
  541. this.$notify.error({
  542. title: '错误',
  543. message: "出现错误,请重试!" + filePath
  544. });
  545. throw err;
  546. return false;
  547. }
  548. });
  549. }
  550. this.showPercent(100, index, item);
  551. return true;
  552. },
  553. dealFile(index, item, newPath){ // 开始注音
  554. return new Promise((resolve, reject) => {
  555. let path = item.path;
  556. this.showPercent(10, index, item);
  557. if(item.type.toUpperCase() == 'DOCX'){ // word文档
  558. mammoth.extractRawText({path: path}).then(async(res) =>{
  559. let data = res.value;
  560. let result = await this.pinFile(index, item, newPath, data);
  561. if(result){
  562. resolve(true);
  563. }else{
  564. reject(false);
  565. }
  566. }).catch((error) => {
  567. console.error(error);
  568. this.$notify.error({
  569. title: '错误',
  570. message: "出现错误,请重试!" + filePath
  571. });
  572. reject(false);
  573. });
  574. }else{
  575. fs.readFile(path, 'utf8', async(err, data) => {
  576. if (err) {
  577. console.error(err);
  578. this.$notify.error({
  579. title: '错误',
  580. message: "出现错误,请重试!" + filePath
  581. });
  582. reject(false);
  583. }
  584. let result = await this.pinFile(index, item, newPath, data);
  585. if(result){
  586. resolve(true);
  587. }else{
  588. reject(false);
  589. }
  590. });
  591. }
  592. });
  593. },
  594. // 文件注音
  595. async pinFile(index, item, newPath, data) {
  596. if (!this.$refs.headerRef.authority.isAuthority) {
  597. if(data.length > 20){
  598. data = data.slice(0, 20);
  599. }
  600. }
  601. let textLength = data.length;
  602. let blockLength = Math.ceil(textLength / 100000);
  603. let textArr = [];
  604. if(textLength > 100000 && this.pinBuild != 3){ // 文字长度大于10万字且是结构输出,分块处理
  605. for(let i=0; i<blockLength; i++){
  606. let startIndex = i * 100000;
  607. let endIndex = (i+1) * 100000;
  608. textArr.push(data.slice(startIndex ,endIndex));
  609. }
  610. let result = await this.blockFile(index, item, textArr);
  611. if(result){
  612. this.showPercent(100, index, item);
  613. return true;
  614. }else{
  615. this.$notify.error({
  616. title: '错误',
  617. message: "出现错误,请重试!" + filePath
  618. });
  619. return false;
  620. }
  621. }else{
  622. let outContent = '';
  623. this.pinSetting.separator = this.separator == '' ? ' ' : this.separator;
  624. if(this.pinBuild == 3){ // 只要拼音
  625. outContent = pinyin(data, this.pinSetting);
  626. }else{
  627. let htmls = this.dealHtml(html(data, {toneType : this.pinSetting.toneType, wrapNonChinese: true}));
  628. if(this.pinBuild == 2){ // 左右结构
  629. let regex4 = /<ruby><span class="py-chinese-item">|<\/span><rp>|<\/rp><rt class="py-pinyin-item">|<\/rt><rp>|<\/rp><\/ruby>/g;
  630. htmls = htmls.replace(regex4, '');
  631. }
  632. outContent = htmls;
  633. }
  634. this.showPercent(50, index, item);
  635. let buffer;
  636. if(this.pinBuild == 3){ // 只要拼音
  637. buffer = outContent;
  638. }else{
  639. let blob = htmlDocx.asBlob(outContent);
  640. buffer = Buffer.from(await blob.arrayBuffer());
  641. }
  642. this.showPercent(70, index, item);
  643. let filePath = newPath;
  644. fs.writeFile(filePath, buffer, (err) => {
  645. if (err){
  646. this.loading = false;
  647. this.$notify.error({
  648. title: '错误',
  649. message: err.message + filePath
  650. });
  651. return false;
  652. throw err;
  653. }
  654. this.showPercent(100, index, item);
  655. return true;
  656. });
  657. }
  658. },
  659. }
  660. };
  661. </script>
  662. <style lang="scss">
  663. @import "../assets/css/font/iconfont.css";
  664. @import "../assets/css/fontx/iconfont.css";
  665. @import "../assets/css/home.scss";
  666. .ivu-input-number-controls-outside-btn i {
  667. font-weight: 800;
  668. }
  669. .update-point {
  670. display: inline-block;
  671. width: 8px;
  672. height: 8px;
  673. border-radius: 8px;
  674. background: #ff0000;
  675. top: 14px;
  676. position: absolute;
  677. left: -13px;
  678. }
  679. .menu-item {
  680. padding: 8px 0;
  681. font-size: 14px;
  682. .iconfont {
  683. font-size: 32px;
  684. }
  685. &:hover,
  686. &.active {
  687. color: #ed4014;
  688. }
  689. }
  690. .ivu-progress-show-info .ivu-progress-outer {
  691. padding-right: 40px !important;
  692. margin-right: -40px !important;
  693. }
  694. .tips {
  695. text-align: center;
  696. padding: 10px 0;
  697. color: #ed4014;
  698. font-size: 12px;
  699. }
  700. .soft-content {
  701. height: calc(100% - 118px);
  702. }
  703. .handle-label {
  704. vertical-align: top;
  705. }
  706. .handle-desc {
  707. display: inline-block;
  708. width: calc(100% - 100px);
  709. overflow: hidden;
  710. }
  711. .ivu-menu-submenu-title {
  712. font-weight: 600;
  713. }
  714. .ivu-menu .ivu-menu-item {
  715. line-height: 1;
  716. }
  717. // new-el
  718. .el-menu {
  719. border-right: none !important;
  720. }
  721. .cmenu-item {
  722. padding: 0 20px 20px;
  723. margin-bottom: 15px;
  724. .cmenu-title {
  725. font-size: 18px;
  726. font-weight: 600;
  727. padding-bottom: 20px;
  728. }
  729. .citem-nav {
  730. text-align: center;
  731. border-radius: 10px;
  732. min-height: 110px;
  733. padding: 15px 0;
  734. background-color: #fff;
  735. font-size: 15px;
  736. cursor: pointer;
  737. &.bg-linear1 {
  738. color: #fff;
  739. font-size: 20px;
  740. background: linear-gradient(to right bottom, #2A56CA, #5795F4);
  741. }
  742. &.bg-linear2 {
  743. color: #fff;
  744. font-size: 20px;
  745. background: linear-gradient(to right top, #147FBB, #5EB3E3);
  746. }
  747. &.bg-linear3 {
  748. color: #fff;
  749. font-size: 20px;
  750. background: linear-gradient(to right bottom, #2F9E8A, #56CDB1);
  751. }
  752. &:hover {
  753. margin-top: -5px;
  754. box-shadow: 3px 3px 6px #ccc, -3px -3px 6px #ccc;
  755. }
  756. .citem-img {
  757. width: 50px;
  758. margin-bottom: 10px;
  759. }
  760. }
  761. }
  762. .popper-open{
  763. text-align: center !important;
  764. padding: 10px !important;
  765. background: #303133 !important;
  766. color: #fff !important;
  767. min-width: 120px !important;
  768. opacity: 0.8;
  769. }
  770. .popper-open[x-placement^=bottom] .popper__arrow::after{
  771. border-bottom-color: #303133 !important;
  772. }
  773. textarea.el-textarea__inner{
  774. height: 100% !important;
  775. font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
  776. }
  777. .set-item{
  778. margin-right: 15px;
  779. .set-title{
  780. font-size: 13px;
  781. }
  782. }
  783. .export-btn{
  784. position:absolute !important;
  785. right: 0;
  786. bottom: 20px;
  787. }
  788. .outarea{
  789. height: 100%;
  790. border: 1px solid #DCDFE6;
  791. background-color: #4851a415;
  792. padding: 15px;
  793. font-size: 20px;
  794. overflow: hidden auto;
  795. }
  796. .outtext{
  797. height: 100%;
  798. font-size: 20px;
  799. overflow: hidden auto;
  800. textarea{
  801. background-color: #4851a415;
  802. }
  803. }
  804. .pin-tips{
  805. font-size: 14px;
  806. position: absolute;
  807. bottom: 10px;
  808. color: #ff0000;
  809. left: 0;
  810. right: 0;
  811. margin: auto;
  812. text-align: center;
  813. }
  814. .outarea.red-border{
  815. border: 1px solid #F56C6C;
  816. }
  817. .outtext.red-border textarea{
  818. border: 1px solid #F56C6C;
  819. }
  820. </style>