Character.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <template>
  2. <ModuleBase :type="data.type">
  3. <template #content>
  4. <!-- eslint-disable max-len -->
  5. <div class="character-wrapper">
  6. <div class="fun-type">
  7. <a
  8. v-for="{ value, label } in modelList"
  9. :key="value"
  10. :class="[data.property.model === value ? 'active' : '']"
  11. @click="data.property.model = value"
  12. >{{ label }}</a
  13. >
  14. </div>
  15. <div v-for="(item, index) in data.option_list" :key="index" class="content-box">
  16. <div class="content-item">
  17. <el-input
  18. v-model="item.content"
  19. maxlength="10"
  20. show-word-limit
  21. placeholder="输入汉字,@:代表图片,#:代表书写格"
  22. @blur="handleMindMap"
  23. />
  24. <el-button @click="identify(item, index)">识别</el-button>
  25. <SoundRecord v-if="item.audio_file_id" :wav-blob.sync="item.audio_file_id" />
  26. <el-button v-else @click="handleMatic(item.content, index)">生成音频</el-button>
  27. <el-button @click="handleDelete(index)">删除此条</el-button>
  28. </div>
  29. <div class="option-item">
  30. <template v-if="data.property.model === 'miao'">
  31. <span>显示本体:</span>
  32. <el-radio-group v-model="item.is_show_ben">
  33. <el-radio :label="true">是</el-radio>
  34. <el-radio :label="false">否</el-radio>
  35. </el-radio-group>
  36. </template>
  37. <template v-if="data.property.model === 'write'">
  38. <span>需要间距:</span>
  39. <el-radio-group v-model="item.is_margin">
  40. <el-radio :label="true">是</el-radio>
  41. <el-radio :label="false">否</el-radio>
  42. </el-radio-group>
  43. </template>
  44. <template v-if="isEnable(data.property.view_pinyin)">
  45. <!-- <span>共用拼音:</span>
  46. <el-radio-group v-model="item.is_common_pinyin">
  47. <el-radio :label="true">是</el-radio>
  48. <el-radio :label="false">否</el-radio>
  49. </el-radio-group> -->
  50. <template v-if="item.is_common_pinyin">
  51. <span>拼音</span>
  52. <el-input v-model="item.pinyin" placeholder="输入拼音" />
  53. </template>
  54. </template>
  55. <template v-if="isEnable(data.property.is_enable_shiyi)">
  56. <span>释义</span>
  57. <el-input v-model="item.shiyi" placeholder="输入释义" />
  58. </template>
  59. </div>
  60. <div v-for="(items, indexs) in item.content_list" :key="indexs" class="content-items">
  61. <template v-if="items">
  62. <label>内容:{{ items.con }} </label>
  63. <UploadFile
  64. v-if="items.type === 'img'"
  65. :type="data.type"
  66. :file-list="items.file_list"
  67. :file-id-list="items.file_id_list"
  68. :label-text="'图片'"
  69. :accept-file-type="acceptFileType"
  70. :index="index"
  71. :indexs="indexs"
  72. :limit="1"
  73. @updateFileList="updateFileList"
  74. />
  75. <div class="option-item">
  76. <template v-if="isEnable(data.property.view_pinyin) && !item.is_common_pinyin">
  77. <span>拼音</span>
  78. <el-input v-model="items.pinyin" placeholder="输入拼音" />
  79. </template>
  80. <!-- <template v-if="isEnable(data.property.is_enable_shiyi) && data.property.model === 'input'">
  81. <span>释义</span>
  82. <el-input v-model="items.shiyi" placeholder="输入释义"></el-input>
  83. </template> -->
  84. </div>
  85. </template>
  86. </div>
  87. </div>
  88. <el-button icon="el-icon-plus" style="margin: 10px 0" @click="addElement">增加一个</el-button>
  89. </div>
  90. </template>
  91. </ModuleBase>
  92. </template>
  93. <script>
  94. import ModuleMixin from '../../common/ModuleMixin';
  95. import { getCharacterData, modelList, getOption } from '@/views/book/courseware/data/character';
  96. import { GetStaticResources, TextToAudioFile } from '@/api/app';
  97. import cnchar from 'cnchar';
  98. import UploadFile from '../../base/common/UploadFile.vue';
  99. import SoundRecord from '@/views/book/courseware/create/components/question/fill/components/SoundRecord.vue';
  100. export default {
  101. name: 'CharacterPage',
  102. components: { UploadFile, SoundRecord },
  103. mixins: [ModuleMixin],
  104. data() {
  105. return {
  106. data: getCharacterData(),
  107. modelList,
  108. getOption,
  109. acceptFileType: '.png,.jpg,.jpeg',
  110. };
  111. },
  112. watch: {
  113. // 'data.content': 'handleMindMap',
  114. },
  115. methods: {
  116. // 解析输入内容
  117. handleChangeContent() {
  118. if (this.data.content.trim()) {
  119. let contentArr = this.data.content.split('\n');
  120. let contentList = [];
  121. contentArr.forEach((item, index) => {
  122. if (item.trim()) {
  123. let content_arr = item.trim().split('');
  124. let content_arrs = [];
  125. let content_arr_strokes = [];
  126. content_arr.forEach((itemc) => {
  127. if (itemc.trim()) {
  128. content_arrs.push(itemc.trim());
  129. }
  130. });
  131. content_arrs.forEach((itemc, indexc) => {
  132. content_arr_strokes.push(null);
  133. let MethodName = 'hz_resource_manager-GetHZStrokesContent';
  134. let data = {
  135. hz: itemc,
  136. };
  137. GetStaticResources(MethodName, data).then((res) => {
  138. let obj = {
  139. hz: itemc.trim(),
  140. strokes: res,
  141. };
  142. content_arr_strokes[indexc] = obj;
  143. });
  144. });
  145. contentList.push({
  146. con: item.trim(),
  147. pinyin: cnchar.spell(item.trim(), 'array', 'low', 'tone').join(' '),
  148. audio_file_id: '',
  149. hz_strokes_list: content_arr_strokes,
  150. });
  151. this.handleMatic(item.trim(), contentList.length - 1);
  152. }
  153. });
  154. this.data.content_list = contentList;
  155. } else {
  156. this.data.content_list = [];
  157. }
  158. this.handleMindMap();
  159. },
  160. // 自动生成音频
  161. handleMatic(con, index) {
  162. TextToAudioFile({
  163. text: con.replace(/<[^>]+>/g, ''),
  164. voice_type: this.data.property.voice_type,
  165. emotion: this.data.property.emotion,
  166. speed_ratio: this.data.property.speed_ratio,
  167. })
  168. .then(({ status, file_id, file_url }) => {
  169. if (status === 1) {
  170. this.data.option_list[index].audio_file_id = {
  171. file_url,
  172. file_id,
  173. };
  174. }
  175. })
  176. .catch(() => {});
  177. },
  178. updateFileList({ file_list, file_id_list }, index, indexs) {
  179. this.data.option_list[index].content_list[indexs].file_list = file_list;
  180. this.data.option_list[index].content_list[indexs].file_id_list = file_id_list;
  181. },
  182. // 增加
  183. addElement() {
  184. this.data.option_list.push(getOption());
  185. },
  186. // 删除
  187. handleDelete(index) {
  188. this.data.option_list.splice(index, 1);
  189. },
  190. handleMindMap() {
  191. // 思维导图数据
  192. let node_list = [];
  193. this.data.option_list.forEach((item) => {
  194. node_list.push({
  195. name: item.content.replace(/<[^>]*>?/gm, ''),
  196. id: Math.random().toString(36).substring(2, 12),
  197. });
  198. });
  199. this.data.mind_map.node_list = node_list;
  200. },
  201. // 识别
  202. async identify(items, index) {
  203. let con = items.content.trim();
  204. if (con) {
  205. items.content_list = [];
  206. let arr = con.split('');
  207. const regex = /[\u4E00-\u9FFF]/;
  208. let str = '';
  209. arr.forEach((item) => {
  210. if (regex.test(item)) {
  211. str += item;
  212. }
  213. });
  214. let MethodName = 'hz_resource_manager-GetMultHZStrokesContent';
  215. let data = {
  216. hz_str: str,
  217. };
  218. items.pinyin = cnchar.spell(str, 'array', 'low', 'tone').join(' ');
  219. await GetStaticResources(MethodName, data)
  220. .then((res) => {
  221. for (let key in res) {
  222. if (key != 'status' && key != ',' && res[key]) {
  223. res[key] = JSON.parse(res[key]);
  224. }
  225. }
  226. let hzDetailList = res;
  227. arr.forEach((item, index) => {
  228. let objs = {};
  229. if (item === '@') {
  230. // 图片
  231. objs = {
  232. con: item,
  233. type: 'img',
  234. file_list: [],
  235. file_id_list: [],
  236. pinyin: '',
  237. };
  238. } else if (item === '#') {
  239. // 书写
  240. objs = {
  241. con: item,
  242. type: 'write',
  243. img: '',
  244. base64: '',
  245. pinyin: '',
  246. };
  247. } else if (regex.test(item)) {
  248. // 汉字
  249. let hz_list = [];
  250. let res = JSON.parse(JSON.stringify(hzDetailList[item]));
  251. let obj = {
  252. con: item,
  253. hzDetail: {
  254. hz_json: res,
  255. },
  256. };
  257. hz_list.push(obj);
  258. objs = {
  259. con: item,
  260. type: 'hanzi',
  261. hz_info: hz_list,
  262. pinyin: cnchar.spell(item, 'array', 'low', 'tone').join(' '),
  263. shiyi: '',
  264. answer: '',
  265. is_example: false,
  266. answer_pinyin: '',
  267. answer_en: '',
  268. is_can_input_answer: true,
  269. high_strokes: '',
  270. };
  271. } else {
  272. // 连字符
  273. objs = {
  274. con: item,
  275. type: 'lian',
  276. pinyin: '',
  277. };
  278. }
  279. this.$set(items.content_list, index, objs);
  280. });
  281. })
  282. .catch(() => {});
  283. if (str) {
  284. this.handleMatic(str, index);
  285. }
  286. } else {
  287. this.$message.warning('请先输入内容');
  288. }
  289. },
  290. },
  291. };
  292. </script>
  293. <style lang="scss" scoped>
  294. .character-wrapper {
  295. display: flex;
  296. flex-direction: column;
  297. row-gap: 16px;
  298. align-items: flex-start;
  299. :deep .rich-wrapper {
  300. width: 100%;
  301. }
  302. .tips {
  303. font-size: 12px;
  304. color: #999;
  305. }
  306. .fun-type {
  307. display: flex;
  308. gap: 5px;
  309. width: 100%;
  310. padding-bottom: 10px;
  311. border-bottom: 1px solid #e5e6eb;
  312. a {
  313. padding: 5px 10px;
  314. font-weight: normal;
  315. color: #1d2129;
  316. cursor: pointer;
  317. background: #f2f3f5;
  318. border: 1px solid #f2f3f5;
  319. border-radius: 2px;
  320. &.active {
  321. color: #165dff;
  322. background: #e7eeff;
  323. border-color: #165dff;
  324. }
  325. }
  326. }
  327. .upload-file {
  328. display: flex;
  329. column-gap: 12px;
  330. align-items: center;
  331. margin: 8px 0;
  332. .file-name {
  333. display: flex;
  334. column-gap: 14px;
  335. align-items: center;
  336. justify-content: space-between;
  337. max-width: 360px;
  338. padding: 8px 12px;
  339. font-size: 14px;
  340. color: #1d2129;
  341. background-color: #f7f8fa;
  342. span {
  343. display: flex;
  344. column-gap: 14px;
  345. align-items: center;
  346. }
  347. }
  348. .svg-icon {
  349. cursor: pointer;
  350. }
  351. }
  352. .fun-type {
  353. display: flex;
  354. gap: 5px;
  355. width: 100%;
  356. padding-bottom: 10px;
  357. border-bottom: 1px solid #e5e6eb;
  358. a {
  359. padding: 5px 10px;
  360. font-weight: normal;
  361. color: #1d2129;
  362. cursor: pointer;
  363. background: #f2f3f5;
  364. border: 1px solid #f2f3f5;
  365. border-radius: 2px;
  366. &.active {
  367. color: #165dff;
  368. background: #e7eeff;
  369. border-color: #165dff;
  370. }
  371. }
  372. }
  373. .content-box {
  374. width: 100%;
  375. }
  376. .content-item {
  377. display: flex;
  378. gap: 16px;
  379. .el-input {
  380. max-width: 400px;
  381. }
  382. }
  383. .content-items {
  384. margin: 10px 0;
  385. label {
  386. font-size: 14px;
  387. line-height: 34px;
  388. color: #4e5969;
  389. }
  390. }
  391. .option-item {
  392. display: flex;
  393. flex-flow: wrap;
  394. align-items: center;
  395. margin-top: 5px;
  396. span {
  397. flex-shrink: 0;
  398. width: max-content;
  399. margin-right: 10px;
  400. font-size: 14px;
  401. color: #4e5969;
  402. }
  403. .el-input {
  404. max-width: 100px;
  405. margin-right: 30px;
  406. }
  407. .el-radio-group {
  408. margin-right: 30px;
  409. }
  410. }
  411. }
  412. </style>