Select.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <template>
  2. <ModuleBase ref="base" :type="data.type">
  3. <template #content>
  4. <div v-loading="loading" class="select-wrapper">
  5. <ul class="option-list">
  6. <li v-for="(item, i) in data.option_list" :key="item.mark" class="option-item">
  7. <span
  8. v-if="!('enable_serial' in data.property) || isEnable(data.property.enable_serial)"
  9. class="serial-number"
  10. >{{ computedOptionNumber(i) }}.</span
  11. >
  12. <div class="option-contnet">
  13. <span
  14. :class="[isSingle ? 'radio' : 'checkbox', { active: isAnswer(item.mark) }]"
  15. @click="selectAnswer(item.mark)"
  16. >
  17. <SvgIcon icon-class="check-mark" width="10" height="7" />
  18. </span>
  19. <RichText
  20. v-if="property.isGetContent"
  21. ref="richText"
  22. v-model="item.content"
  23. :font-size="data?.unified_attrib?.font_size"
  24. :font-family="data?.unified_attrib?.font"
  25. :font-color="data?.unified_attrib?.text_color"
  26. placeholder="输入内容"
  27. :inline="true"
  28. :height="32"
  29. />
  30. </div>
  31. <span class="multilingual" @click="openMultilingual(i)">
  32. <SvgIcon icon-class="multilingual" class-name="multilingual" width="12" height="12" />
  33. </span>
  34. <span class="delete" @click="deleteOption(i)">
  35. <SvgIcon icon-class="delete-2" width="12" height="12" />
  36. </span>
  37. </li>
  38. </ul>
  39. <div class="add">
  40. <SvgIcon icon-class="add-circle" width="14" height="14" />
  41. <span class="add-button" @click="addOption">增加选项</span>
  42. </div>
  43. <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left">
  44. <span>显示效果</span>
  45. <el-button
  46. type="text"
  47. icon="el-icon-refresh"
  48. title="刷新"
  49. class="refresh-pinyin-btn"
  50. @click.native="handleViewPinyin(true)"
  51. />
  52. </el-divider>
  53. <PinyinText
  54. v-for="(item, i) in data.option_list"
  55. v-show="isEnable(data.property.view_pinyin)"
  56. :key="i"
  57. ref="PinyinText"
  58. :paragraph-list="item.paragraph_list"
  59. :rich-text-list="item.rich_text_list"
  60. :pinyin-position="data.property.pinyin_position"
  61. @fillCorrectPinyin="fillCorrectPinyin($event, i)"
  62. />
  63. </div>
  64. <MultilingualFill
  65. v-if="curSelectIndex !== -1"
  66. :visible.sync="multilingualVisible"
  67. :text="data.option_list[curSelectIndex].content"
  68. :translations="data.option_list[curSelectIndex].multilingual"
  69. @SubmitTranslation="handleMultilingualTranslation"
  70. />
  71. <AnswerAnalysisList
  72. v-if="data.answer_list?.length > 0 || data.analysis_list?.length > 0"
  73. :answer-list="data.answer_list"
  74. :analysis-list="data.analysis_list"
  75. :unified-attrib="data.unified_attrib"
  76. @updateAnswerAnalysisFileList="updateAnswerAnalysisFileList"
  77. @deleteAnswerAnalysis="deleteAnswerAnalysis"
  78. />
  79. </template>
  80. </ModuleBase>
  81. </template>
  82. <script>
  83. import ModuleMixin from '../../common/ModuleMixin';
  84. import PinyinText from '@/components/PinyinText.vue';
  85. import { getSelectData, getOption, arrangeTypeList, selectTypeList } from '@/views/book/courseware/data/select';
  86. import { serialNumberTypeList, computeOptionMethods } from '@/views/book/courseware/data/common';
  87. export default {
  88. name: 'SelectPage',
  89. components: {
  90. PinyinText,
  91. },
  92. mixins: [ModuleMixin],
  93. data() {
  94. return {
  95. data: getSelectData(),
  96. curSelectIndex: -1,
  97. loading: false,
  98. };
  99. },
  100. computed: {
  101. isSingle() {
  102. return this.data.property?.select_type === selectTypeList[0].value;
  103. },
  104. },
  105. watch: {
  106. 'data.property.arrange_type': 'handlerMindMap',
  107. 'data.answer.answer_list': 'handlerMindMap',
  108. 'data.property': {
  109. handler(val) {
  110. if (this.property.isSilentPropertyWatch) return;
  111. if (val.view_pinyin === 'true') {
  112. this.handleViewPinyin();
  113. }
  114. },
  115. deep: true,
  116. },
  117. 'data.property.select_type': {
  118. handler() {
  119. if (this.isSingle && this.data.answer.answer_list.length > 1) {
  120. this.data.answer.answer_list = [this.data.answer.answer_list[0]];
  121. }
  122. },
  123. },
  124. },
  125. methods: {
  126. async handleViewPinyin(isPrompt = false) {
  127. if (isPrompt) {
  128. try {
  129. await this.$confirm('刷新将会重置分词和拼音,是否刷新?', '提示', {
  130. confirmButtonText: '确定',
  131. cancelButtonText: '取消',
  132. type: 'warning',
  133. });
  134. } catch (e) {
  135. return;
  136. }
  137. }
  138. this.loading = true;
  139. if (!this.isEnable(this.data.property.view_pinyin)) {
  140. this.data.option_list.forEach((item) => {
  141. item.paragraph_list = [];
  142. item.paragraph_list_parameter = {
  143. text: '',
  144. pinyin_proofread_word_list: [],
  145. };
  146. });
  147. this.loading = false;
  148. return;
  149. }
  150. if (this.data.option_list.length > 0 && this.data.option_list[0].paragraph_list.length > 0) return;
  151. this.data.option_list.forEach((item, i) => {
  152. const text = item.content;
  153. if (!text) return;
  154. item.paragraph_list_parameter.text = text;
  155. this.createParsedTextInfoPinyin(text, i);
  156. });
  157. this.loading = false;
  158. },
  159. computedOptionNumber(number) {
  160. let type = serialNumberTypeList.find((item) => item.value === this.data.property.option_serial_type)?.value;
  161. if (!type) return number + 1;
  162. return computeOptionMethods[type](number);
  163. },
  164. // 将数字转换为小写字母
  165. convertNumberToLetter(number) {
  166. return String.fromCharCode(97 + number);
  167. },
  168. /**
  169. * @description 判断是否为答案
  170. * @param {string} mark 选项标记
  171. */
  172. isAnswer(mark) {
  173. return this.data.answer.answer_list.includes(mark);
  174. },
  175. /**
  176. * @description 选择答案
  177. * @param {string} mark 选项标记
  178. */
  179. selectAnswer(mark) {
  180. if (mark === null || mark === undefined) return;
  181. const list = this.data.answer.answer_list || [];
  182. const isSelected = list.includes(mark);
  183. if (this.isSingle) {
  184. this.data.answer.answer_list = isSelected ? [] : [mark];
  185. return;
  186. }
  187. this.data.answer.answer_list = isSelected ? list.filter((item) => item !== mark) : list.concat(mark);
  188. },
  189. /**
  190. * @description 添加选项
  191. */
  192. addOption() {
  193. this.data.option_list.push(getOption());
  194. },
  195. /**
  196. * @description 删除选项
  197. * @param {number} index 选项索引
  198. */
  199. deleteOption(index) {
  200. if (this.data.option_list.length <= 2) {
  201. this.$message.warning('请至少保留两个选项');
  202. return;
  203. }
  204. this.data.option_list.splice(index, 1);
  205. },
  206. handlerMindMap() {
  207. const direction = this.data.property.arrange_type === arrangeTypeList[0].value ? '横排' : '竖排';
  208. const select =
  209. this.data.property?.select_type === selectTypeList[1].value ? selectTypeList[1].label : selectTypeList[0].label;
  210. this.data.mind_map.node_list = [{ name: `${direction}${select}选择题` }];
  211. },
  212. openMultilingual(i) {
  213. this.curSelectIndex = i;
  214. this.multilingualVisible = true;
  215. },
  216. handleMultilingualTranslation(translations) {
  217. this.$set(this.data.option_list[this.curSelectIndex], 'multilingual', translations);
  218. },
  219. },
  220. };
  221. </script>
  222. <style lang="scss" scoped>
  223. .option-list {
  224. display: flex;
  225. flex-direction: column;
  226. row-gap: 8px;
  227. .option-item {
  228. display: flex;
  229. column-gap: 8px;
  230. align-items: center;
  231. min-height: 36px;
  232. .serial-number {
  233. width: 40px;
  234. height: 36px;
  235. padding: 4px 8px;
  236. color: $text-color;
  237. background-color: $fill-color;
  238. border-radius: 2px;
  239. }
  240. .option-contnet {
  241. display: flex;
  242. flex: 1;
  243. column-gap: 6px;
  244. align-items: center;
  245. padding: 0 12px;
  246. overflow: hidden;
  247. background-color: $fill-color;
  248. .radio,
  249. .checkbox {
  250. display: flex;
  251. align-items: center;
  252. justify-content: center;
  253. width: 16px;
  254. height: 16px;
  255. cursor: pointer;
  256. background-color: #fff;
  257. border: 1px solid #dcdfe6;
  258. border-radius: 2px;
  259. .svg-icon {
  260. display: none;
  261. }
  262. &.active {
  263. color: #fff;
  264. background-color: #000;
  265. .svg-icon {
  266. display: block;
  267. }
  268. }
  269. }
  270. .radio {
  271. border-radius: 50%;
  272. &.active {
  273. background-color: #fff;
  274. box-shadow: inset 0 0 0 4px #000;
  275. }
  276. }
  277. }
  278. .delete,
  279. .multilingual {
  280. margin-left: 8px;
  281. cursor: pointer;
  282. }
  283. }
  284. }
  285. .add {
  286. display: flex;
  287. column-gap: 6px;
  288. align-items: center;
  289. justify-content: center;
  290. margin-top: 12px;
  291. .svg-icon {
  292. cursor: pointer;
  293. }
  294. .add-button {
  295. font-size: 14px;
  296. color: $main-color;
  297. cursor: pointer;
  298. }
  299. }
  300. </style>