common.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. import DOMPurify from 'dompurify';
  2. import {
  3. changeNumToHan
  4. } from '@/utils/transform';
  5. // 选项类型
  6. export const optionTypeList = [{
  7. value: 'letter',
  8. label: '字母'
  9. },
  10. {
  11. value: 'number',
  12. label: '数字'
  13. },
  14. {
  15. value: 'capital',
  16. label: '大写字母'
  17. },
  18. {
  19. value: 'bracket_number',
  20. label: '括号数字'
  21. },
  22. ];
  23. // 题号选项
  24. export const computeOptionMethods = {
  25. [optionTypeList[0].value]: (i) => `${String.fromCharCode(97 + i)}.`,
  26. [optionTypeList[1].value]: (i) => `${i + 1}.`,
  27. [optionTypeList[2].value]: (i) => `${String.fromCharCode(65 + i)}.`,
  28. // [optionTypeList[2].value]: (i) => changeNumToHan(i + 1) + '.',
  29. // [optionTypeList[3].value]: (i) => `(${i + 1})`, //中文括号
  30. [optionTypeList[3].value]: (i) => `(${i + 1})`, //英文括号
  31. };
  32. /**
  33. * 改变选项类型
  34. * @param {object} data 数据
  35. */
  36. export function changeOptionType(data) {
  37. let index = optionTypeList.findIndex(({
  38. value
  39. }) => value === data.option_number_show_mode);
  40. data.option_number_show_mode = optionTypeList[index + 1]?.value || optionTypeList[0].value;
  41. }
  42. /**
  43. * 计算选项题号
  44. * @param {Number} i 序号
  45. * @param {String} option_number_show_mode 选项类型
  46. * @returns String 题号
  47. */
  48. export function computedQuestionNumber(i, option_number_show_mode) {
  49. const computationMethod = computeOptionMethods[option_number_show_mode];
  50. if (computationMethod) {
  51. return computationMethod(i);
  52. }
  53. return '';
  54. }
  55. // 题干类型
  56. export const stemTypeList = [{
  57. value: 'text',
  58. label: '纯文本'
  59. },
  60. {
  61. value: 'rich',
  62. label: '富文本'
  63. },
  64. ];
  65. // 分值类型
  66. export const scoreTypeList = [{
  67. value: 'aggregate',
  68. label: '总分'
  69. },
  70. {
  71. value: 'subdivision',
  72. label: '细分'
  73. },
  74. ];
  75. // 选择类型
  76. export const selectTypeList = [{
  77. value: 'single',
  78. label: '单选'
  79. },
  80. {
  81. value: 'multiple',
  82. label: '多选'
  83. },
  84. ];
  85. // 开关选项
  86. export const switchOption = [{
  87. value: 'true',
  88. label: '开启'
  89. },
  90. {
  91. value: 'false',
  92. label: '关闭'
  93. },
  94. ];
  95. // 题号类型
  96. export const questionNumberTypeList = [{
  97. value: 'recalculate',
  98. label: '重新计算'
  99. },
  100. {
  101. value: 'follow',
  102. label: '跟随上题'
  103. },
  104. ];
  105. // 选项类型列表
  106. export const option_type_list = [{
  107. value: 'right',
  108. label: '是'
  109. },
  110. {
  111. value: 'error',
  112. label: '非'
  113. },
  114. {
  115. value: 'incertitude',
  116. label: '不确定'
  117. },
  118. ];
  119. // 布局方式,horizontal 【横排】,vertical【竖排】
  120. export const layout_type_list = [{
  121. value: 'horizontal',
  122. label: '横排'
  123. },
  124. {
  125. value: 'vertical',
  126. label: '竖排'
  127. }
  128. ];
  129. // 答题模式列表
  130. export const answer_mode_list = [{
  131. value: 1,
  132. label: '练习模式'
  133. },
  134. {
  135. value: 2,
  136. label: '考试模式'
  137. }
  138. ];
  139. // 答题模式列表
  140. export const answer_status_list = [{
  141. value: 0,
  142. label: '未作答'
  143. },
  144. {
  145. value: 1,
  146. label: '客观题(正确)'
  147. },
  148. {
  149. value: 2,
  150. label: '客观题(错误)'
  151. },
  152. {
  153. value: 3,
  154. label: '主观题(已答)'
  155. }
  156. ];
  157. // 任务时间类型列表
  158. export const time_type_list = [{
  159. value: 0,
  160. label: '课前任务'
  161. },
  162. {
  163. value: 1,
  164. label: '课中任务'
  165. },
  166. {
  167. value: 2,
  168. label: '课后任务'
  169. },
  170. {
  171. value: 3,
  172. label: '练习任务'
  173. }
  174. ];
  175. // 任务完成状态列表
  176. export const task_finish_status_list = [{
  177. value: 0,
  178. label: '未开始'
  179. },
  180. {
  181. value: 1,
  182. label: '进行中'
  183. },
  184. {
  185. value: 2,
  186. label: '结束'
  187. }
  188. ];
  189. export const questionData = {
  190. share_record_id: '',
  191. parentType: '',
  192. answer_record_id: '',
  193. question_id: '',
  194. type: '', // 题型
  195. stem: '', // 题干
  196. option_number_show_mode: optionTypeList[0].value, // 选项类型
  197. description: '', // 描述
  198. option_list: [{
  199. content: '',
  200. mark: '',
  201. answer: '',
  202. checked: false
  203. }], // 选项
  204. question_list: [{ //阅读题特有
  205. id: '', // 小题id
  206. type: '', // 小题类型
  207. additional_type: '', // 附加类型多选单选等
  208. }],
  209. file_id_list: [], // 文件 id 列表
  210. file_list: [],
  211. audioSrc: '',
  212. answer: {
  213. answer_list: [],
  214. score: 0,
  215. score_type: scoreTypeList[0].value,
  216. }, // 答案
  217. // 题型属性
  218. property: {
  219. stem_type: stemTypeList[0].value, // 题干类型
  220. question_number: 1, // 题号
  221. is_enable_description: false, // 描述
  222. select_type: selectTypeList[0].value, // 选择类型
  223. is_enable_listening: true, // 是否听力
  224. score: 1, // 分值
  225. score_type: scoreTypeList[0].value, // 分值类型
  226. },
  227. // 其他属性
  228. other: {
  229. question_number_type: questionNumberTypeList[0].value, // 题号类型
  230. },
  231. user_answer: {},
  232. audio: new Audio(),
  233. playing: false,
  234. isSubSub: false, //子组件的子组件
  235. isLoaded: false, //数据加载完成
  236. remark: {}, //教师批注
  237. answer_mode: 1, //答题模式
  238. /**
  239. * 阅读题专用,用于切换题目时给阅读题小题答题控制赋值
  240. */
  241. isReadQuestionWatch: false,
  242. }
  243. export const answer_control = {};
  244. //是否直接切换题目索引
  245. export const isSwitchQuestionIndex = {};
  246. /**
  247. * 过滤 html,防止 xss 攻击
  248. * @param {String} html 需要过滤的html
  249. * @returns {String} 过滤后的html
  250. */
  251. export function sanitizeHTML(html) {
  252. html = html.replace(/<video[^>]*>/g, function(match) {
  253. if (match.includes('controlsList="')) {
  254. return match.replace(/controlsList="[^"]*"/,
  255. 'controlsList="nodownload nodirection noplaybackrate" disablePictureInPicture');
  256. } else {
  257. return match.replace('>', ' controlsList="nodownload nodirection noplaybackrate" disablePictureInPicture>');
  258. }
  259. });
  260. var platform = "android";
  261. uni.getSystemInfo({
  262. success: function(res) {
  263. platform = res.platform; // 操作系统类型,如 "android", "ios" 等
  264. }
  265. });
  266. if (platform === "android") {
  267. html = html.replace(/<audio[^>]*>/g, function(match) {
  268. if (match.includes('controlsList="')) {
  269. return match.replace(/controlsList="[^"]*"/,
  270. 'controlsList="nodownload nodirection noplaybackrate" style="height:47px"');
  271. } else {
  272. return match.replace('>',
  273. ' controlsList="nodownload nodirection noplaybackrate" style="height:47px">');
  274. }
  275. });
  276. html = html.replace(/<p><audio[^>]*>/g, function(match) {
  277. return match.replace('<p>', '<p style="width:47px;height:47px;overflow:hidden;border-radius:24px">');
  278. });
  279. } else {
  280. html = html.replace(/<audio[^>]*>/g, function(match) {
  281. if (match.includes('controlsList="')) {
  282. return match.replace(/controlsList="[^"]*"/,
  283. 'controlsList="nodownload nodirection noplaybackrate" style="height:27px;margin:0"');
  284. } else {
  285. return match.replace('>',
  286. ' controlsList="nodownload nodirection noplaybackrate" style="height:27px;margin:0">');
  287. }
  288. });
  289. html = html.replace(/<p><audio[^>]*>/g, function(match) {
  290. return match.replace('<p>',
  291. '<p style="width:40px;height:32px;background-color:#818181;overflow:hidden;border-radius:8px">'
  292. );
  293. });
  294. }
  295. return DOMPurify.sanitize(html);
  296. }
  297. /**
  298. * 是否开启
  299. * @param {String} value 值
  300. * @returns Boolean
  301. */
  302. export function isEnable(value) {
  303. return value === switchOption[0].value;
  304. }
  305. export const tone_data = [
  306. ['ā', 'á', 'ǎ', 'à', 'a'],
  307. ['ō', 'ó', 'ǒ', 'ò', 'o'],
  308. ['ē', 'é', 'ě', 'è', 'e'],
  309. ['ī', 'í', 'ǐ', 'ì', 'i'],
  310. ['ū', 'ú', 'ǔ', 'ù', 'u'],
  311. ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
  312. ['ǖ', 'ǘ', 'ǚ', 'ǜ', 'ü'],
  313. ['Ā', 'Á', 'Â', 'À', 'A'],
  314. ['Ō', 'Ó', 'Ô', 'Ò', 'O'],
  315. ['Ē', 'É', 'Ê', 'È', 'E'],
  316. ['Ī', 'Í', 'Î', 'Ì', 'I'],
  317. ['Ū', 'Ú', 'Û', 'Ù', 'U'],
  318. ];
  319. /**
  320. * 添加声调
  321. * @param {Number} number
  322. * @param {String} con
  323. * @returns String
  324. */
  325. export function addTone(number, con) {
  326. const zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'ü', 'A', 'O', 'E', 'I', 'U'];
  327. let cons = con;
  328. if (number) {
  329. for (let i = 0; i < zmList.length; i++) {
  330. let zm = zmList[i];
  331. if (con.includes(zm)) {
  332. let zm2 = tone_data[i][number - 1];
  333. if (con.includes('iu')) {
  334. zm2 = tone_data[4][number - 1];
  335. cons = con.replace('u', zm2);
  336. } else if (con.includes('ui')) {
  337. zm2 = tone_data[3][number - 1];
  338. cons = con.replace('i', zm2);
  339. } else if (/yv|jv|qv|xv/.test(con)) {
  340. zm2 = tone_data[4][number - 1];
  341. cons = con.replace('v', zm2);
  342. } else if (/yü|jü|qü|xü/.test(con)) {
  343. zm2 = tone_data[4][number - 1];
  344. cons = con.replace('ü', zm2);
  345. } else {
  346. cons = con.replace(zm, zm2);
  347. }
  348. break;
  349. }
  350. }
  351. }
  352. return cons;
  353. }
  354. export function handleToneValue(valItem) {
  355. let numList = [];
  356. if (/[A-Za-zü]+\d/g.test(valItem)) {
  357. valItem.split('').forEach((item, i) => {
  358. if (/\d/.test(item)) {
  359. let numIndex = numList.length === 0 ? 0 : numList[numList.length - 1].index;
  360. let con = valItem.substring(numIndex, i).replace(/\d/g, '');
  361. numList.push({
  362. number: item,
  363. con,
  364. });
  365. }
  366. });
  367. } else {
  368. numList = [];
  369. }
  370. return numList.length === 0 ? [{
  371. con: valItem
  372. }] : numList;
  373. }
  374. export const svgNS = 'http://www.w3.org/2000/svg'; // SVG命名空间