This commit is contained in:
Zyronon
2026-01-01 01:35:06 +08:00
parent a009a291bc
commit 4d1f5a220f
5 changed files with 103 additions and 103 deletions

View File

@@ -3,14 +3,19 @@
"id": "fPlqrZ",
"title": "Finding fossil man",
"titleTranslate": "发现化石人",
"text": "We can read of things that happened 5,000 years ago in the Near East, where people first learned to write. \nBut there are some parts of the word where even now people cannot write. \nThe only way that they can preserve their history is to recount it as sagas--legends handed down from one generation of another. \nThese legends are useful because they can tell us something about migrations of people who lived long ago, \nbut none could write down what they did. \nAnthropologists wondered where the remote ancestors of the Polynesian peoples now living in the Pacific Islands came from. \nThe sagas of these people explain that some of them came from Indonesia about 2,000 years ago. \nBut the first people who were like ourselves lived so long ago that even their sagas, if they had any, are forgotten. \nSo archaeologists have neither history nor legends to help them to find out where the first 'modern men' came from. \nFortunately, however, ancient men made tools of stone, especially flint, \nbecause this is easier to shape than other kinds. \nThey may also have used wood and skins, but these have rotted away. \nStone does not decay, \nand so the tools of long ago have remained when even the bones of the men who made them have disappeared without trace.\n\nROBIN PLACE Finding fossil man",
"textTranslate": "我们从书籍中可读到5,000 年前近东发生的事情,那里的人最早学会了写字。 \n但直到现在,世界上有些地方,人们还不会书写。 \n他们保存历史的唯一办法是将历史当作传说讲述由讲述人一代接一代地将史实描述为传奇故事口传下来。 \n这些传说很有用因为它们可以告诉我们很久以前人们的迁徙 \n但没有人能写下他们做了什么。 \n人类学家想知道现在生活在太平洋岛屿上的波利尼西亚人的祖先来自哪里。 \n这些人的传说解释说他们中的一些人大约在2000年前来自印度尼西亚。 \n但是与我们相似的第一批人生活在很久以前即使他们有传奇故事也被遗忘了。 \n因此考古学家既没有历史也没有传说来帮助他们找出第一批“现代人”来自哪里。 \n然而幸运的是古人用石头尤其是燧石制作了工具 \n因为这比其他种类更容易成形。 \n他们也可能使用木头和兽皮但这些都腐烂了。 \n石头不会腐烂 \n所以很久以前的工具保存了下来而制造这些工具的人的骨头却消失得无影无踪。 \n\n寻找化石人",
"text": "We can read of things that happened 5,000 years ago in the Near East, where people first learned to write. \nBut there are some parts of the word where even now people cannot write. \nThe only way that they can preserve their history is to recount it as sagas--legends handed down from one generation of another. \nThese legends are useful because they can tell us something about migrations of people who lived long ago, \nbut none could write down what they did. \nAnthropologists wondered where the remote ancestors of the Polynesian peoples now living in the Pacific Islands came from. \nThe sagas of these people explain that some of them came from Indonesia about 2,000 years ago. \nBut the first people who were like ourselves lived so long ago that even their sagas, if they had any, are forgotten. \nSo archaeologists have neither history nor legends to help them to find out where the first 'modern men' came from. \nFortunately, however, ancient men made tools of stone, especially flint, \nbecause this is easier to shape than other kinds. \nThey may also have used wood and skins, but these have rotted away. \nStone does not decay, \nand so the tools of long ago have remained when even the bones of the men who made them have disappeared without trace.",
"textTranslate": "我们从书籍中可读到5,000 年前近东发生的事情,那里的人最早学会了写字。 \n但直到现在,世界上有些地方,人们还不会书写。 \n他们保存历史的唯一办法是将历史当作传说讲述由讲述人一代接一代地将史实描述为传奇故事口传下来。 \n这些传说很有用因为它们可以告诉我们很久以前人们的迁徙 \n但没有人能写下他们做了什么。 \n人类学家想知道现在生活在太平洋岛屿上的波利尼西亚人的祖先来自哪里。 \n这些人的传说解释说他们中的一些人大约在2000年前来自印度尼西亚。 \n但是与我们相似的第一批人生活在很久以前即使他们有传奇故事也被遗忘了。 \n因此考古学家既没有历史也没有传说来帮助他们找出第一批“现代人”来自哪里。 \n然而幸运的是古人用石头尤其是燧石制作了工具 \n因为这比其他种类更容易成形。 \n他们也可能使用木头和兽皮但这些都腐烂了。 \n石头不会腐烂 \n所以很久以前的工具保存了下来而制造这些工具的人的骨头却消失得无影无踪。",
"newWords": [],
"audioSrc": "/sound/article/nce4/01Finding Fossil Man.mp3",
"audioFileId": "",
"lrcPosition": [[16.65,26.3],[26.3,33.25],[33.25,46.11],[46.11,53.82],[53.82,57.22],[57.22,66.79],[66.79,74.55],[74.55,85.77],[85.77,94.92],[94.92,101.6],[101.6,105.92],[105.92,111.94],[111.94,114.51],[114.51,124.55],[124.55,129.13]],
"lrcPosition": [[16.65,26.3],[26.3,33.25],[33.25,46.11],[46.11,53.82],[53.82,57.22],[57.22,66.79],[66.79,74.55],[74.55,85.77],[85.77,94.92],[94.92,101.6],[101.6,105.92],[105.92,111.94],[111.94,114.51],[114.51,124.55]],
"questions": [],
"nameList": [],
"quote": {
"start": 124.55,
"text": "ROBIN PLACE Finding fossil man",
"translate": "寻找化石人",
"end": 129.13
},
"textAllWords": [],
"question": {
"start": 10.9,

View File

@@ -50,7 +50,7 @@ const studyProgress = $computed(() => {
@change="$emit('check')"
class="absolute left-3 bottom-3 z-2"/>
<div class="custom z-1" v-if="item.custom">自定义</div>
<div class="custom bg-red! color-white z-1" v-else-if="item.update">更新中</div>
<!-- <div class="custom bg-red! color-white z-1" v-else-if="item.update">更新中</div>-->
<!-- <div class="sync bg-red! color-white z-1" v-if="!item.sync && isUser && !showCheckbox">未同步</div>-->
</div>
<div class="text-base mt-1" v-if="item?.cover">{{ item?.name }}</div>

View File

@@ -107,9 +107,9 @@ defineExpose({ scrollToBottom, scrollToItem })
<IconFluentStar16Regular v-if="!isArticleCollect(item)" />
<IconFluentStar16Filled v-else />
</BaseIcon>
<BaseIcon title="可播放音频" v-if="item.audioSrc || item.audioFileId" noBg>
<IconBxVolumeFull class="opacity-100! color-gray" />
</BaseIcon>
<!-- <BaseIcon title="可播放音频" v-if="item.audioSrc || item.audioFileId" noBg>-->
<!-- <IconBxVolumeFull class="opacity-100! color-gray" />-->
<!-- </BaseIcon>-->
</div>
</div>
</template>

View File

@@ -1,34 +1,31 @@
<script setup lang="ts">
import BasePage from "@/components/BasePage.vue";
import BackIcon from "@/components/BackIcon.vue";
import Empty from "@/components/Empty.vue";
import ArticleList from "@/components/list/ArticleList.vue";
import { useBaseStore } from "@/stores/base.ts";
import { Article, Dict, DictId, DictType } from "@/types/types.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import BaseButton from "@/components/BaseButton.vue";
import { useRoute, useRouter } from "vue-router";
import EditBook from "@/pages/article/components/EditBook.vue";
import { computed, onMounted } from "vue";
import { _dateFormat, _getDictDataByUrl, msToHourMinute, resourceWrap, total, useNav } from "@/utils";
import BaseIcon from "@/components/BaseIcon.vue";
import { useArticleOptions } from "@/hooks/dict.ts";
import { getDefaultArticle, getDefaultDict } from "@/types/func.ts";
import Toast from "@/components/base/toast/Toast.ts";
import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
import { MessageBox } from "@/utils/MessageBox.tsx";
import { useSettingStore } from "@/stores/setting.ts";
import { useFetch } from "@vueuse/core";
import { AppEnv, DICT_LIST } from "@/config/env.ts";
import { detail } from "@/apis";
import BasePage from '@/components/BasePage.vue'
import BackIcon from '@/components/BackIcon.vue'
import Empty from '@/components/Empty.vue'
import ArticleList from '@/components/list/ArticleList.vue'
import { useBaseStore } from '@/stores/base.ts'
import { Article, Dict, DictId, DictType } from '@/types/types.ts'
import { useRuntimeStore } from '@/stores/runtime.ts'
import BaseButton from '@/components/BaseButton.vue'
import { useRoute, useRouter } from 'vue-router'
import EditBook from '@/pages/article/components/EditBook.vue'
import { computed, onMounted } from 'vue'
import { _dateFormat, _getDictDataByUrl, msToHourMinute, resourceWrap, total, useNav } from '@/utils'
import { getDefaultArticle, getDefaultDict } from '@/types/func.ts'
import Toast from '@/components/base/toast/Toast.ts'
import ArticleAudio from '@/pages/article/components/ArticleAudio.vue'
import { MessageBox } from '@/utils/MessageBox.tsx'
import { useSettingStore } from '@/stores/setting.ts'
import { useFetch } from '@vueuse/core'
import { AppEnv, DICT_LIST } from '@/config/env.ts'
import { detail } from '@/apis'
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
const base = useBaseStore()
const router = useRouter()
const route = useRoute()
const {nav} = useNav()
const { nav } = useNav()
let isEdit = $ref(false)
let isAdd = $ref(false)
@@ -65,13 +62,13 @@ async function addMyStudyList() {
name: sbook.name,
custom: sbook.custom,
complete: sbook.complete,
s:`name:${sbook.name},index:${sbook.lastLearnIndex},title:${sbook.articles[sbook.lastLearnIndex].title}`,
s: `name:${sbook.name},index:${sbook.lastLearnIndex},title:${sbook.articles[sbook.lastLearnIndex].title}`,
})
nav('/practice-articles/' + sbook.id)
}
const showBookDetail = computed(() => {
return !(isAdd || isEdit);
return !(isAdd || isEdit)
})
async function init() {
@@ -80,12 +77,13 @@ async function init() {
runtimeStore.editDict = getDefaultDict()
} else {
if (!runtimeStore.editDict.id) {
await router.push("/articles")
await router.push('/articles')
} else {
if (!runtimeStore.editDict?.articles?.length
&& !runtimeStore.editDict?.custom
&& ![DictId.articleCollect].includes(runtimeStore.editDict.en_name || runtimeStore.editDict.id)
&& !runtimeStore.editDict?.is_default
if (
!runtimeStore.editDict?.articles?.length &&
!runtimeStore.editDict?.custom &&
![DictId.articleCollect].includes(runtimeStore.editDict.en_name || runtimeStore.editDict.id) &&
!runtimeStore.editDict?.is_default
) {
loading = true
let r = await _getDictDataByUrl(runtimeStore.editDict, DictType.article)
@@ -94,7 +92,7 @@ async function init() {
if (base.article.bookList.find(book => book.id === runtimeStore.editDict.id)) {
if (AppEnv.CAN_REQUEST) {
let res = await detail({id: runtimeStore.editDict.id})
let res = await detail({ id: runtimeStore.editDict.id })
if (res.success) {
runtimeStore.editDict.statistics = res.data.statistics
if (res.data.articles.length) {
@@ -118,37 +116,32 @@ function formClose() {
else router.back()
}
const {
isArticleCollect,
toggleArticleCollect
} = useArticleOptions()
const {data: book_list} = useFetch(resourceWrap(DICT_LIST.ARTICLE.ALL)).json()
const { data: book_list } = useFetch(resourceWrap(DICT_LIST.ARTICLE.ALL)).json()
function reset() {
MessageBox.confirm(
'继续此操作会重置所有文章,并从官方书籍获取最新文章列表,学习记录不会被重置。确认恢复默认吗?',
'恢复默认',
async () => {
let dict = book_list.value.find(v => v.url === runtimeStore.editDict.url) as Dict
if (dict && dict.id) {
dict = await _getDictDataByUrl(dict, DictType.article)
let rIndex = base.article.bookList.findIndex(v => v.id === runtimeStore.editDict.id)
if (rIndex > -1) {
let item = base.article.bookList[rIndex]
item.custom = false
item.id = dict.id
item.articles = dict.articles
if (item.lastLearnIndex >= item.articles.length) {
item.lastLearnIndex = item.articles.length - 1
}
runtimeStore.editDict = item
Toast.success('恢复成功')
return
'继续此操作会重置所有文章,并从官方书籍获取最新文章列表,学习记录不会被重置。确认恢复默认吗?',
'恢复默认',
async () => {
let dict = book_list.value.find(v => v.url === runtimeStore.editDict.url) as Dict
if (dict && dict.id) {
dict = await _getDictDataByUrl(dict, DictType.article)
let rIndex = base.article.bookList.findIndex(v => v.id === runtimeStore.editDict.id)
if (rIndex > -1) {
let item = base.article.bookList[rIndex]
item.custom = false
item.id = dict.id
item.articles = dict.articles
if (item.lastLearnIndex >= item.articles.length) {
item.lastLearnIndex = item.articles.length - 1
}
runtimeStore.editDict = item
Toast.success('恢复成功')
return
}
Toast.error('恢复失败')
}
Toast.error('恢复失败')
}
)
}
@@ -181,38 +174,36 @@ function next() {
<BasePage>
<div class="card mb-0 dict-detail-card flex flex-col" v-if="showBookDetail">
<div class="dict-header flex justify-between items-center relative">
<BackIcon class="dict-back z-2"/>
<BackIcon class="dict-back z-2" />
<div class="dict-title absolute text-2xl text-align-center w-full">{{ runtimeStore.editDict.name }}</div>
<div class="dict-actions flex">
<BaseButton v-if="runtimeStore.editDict.custom && runtimeStore.editDict.url" type="info" @click="reset">
恢复默认
</BaseButton>
<BaseButton :loading="studyLoading||loading" type="info" @click="isEdit = true">编辑</BaseButton>
<BaseButton :loading="studyLoading || loading" type="info" @click="isEdit = true">编辑</BaseButton>
<BaseButton type="info" @click="router.push('batch-edit-article')">文章管理</BaseButton>
<BaseButton :loading="studyLoading||loading" @click="addMyStudyList">学习</BaseButton>
<BaseButton :loading="studyLoading || loading" @click="addMyStudyList">学习</BaseButton>
</div>
</div>
<div class="flex gap-4 mt-2">
<img :src="runtimeStore.editDict?.cover"
class="w-30 rounded-md"
v-if="runtimeStore.editDict?.cover"
alt="">
<img :src="runtimeStore.editDict?.cover" class="w-30 rounded-md" v-if="runtimeStore.editDict?.cover" alt="" />
<div class="text-lg">介绍{{ runtimeStore.editDict.description }}</div>
</div>
<div class="text-base " v-if="totalSpend">总学习时长{{ totalSpend }}</div>
<div class="text-base" v-if="totalSpend">总学习时长{{ totalSpend }}</div>
<div class="line my-3"></div>
<div class="flex flex-1 overflow-hidden">
<div class="left flex-[2] scroll p-0">
<ArticleList
v-if="runtimeStore.editDict.length"
@title="handleCheckedChange"
@click="handleCheckedChange"
:list="runtimeStore.editDict.articles"
:active-id="selectArticle.id">
v-if="runtimeStore.editDict.length"
@title="handleCheckedChange"
@click="handleCheckedChange"
:list="runtimeStore.editDict.articles"
:active-id="selectArticle.id"
>
</ArticleList>
<Empty v-else/>
<Empty v-else />
</div>
<div class="right flex-[4] shrink-0 pl-4 overflow-auto">
<div v-if="selectArticle.id">
@@ -220,50 +211,46 @@ function next() {
<div class="text-2xl font-bold">学习记录</div>
<div class="mt-1 mb-3">总学习时长{{ msToHourMinute(total(currentPractice, 'spend')) }}</div>
<div
class="item border border-item border-solid mt-2 p-2 bg-[var(--bg-history)] rounded-md flex justify-between"
v-for="i in currentPractice">
class="item border border-item border-solid mt-2 p-2 bg-[var(--bg-history)] rounded-md flex justify-between"
v-for="i in currentPractice"
>
<span class="color-gray">{{ _dateFormat(i.startDate) }}</span>
<span>{{ msToHourMinute(i.spend) }}</span>
</div>
</div>
<div class="en-article-family title text-xl">
<div class="text-center text-2xl my-2">
<ArticleAudio
:article="selectArticle"
:autoplay="settingStore.articleAutoPlayNext"
@ended="next"/>
<div class="text-center text-2xl mb-6">
<ArticleAudio :article="selectArticle" :autoplay="settingStore.articleAutoPlayNext" @ended="next" />
</div>
<div class="text-center text-2xl">{{ selectArticle.title }}</div>
<div class="text-2xl" v-if="selectArticle.text">
<div class="my-5" v-for="t in selectArticle.text.split('\n\n')">{{ t }}</div>
<div class="text-right italic mb-5">{{ selectArticle?.quote?.text }}</div>
</div>
</div>
<div class="mt-2">
<div class="text-center text-2xl">{{ selectArticle.titleTranslate }}</div>
<div class="text-xl" v-if="selectArticle.textTranslate">
<div class="my-5" v-for="t in selectArticle.textTranslate.split('\n\n')">{{ t }}</div>
<div class="text-right italic mb-5">{{ selectArticle?.quote?.translate }}</div>
</div>
<Empty v-else/>
<Empty v-else />
</div>
</div>
<Empty v-else/>
<Empty v-else />
</div>
</div>
</div>
<div class="card mb-0 dict-detail-card" v-else>
<div class="dict-header flex justify-between items-center relative">
<BackIcon class="dict-back z-2" @click="isAdd ? $router.back():(isEdit = false)"/>
<div class="dict-title absolute text-2xl text-align-center w-full">{{ runtimeStore.editDict.id ? '修改' : '创建' }}书籍
<BackIcon class="dict-back z-2" @click="isAdd ? $router.back() : (isEdit = false)" />
<div class="dict-title absolute text-2xl text-align-center w-full">
{{ runtimeStore.editDict.id ? '修改' : '创建' }}书籍
</div>
</div>
<div class="center">
<EditBook
:is-add="isAdd"
:is-book="true"
@close="formClose"
@submit="isEdit = isAdd = false"
/>
<EditBook :is-add="isAdd" :is-book="true" @close="formClose" @submit="isEdit = isAdd = false" />
</div>
</div>
</BasePage>
@@ -284,7 +271,7 @@ function next() {
@media (max-width: 768px) {
.dict-detail-card {
height: calc(100vh - 2rem);
height: calc(100vh - 2rem);
}
.dict-header {
@@ -327,5 +314,4 @@ function next() {
}
}
}
</style>

View File

@@ -66,7 +66,7 @@ export interface Sentence {
}
export interface Article {
id?: number|string
id?: number | string
title: string
titleTranslate: string
text: string
@@ -83,6 +83,18 @@ export interface Article {
correctAnswer: string[]
explanation: string
}[]
quote?: {
start: number
text: string
translate: string
end: number
}
question?: {
start: number
text: string
translate: string
end: number
}
}
export interface Statistics {
@@ -309,10 +321,7 @@ export const WordPracticeModeStageMap: Record<WordPracticeMode, WordPracticeStag
WordPracticeStage.DictationReviewAll,
WordPracticeStage.Complete,
],
[WordPracticeMode.Shuffle]: [
WordPracticeStage.Shuffle,
WordPracticeStage.Complete,
],
[WordPracticeMode.Shuffle]: [WordPracticeStage.Shuffle, WordPracticeStage.Complete],
[WordPracticeMode.Review]: [
WordPracticeStage.IdentifyReview,
WordPracticeStage.ListenReview,