+
-
-
+
+
+
+ {{ Math.floor(volumeProgress) }}%
-
@@ -641,6 +600,7 @@ defineExpose({audioRef})
.volume-section {
display: flex;
align-items: center;
+ justify-content: center;
gap: 8px;
flex-shrink: 0;
position: relative;
@@ -671,13 +631,9 @@ defineExpose({audioRef})
.volume-dropdown {
position: absolute;
- top: 100%;
- left: 50%;
- transform: translateX(-50%);
background: var(--color-primary);
- border-radius: 4px;
+ border-radius: 8px;
padding: 8px;
- margin-top: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
opacity: 0;
visibility: hidden;
@@ -688,6 +644,14 @@ defineExpose({audioRef})
opacity: 1;
visibility: visible;
}
+
+ &.top {
+ bottom: 42px;
+ }
+
+ &.down {
+ top: 42px;
+ }
}
.volume-container {
@@ -705,35 +669,41 @@ defineExpose({audioRef})
width: 6px;
height: 100%;
background: var(--color-second);
- border-radius: 2px;
- overflow: hidden;
+ border-radius: 6px;
+ // overflow: hidden;
+}
+
+.volume-num {
+ display: flex;
+ position: absolute;
+ bottom: 0;
+ font-size: 12px;
+ color: #333;
+ transform: scale(0.85);
+ line-height: normal;
}
.volume-fill {
position: absolute;
- top: 0;
+ bottom: 0;
width: 100%;
height: var(--fill-height);
background: var(--color-fourth);
- border-radius: 2px;
-}
+ border-radius: 6px;
+ display: flex;
+ justify-content: center;
-.volume-thumb {
- position: absolute;
- left: 50%;
- top: var(--thumb-top);
- transform: translate(-50%, -50%);
- width: 10px;
- height: 10px;
- background: var(--color-fourth);
- border-radius: 50%;
- box-shadow: var(--audio-volume-thumb-shadow);
- cursor: grab;
- opacity: 1;
- transition: all 0.2s ease;
-
- &:active {
- cursor: grabbing;
+ &::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ width: 10px;
+ height: 10px;
+ border-radius: 100%;
+ background: var(--color-fourth);
+ transform: translateY(-50%);
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
+ cursor: grab;
}
}
@@ -772,6 +742,7 @@ defineExpose({audioRef})
0% {
transform: rotate(0deg);
}
+
100% {
transform: rotate(360deg);
}
diff --git a/src/components/list/ArticleList.vue b/src/components/list/ArticleList.vue
index 11e6c433..a4491444 100644
--- a/src/components/list/ArticleList.vue
+++ b/src/components/list/ArticleList.vue
@@ -4,12 +4,16 @@ import { Article } from "@/types/types.ts";
import BaseList from "@/components/list/BaseList.vue";
import BaseInput from "@/components/base/BaseInput.vue";
-const props = withDefaults(defineProps<{
- list: Article[],
- showTranslate?: boolean
-}>(), {
- list: [],
+interface IProps {
+ list: Article[];
+ showTranslate?: boolean;
+ activeId: string | number;
+}
+
+const props = withDefaults(defineProps
(), {
+ list: () => [] as Article[],
showTranslate: true,
+ activeId: ""
})
const emit = defineEmits<{
@@ -62,27 +66,20 @@ function scrollToItem(index: number) {
listRef?.scrollToItem(index)
}
-defineExpose({scrollToBottom, scrollToItem})
+defineExpose({ scrollToBottom, scrollToItem })
-
+
-
+
-
emit('click',e)"
- :list="localList"
- v-bind="$attrs">
+ emit('click', e)" :list="localList" v-bind="$attrs">
@@ -91,7 +88,7 @@ defineExpose({scrollToBottom, scrollToItem})
{{ `${searchKey ? '' : (index + 1) + '. '}${item.title}` }}
-
{{ ` ${item.titleTranslate}` }}
+
{{ ` ${item.titleTranslate}` }}
diff --git a/src/hooks/article.ts b/src/hooks/article.ts
index e5543bc1..1f82ae81 100644
--- a/src/hooks/article.ts
+++ b/src/hooks/article.ts
@@ -1,126 +1,131 @@
-import { Article, DictId, PracticeArticleWordType, Sentence } from "@/types/types.ts";
-import { _nextTick, cloneDeep } from "@/utils";
-import { usePlayWordAudio } from "@/hooks/sound.ts";
-import { getSentenceAllText, getSentenceAllTranslateText } from "@/hooks/translate.ts";
-import { getDefaultArticleWord } from "@/types/func.ts";
-import { useSettingStore } from "@/stores/setting.ts";
-import { useBaseStore } from "@/stores/base.ts";
-import { useRuntimeStore } from "@/stores/runtime.ts";
-
+import { Article, DictId, PracticeArticleWordType, Sentence } from "@/types/types.ts"
+import { _nextTick, cloneDeep } from "@/utils"
+import { usePlayWordAudio } from "@/hooks/sound.ts"
+import { getSentenceAllText, getSentenceAllTranslateText } from "@/hooks/translate.ts"
+import { getDefaultArticleWord } from "@/types/func.ts"
+import { useSettingStore } from "@/stores/setting.ts"
+import { useBaseStore } from "@/stores/base.ts"
+import { useRuntimeStore } from "@/stores/runtime.ts"
function parseSentence(sentence: string) {
// 先统一一些常见的“智能引号” -> 直引号,避免匹配问题
sentence = sentence
.replace(/[\u2018\u2019\u201A\u201B]/g, "'") // 各种单引号 → '
- .replace(/[\u201C\u201D\u201E\u201F]/g, '"'); // 各种双引号 → "
+ .replace(/[\u201C\u201D\u201E\u201F]/g, '"') // 各种双引号 → "
- const len = sentence.length;
- const tokens = [];
- let i = 0;
+ const len = sentence.length
+ const tokens = []
+ let i = 0
while (i < len) {
- const ch = sentence[i];
+ const ch = sentence[i]
// 跳过空白(但不把空白作为 token)
if (/\s/.test(ch)) {
- i++;
- continue;
+ i++
+ continue
}
- const rest = sentence.slice(i);
+ const rest = sentence.slice(i)
// 1) 货币 + 数字($1,000.50 或 ¥200 或 €100.5)
- let m = rest.match(/^[\$¥€£]\d{1,3}(?:,\d{3})*(?:\.\d+)?%?/);
+ let m = rest.match(/^[\$¥€£]\d{1,3}(?:,\d{3})*(?:\.\d+)?%?/)
if (m) {
- tokens.push({word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Number});
- i += m[0].length;
- continue;
+ tokens.push({ word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Number })
+ i += m[0].length
+ continue
}
// 2) 数字/小数/百分比(100% 3.14 1,000.00)
- m = rest.match(/^\d{1,3}(?:,\d{3})*(?:\.\d+)?%?/);
+ m = rest.match(/^\d{1,3}(?:,\d{3})*(?:\.\d+)?%?/)
if (m) {
- tokens.push({word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Number});
- i += m[0].length;
- continue;
+ tokens.push({ word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Number })
+ i += m[0].length
+ continue
}
// 3) 带点缩写或多段缩写(U.S. U.S.A. e.g. i.e. Ph.D.)
- m = rest.match(/^[A-Za-z]+(?:\.[A-Za-z]+)+\.?/);
+ m = rest.match(/^[A-Za-z]+(?:\.[A-Za-z]+)+\.?/)
if (m) {
- tokens.push({word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Word});
- i += m[0].length;
- continue;
+ tokens.push({ word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Word })
+ i += m[0].length
+ continue
}
// 4) 单词(包含撇号/连字符,如 it's, o'clock, we'll, mother-in-law)
- m = rest.match(/^[A-Za-z0-9]+(?:[\'\-][A-Za-z0-9]+)*/);
+ m = rest.match(/^[A-Za-z0-9]+(?:[\'\-][A-Za-z0-9]+)*/)
if (m) {
- tokens.push({word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Word});
- i += m[0].length;
- continue;
+ tokens.push({ word: m[0], start: i, end: i + m[0].length, type: PracticeArticleWordType.Word })
+ i += m[0].length
+ continue
}
// 5) 其它可视符号(标点)——单字符处理(连续标点会被循环拆为单字符)
// 包括:.,!?;:"'()-[]{}<>/\\@#%^&*~`等非单词非空白字符
if (/[^\w\s]/.test(ch)) {
- tokens.push({word: ch, start: i, end: i + 1, type: PracticeArticleWordType.Symbol});
- i += 1;
- continue;
+ tokens.push({ word: ch, start: i, end: i + 1, type: PracticeArticleWordType.Symbol })
+ i += 1
+ continue
}
// 6) 回退方案:把当前字符当作一个 token(防止意外丢失)
- tokens.push({word: ch, start: i, end: i + 1, type: PracticeArticleWordType.Symbol});
- i += 1;
+ tokens.push({ word: ch, start: i, end: i + 1, type: PracticeArticleWordType.Symbol })
+ i += 1
}
// 计算 nextSpace:查看当前 token 的 end 到下一个 token 的 start 之间是否含空白
const result = tokens.map((t, idx) => {
- const next = tokens[idx + 1];
- const between = next ? sentence.slice(t.end, next.start) : sentence.slice(t.end);
- const nextSpace = /\s/.test(between);
- return getDefaultArticleWord({word: t.word, nextSpace, type: t.type});
- });
+ const next = tokens[idx + 1]
+ const between = next ? sentence.slice(t.end, next.start) : sentence.slice(t.end)
+ const nextSpace = /\s/.test(between)
+ return getDefaultArticleWord({ word: t.word, nextSpace, type: t.type })
+ })
- return result;
+ return result
}
//生成文章段落数据
export function genArticleSectionData(article: Article): number {
let text = article.text.trim()
let sections: Sentence[][] = []
- text.split('\n\n').filter(Boolean).map((sectionText, i) => {
- let section: Sentence[] = []
- sections.push(section)
- sectionText.trim().split('\n').filter(Boolean).map((item, i, arr) => {
- item = item.trim()
- //如果没有空格,导致修改一行一行的数据时,汇总时全没有空格了,库无法正常断句
- //所以要保证最后一个是空格,但防止用户打N个空格,就去掉再加上一个空格,只需要一个即可
- //2025/10/1:最后一句不需要空格
- if (i < arr.length - 1) item += ' '
- let sentence: Sentence = cloneDeep({
- text: item,
- translate: '',
- words: parseSentence(item),
- audioPosition: [0, 0],
- })
- section.push(sentence)
+ text
+ .split("\n\n")
+ .filter(Boolean)
+ .map((sectionText, i) => {
+ let section: Sentence[] = []
+ sections.push(section)
+ sectionText
+ .trim()
+ .split("\n")
+ .filter(Boolean)
+ .map((item, i, arr) => {
+ item = item.trim()
+ //如果没有空格,导致修改一行一行的数据时,汇总时全没有空格了,库无法正常断句
+ //所以要保证最后一个是空格,但防止用户打N个空格,就去掉再加上一个空格,只需要一个即可
+ //2025/10/1:最后一句不需要空格
+ if (i < arr.length - 1) item += " "
+ let sentence: Sentence = cloneDeep({
+ text: item,
+ translate: "",
+ words: parseSentence(item),
+ audioPosition: [0, 0]
+ })
+ section.push(sentence)
+ })
})
- })
- sections = sections.filter(v => v.length)
+ sections = sections.filter((v) => v.length)
article.sections = sections
let failCount = 0
- let translateList = article.textTranslate?.split('\n\n') || []
+ let translateList = article.textTranslate?.split("\n\n") || []
for (let i = 0; i < article.sections.length; i++) {
let v = article.sections[i]
let sList = []
try {
let s = translateList[i]
- sList = s.split('\n')
- } catch (e) {
- }
+ sList = s.split("\n")
+ } catch (e) {}
for (let j = 0; j < v.length; j++) {
let sentence = v[j]
@@ -159,167 +164,167 @@ export function genArticleSectionData(article: Article): number {
export function splitEnArticle2(text: string): string {
text = text.trim()
if (!text && false) {
-// text = `It was Sunday. I never get up early on Sundays. I sometimes stay in bed until lunchtime. Last Sunday I got up very late. I looked out of the window. It was dark outside. 'What a day!' I thought. 'It's raining again. ' Just then, the telephone rang. It was my aunt Lucy. 'I've just arrived by train, ' she said. 'I'm coming to see you. '
-//
-// 'But I'm still having breakfast, ' I said.
-// 'What are you doing?' she asked.
-// 'I'm having breakfast, ' I repeated.
-// 'Dear me,$3.000' she said. 'Do you always get up so late? It's one o'clock!'`
-// text = `While it is yet to be seen what direction the second Trump administration will take globally in its China policy, VOA traveled to the main island of Mahe in Seychelles to look at how China and the U.S. have impacted the country, and how each is fairing in that competition for influence there.`
+ // text = `It was Sunday. I never get up early on Sundays. I sometimes stay in bed until lunchtime. Last Sunday I got up very late. I looked out of the window. It was dark outside. 'What a day!' I thought. 'It's raining again. ' Just then, the telephone rang. It was my aunt Lucy. 'I've just arrived by train, ' she said. 'I'm coming to see you. '
+ //
+ // 'But I'm still having breakfast, ' I said.
+ // 'What are you doing?' she asked.
+ // 'I'm having breakfast, ' I repeated.
+ // 'Dear me,$3.000' she said. 'Do you always get up so late? It's one o'clock!'`
+ // text = `While it is yet to be seen what direction the second Trump administration will take globally in its China policy, VOA traveled to the main island of Mahe in Seychelles to look at how China and the U.S. have impacted the country, and how each is fairing in that competition for influence there.`
// text = "It was Sunday. I never get up early on Sundays. I sometimes stay in bed until lunchtime. Last Sunday I got up very late. I looked out of the window. It was dark outside. 'What a day!' I thought. 'It's raining again.' Just then, the telephone rang. It was my aunt Lucy. 'I've just arrived by train,' she said. 'I'm coming to see you.'\n\n 'But I'm still having breakfast,' I said.\n\n 'What are you doing?' she asked.\n\n 'I'm having breakfast,' I repeated.\n\n 'Dear me,' she said. 'Do you always get up so late? It's one o'clock!'"
}
- if (!text) return '';
+ if (!text) return ""
- const abbreviations = [
- 'Mr', 'Mrs', 'Ms', 'Dr', 'Prof', 'Sr', 'Jr',
- 'St', 'Co', 'Ltd', 'Inc', 'e.g', 'i.e', 'U.S.A', 'U.S', 'U.K', 'etc'
- ];
+ const abbreviations = ["Mr", "Mrs", "Ms", "Dr", "Prof", "Sr", "Jr", "St", "Co", "Ltd", "Inc", "e.g", "i.e", "U.S.A", "U.S", "U.K", "etc"]
function isSentenceEnd(text, idx) {
- const before = text.slice(0, idx + 1);
- const after = text.slice(idx + 1);
+ const before = text.slice(0, idx + 1)
+ const after = text.slice(idx + 1)
- const abbrevPattern = new RegExp('\\b(' + abbreviations.join('|') + ')\\.$', 'i');
- if (abbrevPattern.test(before)) return false;
- if (/\d+\.$/.test(before)) return false;
- if (/\d+\.\d/.test(text.slice(idx - 1, idx + 2))) return false;
- if (/%/.test(after)) return false;
- if (/[\$¥€]\d/.test(before + after)) return false;
+ const abbrevPattern = new RegExp("\\b(" + abbreviations.join("|") + ")\\.$", "i")
+ if (abbrevPattern.test(before)) return false
+ if (/\d+\.$/.test(before)) return false
+ if (/\d+\.\d/.test(text.slice(idx - 1, idx + 2))) return false
+ if (/%/.test(after)) return false
+ if (/[\$¥€]\d/.test(before + after)) return false
- return true;
+ return true
}
function normalizeQuotes(text) {
- const isWord = ch => /\w/.test(ch);
- let res = [];
- let singleOpen = false;
- let doubleOpen = false;
+ const isWord = (ch) => /\w/.test(ch)
+ let res = []
+ let singleOpen = false
+ let doubleOpen = false
for (let i = 0; i < text.length; i++) {
- const ch = text[i];
+ const ch = text[i]
if (ch === "'") {
- const prev = i > 0 ? text[i - 1] : '';
- const nxt = i + 1 < text.length ? text[i + 1] : '';
+ const prev = i > 0 ? text[i - 1] : ""
+ const nxt = i + 1 < text.length ? text[i + 1] : ""
if (isWord(prev) && isWord(nxt)) {
- res.push("'");
- continue;
+ res.push("'")
+ continue
}
if (singleOpen) {
- if (res.length && res[res.length - 1] === ' ') res.pop();
- res.push("'");
- singleOpen = false;
+ if (res.length && res[res.length - 1] === " ") res.pop()
+ res.push("'")
+ singleOpen = false
} else {
- res.push("'");
- singleOpen = true;
+ res.push("'")
+ singleOpen = true
}
} else if (ch === '"') {
if (doubleOpen) {
- if (res.length && res[res.length - 1] === ' ') res.pop();
- res.push('"');
- doubleOpen = false;
+ if (res.length && res[res.length - 1] === " ") res.pop()
+ res.push('"')
+ doubleOpen = false
} else {
- res.push('"');
- doubleOpen = true;
+ res.push('"')
+ doubleOpen = true
}
} else {
- res.push(ch);
+ res.push(ch)
}
}
- return res.join('');
+ return res.join("")
}
- let rawParagraphs = text.replaceAll('\n\n', '`^`').replaceAll('\n', '').split('`^`')
+ let rawParagraphs = text.replaceAll("\n\n", "`^`").replaceAll("\n", "").split("`^`")
- const formattedParagraphs = rawParagraphs.map(p => {
- p = p.trim();
- if (!p) return '';
+ const formattedParagraphs = rawParagraphs.map((p) => {
+ p = p.trim()
+ if (!p) return ""
- p = p.replace(/\n/g, ' ');
- p = normalizeQuotes(p);
+ p = p.replace(/\n/g, " ")
+ p = normalizeQuotes(p)
- const tentative: string[] = p.match(/[^.!?。!?]+[.!?。!?'"”’)]*/g) || [];
+ const tentative: string[] = p.match(/[^.!?。!?]+[.!?。!?'"”’)]*/g) || []
- const sentences = [];
- tentative.forEach(segment => {
- segment = segment.trim();
- if (!segment) return;
+ const sentences = []
+ tentative.forEach((segment) => {
+ segment = segment.trim()
+ if (!segment) return
- const lastCharIdx = segment.length - 1;
+ const lastCharIdx = segment.length - 1
if (/[.!?。!?]/.test(segment[lastCharIdx])) {
- const globalIdx = p.indexOf(segment);
+ const globalIdx = p.indexOf(segment)
if (!isSentenceEnd(p, globalIdx + segment.length - 1)) {
if (sentences.length > 0) {
- sentences[sentences.length - 1] += ' ' + segment;
+ sentences[sentences.length - 1] += " " + segment
} else {
- sentences.push(segment);
+ sentences.push(segment)
}
- return;
+ return
}
}
- sentences.push(segment);
- });
+ sentences.push(segment)
+ })
- const finalSentences = [];
- let i = 0;
+ const finalSentences = []
+ let i = 0
while (i < sentences.length) {
- let cur = sentences[i];
+ let cur = sentences[i]
if (i + 1 < sentences.length) {
- const nxt = sentences[i + 1];
+ const nxt = sentences[i + 1]
if (/['"”’)\]]$/.test(cur) && /^[a-z]|^(I|You|She|He|They|We)\b/i.test(nxt)) {
- finalSentences.push(cur + ' ' + nxt);
- i += 2;
- continue;
+ finalSentences.push(cur + " " + nxt)
+ i += 2
+ continue
}
}
- finalSentences.push(cur);
- i += 1;
+ finalSentences.push(cur)
+ i += 1
}
- return finalSentences.join('\n');
- });
+ return finalSentences.join("\n")
+ })
- return formattedParagraphs.filter(p => p).join('\n\n');
+ return formattedParagraphs.filter((p) => p).join("\n\n")
}
export function splitCNArticle2(text: string): string {
if (!text && false) {
// text = "飞机误点了,侦探们在机场等了整整一上午。他们正期待从南非来的一个装着钻石的贵重包裹。数小时以前,有人向警方报告,说有人企图偷走这些钻石。当飞机到达时,一些侦探等候在主楼内,另一些侦探则守候在停机坪上。有两个人把包裹拿下飞机,进了海关。这时两个侦探把住门口,另外两个侦探打开了包裹。令他们吃惊的是,那珍贵的包裹里面装的全是石头和沙子!"
-// text = `那是个星期天,而在星期天我是从来不早起的,有时我要一直躺到吃午饭的时候。上个星期天,我起得很晚。我望望窗外,外面一片昏暗。“鬼天气!”我想,“又下雨了。”正在这时,电话铃响了。是我姑母露西打来的。“我刚下火车,”她说,“我这就来看你。”
-// “但我还在吃早饭,”我说。
-// “你在干什么?”她问道。
-// “我正在吃早饭,”我又说了一遍。
-// “天啊,”她说,“你总是起得这么晚吗?现在已经1点钟了!”`
-// text = `上星期我去看戏。我的座位很好,戏很有意思,但我却无法欣赏。一青年男子与一青年女子坐在我的身后,大声地说着话。我非常生气,因为我听不见演员在说什么。我回过头去怒视着那一男一女,他们却毫不理会。最后,我忍不住了,又一次回过头去,生气地说:“我一个字也听不见了!”
-// “不关你的事,”那男的毫不客气地说,“这是私人间的谈话!”`
+ // text = `那是个星期天,而在星期天我是从来不早起的,有时我要一直躺到吃午饭的时候。上个星期天,我起得很晚。我望望窗外,外面一片昏暗。“鬼天气!”我想,“又下雨了。”正在这时,电话铃响了。是我姑母露西打来的。“我刚下火车,”她说,“我这就来看你。”
+ // “但我还在吃早饭,”我说。
+ // “你在干什么?”她问道。
+ // “我正在吃早饭,”我又说了一遍。
+ // “天啊,”她说,“你总是起得这么晚吗?现在已经1点钟了!”`
+ // text = `上星期我去看戏。我的座位很好,戏很有意思,但我却无法欣赏。一青年男子与一青年女子坐在我的身后,大声地说着话。我非常生气,因为我听不见演员在说什么。我回过头去怒视着那一男一女,他们却毫不理会。最后,我忍不住了,又一次回过头去,生气地说:“我一个字也听不见了!”
+ // “不关你的事,”那男的毫不客气地说,“这是私人间的谈话!”`
}
- const segmenterJa = new Intl.Segmenter("zh-CN", {granularity: "sentence"});
+ const segmenterJa = new Intl.Segmenter("zh-CN", { granularity: "sentence" })
- let sectionTextList = text.replaceAll('\n\n', '`^`').replaceAll('\n', '').split('`^`')
+ let sectionTextList = text.replaceAll("\n\n", "`^`").replaceAll("\n", "").split("`^`")
- let s = sectionTextList.filter(v => v).map((rowSection, i) => {
- const segments = segmenterJa.segment(rowSection);
- let ss = ''
- Array.from(segments).map(sentenceRow => {
- let row = sentenceRow.segment
- if (row) {
- //这个库总是会把反引号给断句到上一行末尾
- //而 sentence-splitter 这个库总是会把反引号给断句到下一行开头
- if (row[row.length - 1] === "“") {
- row = row.substring(0, row.length - 1)
- ss += (row + '\n') + '“'
- } else {
- ss += (row + '\n')
+ let s = sectionTextList
+ .filter((v) => v)
+ .map((rowSection, i) => {
+ const segments = segmenterJa.segment(rowSection)
+ let ss = ""
+ Array.from(segments).map((sentenceRow) => {
+ let row = sentenceRow.segment
+ if (row) {
+ //这个库总是会把反引号给断句到上一行末尾
+ //而 sentence-splitter 这个库总是会把反引号给断句到下一行开头
+ if (row[row.length - 1] === "“") {
+ row = row.substring(0, row.length - 1)
+ ss += row + "\n" + "“"
+ } else {
+ ss += row + "\n"
+ }
}
- }
+ })
+ return ss
})
- return ss
- }).join('\n').trim()
+ .join("\n")
+ .trim()
return s
}
export function getTranslateText(article: Article) {
- return article.textTranslate
- .split('\n\n').filter(v => v)
+ return article.textTranslate.split("\n\n").filter((v) => v)
}
export function usePlaySentenceAudio() {
@@ -327,14 +332,14 @@ export function usePlaySentenceAudio() {
const settingStore = useSettingStore()
let timer = $ref(0)
- function playSentenceAudio(sentence: Sentence, ref?: HTMLAudioElement,) {
+ function playSentenceAudio(sentence: Sentence, ref?: HTMLAudioElement) {
if (sentence.audioPosition?.length && ref && ref.src) {
clearTimeout(timer)
if (ref.played) {
ref.pause()
}
- let start = sentence.audioPosition[0];
- ref.volume = settingStore.wordSoundVolume / 100
+ let start = sentence.audioPosition[0]
+ // ref.volume = settingStore.wordSoundVolume / 100
ref.currentTime = start
ref.play()
let end = sentence.audioPosition?.[1]
@@ -342,9 +347,9 @@ export function usePlaySentenceAudio() {
if (end && end !== -1) {
timer = setTimeout(() => {
- console.log('停')
+ console.log("停")
ref.pause()
- }, (end - start) / ref.playbackRate * 1000)
+ }, ((end - start) / ref.playbackRate) * 1000)
}
} else {
playWordAudio(sentence.text)
@@ -361,8 +366,8 @@ export function syncBookInMyStudyList(study = false) {
_nextTick(() => {
const base = useBaseStore()
const runtimeStore = useRuntimeStore()
- let rIndex = base.article.bookList.findIndex(v => v.id === runtimeStore.editDict.id)
- let temp = cloneDeep(runtimeStore.editDict);
+ let rIndex = base.article.bookList.findIndex((v) => v.id === runtimeStore.editDict.id)
+ let temp = cloneDeep(runtimeStore.editDict)
if (!temp.custom && temp.id !== DictId.articleCollect) {
temp.custom = true
}
@@ -375,4 +380,4 @@ export function syncBookInMyStudyList(study = false) {
if (study) base.article.studyIndex = base.article.bookList.length - 1
}
}, 100)
-}
\ No newline at end of file
+}
diff --git a/src/main.ts b/src/main.ts
index 98d6d23b..a780fd19 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -13,6 +13,7 @@ import loadingDirective from './directives/loading.tsx'
const pinia = createPinia()
const app = createApp(App)
+
app.use(VueVirtualScroller)
app.use(pinia)
app.use(router)
diff --git a/src/pages/article/PracticeArticles.vue b/src/pages/article/PracticeArticles.vue
index e7de59ff..b857457f 100644
--- a/src/pages/article/PracticeArticles.vue
+++ b/src/pages/article/PracticeArticles.vue
@@ -1,6 +1,6 @@
-
+
-
+
-
+
- {{
- store.sbook.name
- }} ({{ store.sbook.lastLearnIndex + 1 }} / {{ articleData.list.length }})
+ {{
+ store.sbook.name
+ }} ({{ store.sbook.lastLearnIndex + 1 }} / {{ articleData.list.length }})
-
-
-
-
-
+
+
+
+
+
@@ -478,12 +485,9 @@ provide('currentPractice', currentPractice)