Character.vue 12 KB

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