|
|
@@ -42,9 +42,6 @@ export const decodeHtmlEntities = (str: string): string => {
|
|
|
'atilde': 'ã',
|
|
|
'otilde': 'õ',
|
|
|
// 其他常用实体
|
|
|
- 'amp': '&',
|
|
|
- 'lt': '<',
|
|
|
- 'gt': '>',
|
|
|
'quot': '"',
|
|
|
'apos': "'",
|
|
|
'nbsp': '\u00A0',
|
|
|
@@ -55,13 +52,105 @@ export const decodeHtmlEntities = (str: string): string => {
|
|
|
'ndash': '–',
|
|
|
'hellip': '…',
|
|
|
// 数学符号
|
|
|
+ 'amp': '&',
|
|
|
'times': '×',
|
|
|
'divide': '÷',
|
|
|
'plusmn': '±',
|
|
|
+ 'deg': '°',
|
|
|
+ 'radic': '√',
|
|
|
+ 'isin': '∈',
|
|
|
+ 'infin': '∞',
|
|
|
+ 'there4': '∴',
|
|
|
+ 'because': '∵',
|
|
|
+ 'forall': '∀',
|
|
|
+ 'exists': '∃',
|
|
|
+ 'nexists': '∄',
|
|
|
+ 'notin': '∉',
|
|
|
+ 'ni': '∋',
|
|
|
+ 'prod': '∏',
|
|
|
+ 'sum': '∑',
|
|
|
+ 'prop': '∝',
|
|
|
+ 'sim': '∼',
|
|
|
+ 'simeq': '≃',
|
|
|
+ 'approx': '≈',
|
|
|
+ 'neq': '≠',
|
|
|
+ 'equiv': '≡',
|
|
|
+ 'ne': '≠',
|
|
|
+ 'le': '≤',
|
|
|
+ 'ge': '≥',
|
|
|
+ 'll': '≪',
|
|
|
+ 'gg': '≫',
|
|
|
+ 'prec': '≺',
|
|
|
+ 'succ': '≻',
|
|
|
+ 'preceq': '≼',
|
|
|
+ 'succeq': '≽',
|
|
|
+ 'subset': '⊂',
|
|
|
+ 'supset': '⊃',
|
|
|
+ 'subseteq': '⊆',
|
|
|
+ 'supseteq': '⊇',
|
|
|
+ 'perp': '⊥',
|
|
|
+ 'parallel': '∥',
|
|
|
+ 'wedge': '∧',
|
|
|
+ 'vee': '∨',
|
|
|
+ 'cap': '∩',
|
|
|
+ 'cup': '∪',
|
|
|
+ 'int': '∫',
|
|
|
+ 'oint': '∮',
|
|
|
+ 'angle': '∠',
|
|
|
+ 'mid': '∣',
|
|
|
+ 'vdash': '⊢',
|
|
|
+ 'dashv': '⊣',
|
|
|
+ 'models': '⊨',
|
|
|
+ 'vDash': '⊩',
|
|
|
+ 'Vdash': '⊫',
|
|
|
+ 'VDash': '⊬',
|
|
|
+ // 希腊字母
|
|
|
+ 'alpha': 'α',
|
|
|
+ 'beta': 'β',
|
|
|
+ 'gamma': 'γ',
|
|
|
+ 'delta': 'δ',
|
|
|
+ 'epsilon': 'ε',
|
|
|
+ 'zeta': 'ζ',
|
|
|
+ 'eta': 'η',
|
|
|
+ 'theta': 'θ',
|
|
|
+ 'iota': 'ι',
|
|
|
+ 'kappa': 'κ',
|
|
|
+ 'lambda': 'λ',
|
|
|
+ 'mu': 'μ',
|
|
|
+ 'nu': 'ν',
|
|
|
+ 'xi': 'ξ',
|
|
|
+ 'omicron': 'ο',
|
|
|
+ 'pi': 'π',
|
|
|
+ 'rho': 'ρ',
|
|
|
+ 'sigma': 'σ',
|
|
|
+ 'tau': 'τ',
|
|
|
+ 'upsilon': 'υ',
|
|
|
+ 'phi': 'φ',
|
|
|
+ 'chi': 'χ',
|
|
|
+ 'psi': 'ψ',
|
|
|
+ 'omega': 'ω',
|
|
|
+ 'thetasym': 'ϑ',
|
|
|
+ 'upsih': 'ϒ',
|
|
|
+ 'piv': 'ϖ',
|
|
|
};
|
|
|
|
|
|
let result = str;
|
|
|
|
|
|
+ // 清除所有的特殊空格实体
|
|
|
+ result = result.replaceAll("‍", "")
|
|
|
+ .replaceAll(" ", "")
|
|
|
+ .replaceAll("‌", "")
|
|
|
+ .replaceAll(" ", "")
|
|
|
+ .replaceAll(" ", "")
|
|
|
+ .replaceAll(" ", "")
|
|
|
+ .replaceAll("​", "");
|
|
|
+
|
|
|
+ // 0. 保护真正的 HTML 标签,转义文本中的 < 和 >
|
|
|
+ // 策略:< 后面如果不是字母或 /(标签开始),则转义为 <
|
|
|
+ // 这样可以处理 x<2、1<x<5 等数学表达式
|
|
|
+ // result = result.replace(/<(?![a-zA-Z/])/g, '<'); // < 后面不是字母或 / 的转义
|
|
|
+ // result = result.replace(/(?<![a-zA-Z0-9])>/g, '>'); // > 前面不是字母数字的转义(避免破坏标签)
|
|
|
+
|
|
|
// 1. 处理非标准的上标实体:&sup数字; → <sup>数字</sup>
|
|
|
// 例如:² → <sup>2</sup>, ¹23; → <sup>123</sup>
|
|
|
result = result.replace(/&sup(\d+);/gi, (match, digits) => {
|
|
|
@@ -74,8 +163,9 @@ export const decodeHtmlEntities = (str: string): string => {
|
|
|
return `<sub>${digits}</sub>`;
|
|
|
});
|
|
|
|
|
|
- // 3. 处理标准命名实体(如 í)
|
|
|
- result = result.replace(/&([a-z]+);/gi, (match, entity) => {
|
|
|
+ // 3. 处理标准命名实体(如 í、∴)
|
|
|
+ // 注意:正则需要支持字母和数字的组合
|
|
|
+ result = result.replace(/&([a-z0-9]+);/gi, (match, entity) => {
|
|
|
const lowerEntity = entity.toLowerCase();
|
|
|
if (entityMap[lowerEntity]) {
|
|
|
return entityMap[lowerEntity];
|
|
|
@@ -456,12 +546,12 @@ export const useExam = () => {
|
|
|
// 开始计时
|
|
|
const startTiming = () => {
|
|
|
startCount();
|
|
|
-
|
|
|
+
|
|
|
// 记录开始时间戳(毫秒)
|
|
|
if (practiceStartTime === 0) {
|
|
|
practiceStartTime = performance.now();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 使用 requestAnimationFrame 更新显示,更流畅且性能更好
|
|
|
const updatePracticeDuration = () => {
|
|
|
if (practiceStartTime > 0) {
|
|
|
@@ -472,20 +562,20 @@ export const useExam = () => {
|
|
|
animationFrameId = requestAnimationFrame(updatePracticeDuration);
|
|
|
}
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
// 开始动画帧循环
|
|
|
animationFrameId = requestAnimationFrame(updatePracticeDuration);
|
|
|
}
|
|
|
// 停止计时
|
|
|
const stopTiming = () => {
|
|
|
stopCount();
|
|
|
-
|
|
|
+
|
|
|
// 取消动画帧
|
|
|
if (animationFrameId !== null) {
|
|
|
cancelAnimationFrame(animationFrameId);
|
|
|
animationFrameId = null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果正在计时,累加经过的时间
|
|
|
if (practiceStartTime > 0) {
|
|
|
const elapsed = (performance.now() - practiceStartTime) / 1000;
|
|
|
@@ -586,6 +676,7 @@ export const useExam = () => {
|
|
|
const transerQuestion = (item: Study.ExamineeQuestion, index: number): Study.Question => {
|
|
|
return {
|
|
|
...item,
|
|
|
+ title: decodeHtmlEntities(item.title || ''),
|
|
|
// 处理没有题型的大题,统一作为阅读题
|
|
|
typeId: (item.typeId === null || item.typeId === undefined) ? EnumQuestionType.OTHER : item.typeId,
|
|
|
answers: item.answers || [],
|