wip
This commit is contained in:
@@ -541,6 +541,6 @@ a {
|
||||
}
|
||||
|
||||
.target-number {
|
||||
@apply text-3xl! mx-2;
|
||||
@apply text-3xl!;
|
||||
color: rgb(176, 116, 211)!important;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { IS_DEV } from '@/config/env'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const router = useRouter()
|
||||
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
if (IS_DEV) {
|
||||
router.push('/')
|
||||
} else {
|
||||
location.href = window.atob('aHR0cHM6Ly90eXBld29yZHMuY2M=')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="center mb-2" @click="goHome">
|
||||
<img v-show="settingStore.theme === 'dark'" src="/logo-text-white.png" alt="">
|
||||
<img v-show="settingStore.theme !== 'dark'" src="/logo-text-black.png" alt="">
|
||||
<img v-show="settingStore.theme === 'dark'" src="/logo-text-white.png" alt="" />
|
||||
<img v-show="settingStore.theme !== 'dark'" src="/logo-text-black.png" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Article, TaskWords, Word, WordPracticeMode } from "@/types/types.ts";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { getDefaultWord } from "@/types/func.ts";
|
||||
import { getRandomN, splitIntoN } from "@/utils";
|
||||
import { Article, TaskWords, Word, WordPracticeMode } from '@/types/types.ts'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { getDefaultWord } from '@/types/func.ts'
|
||||
import { cloneDeep, getRandomN, shuffle, splitIntoN } from '@/utils'
|
||||
|
||||
export function useWordOptions() {
|
||||
const store = useBaseStore()
|
||||
@@ -12,7 +12,9 @@ export function useWordOptions() {
|
||||
}
|
||||
|
||||
function toggleWordCollect(val: Word) {
|
||||
let rIndex = store.collectWord.words.findIndex(v => v.word.toLowerCase() === val.word.toLowerCase())
|
||||
let rIndex = store.collectWord.words.findIndex(
|
||||
v => v.word.toLowerCase() === val.word.toLowerCase()
|
||||
)
|
||||
if (rIndex > -1) {
|
||||
store.collectWord.words.splice(rIndex, 1)
|
||||
} else {
|
||||
@@ -57,7 +59,7 @@ export function useWordOptions() {
|
||||
isWordSimple,
|
||||
toggleWordSimple,
|
||||
delWrongWord,
|
||||
delSimpleWord
|
||||
delSimpleWord,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ export function useArticleOptions() {
|
||||
export function getCurrentStudyWord(): TaskWords {
|
||||
const store = useBaseStore()
|
||||
let data = { new: [], review: [], write: [], shuffle: [] }
|
||||
let dict = store.sdict;
|
||||
let dict = store.sdict
|
||||
let isTest = false
|
||||
let words = dict.words.slice()
|
||||
if (isTest) {
|
||||
@@ -100,47 +102,62 @@ export function getCurrentStudyWord(): TaskWords {
|
||||
const settingStore = useSettingStore()
|
||||
//忽略时是否加上自定义的简单词
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
|
||||
const perDay = dict.perDayStudyNumber;
|
||||
let start = dict.lastLearnIndex;
|
||||
let complete = dict.complete;
|
||||
const perDay = dict.perDayStudyNumber
|
||||
let start = dict.lastLearnIndex
|
||||
let complete = dict.complete
|
||||
let isEnd = start >= dict.length - 1
|
||||
if (isTest) {
|
||||
start = 1
|
||||
complete = true
|
||||
}
|
||||
//如果已完成,并且记录在最后,那么直接随机取复习词
|
||||
if (complete && isEnd) {
|
||||
//复习比最小是1
|
||||
let ratio = settingStore.wordReviewRatio || 1
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][
|
||||
settingStore.ignoreSimpleWord ? 0 : 1
|
||||
]
|
||||
// 先将可用词表全部随机,再按需过滤忽略列表,只取到目标数量为止
|
||||
let shuffled = shuffle(cloneDeep(dict.words))
|
||||
let count = 0
|
||||
data.write = []
|
||||
for (let item of shuffled) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
data.write.push(item)
|
||||
count++
|
||||
if (count >= perDay * ratio) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
let end = start
|
||||
let list = dict.words.slice(start)
|
||||
if (complete) {
|
||||
//如果是已完成,那么把应该学的新词放到复习词组里面
|
||||
//从start往后取perDay个单词,作为当前练习单词
|
||||
for (let item of list) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
if (data.new.length < perDay) {
|
||||
data.new.push(item)
|
||||
} else break
|
||||
}
|
||||
end++
|
||||
}
|
||||
|
||||
//如果复习比大于等于1,或者已完成,那么就取复习词
|
||||
if (settingStore.wordReviewRatio >= 1 || complete) {
|
||||
//从start往前取perDay个单词,作为当前复习单词,取到0为止
|
||||
list = dict.words.slice(0, start).reverse()
|
||||
//但如果已完成,则滚动取值
|
||||
if (complete) list = list.concat(dict.words.slice(end).reverse())
|
||||
for (let item of list) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
if (data.review.length < perDay) {
|
||||
data.review.push(item)
|
||||
} else break
|
||||
}
|
||||
end++
|
||||
}
|
||||
} else {
|
||||
//从start往后取perDay个单词,作为当前练习单词
|
||||
for (let item of list) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
if (data.new.length < perDay) {
|
||||
data.new.push(item)
|
||||
} else break
|
||||
}
|
||||
end++
|
||||
}
|
||||
|
||||
if (settingStore.wordReviewRatio >= 1) {
|
||||
//从start往前取perDay个单词,作为当前复习单词,取到0为止
|
||||
list = dict.words.slice(0, start).reverse()
|
||||
for (let item of list) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
if (data.review.length < perDay) {
|
||||
data.review.push(item)
|
||||
} else break
|
||||
}
|
||||
start--
|
||||
}
|
||||
start--
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +174,10 @@ export function getCurrentStudyWord(): TaskWords {
|
||||
let candidateWords = dict.words.slice(0, start).reverse()
|
||||
//但如果已完成,则滚动取值
|
||||
if (complete) candidateWords = candidateWords.concat(dict.words.slice(end).reverse())
|
||||
candidateWords = candidateWords.filter(w => !ignoreList.includes(w.word.toLowerCase()));
|
||||
candidateWords = candidateWords.filter(w => !ignoreList.includes(w.word.toLowerCase()))
|
||||
// console.log(candidateWords.map(v => v.word))
|
||||
//最终要获取的单词数量
|
||||
const totalNeed = perDay * (settingStore.wordReviewRatio - 1);
|
||||
const totalNeed = perDay * (settingStore.wordReviewRatio - 1)
|
||||
if (candidateWords.length <= totalNeed) {
|
||||
data.write = candidateWords
|
||||
} else {
|
||||
@@ -171,20 +188,27 @@ export function getCurrentStudyWord(): TaskWords {
|
||||
// console.log('groups', groups)
|
||||
|
||||
// 分配数量,靠前组多,靠后组少,例如分配比例 [6,5,4,3,2,1]
|
||||
const ratio = Array.from({ length: days }, (_, i) => i + 1).reverse();
|
||||
const ratioSum = ratio.reduce((a, b) => a + b, 0);
|
||||
const realRatio = ratio.map(r => Math.round(r / ratioSum * totalNeed));
|
||||
const ratio = Array.from({ length: days }, (_, i) => i + 1).reverse()
|
||||
const ratioSum = ratio.reduce((a, b) => a + b, 0)
|
||||
const realRatio = ratio.map(r => Math.round((r / ratioSum) * totalNeed))
|
||||
// console.log(ratio, ratioSum, realRatio, realRatio.reduce((a, b) => a + b, 0))
|
||||
|
||||
// 按比例从每组随机取单词
|
||||
let writeWords: Word[] = [];
|
||||
let writeWords: Word[] = []
|
||||
groups.map((v, i) => {
|
||||
writeWords = writeWords.concat(getRandomN(v, realRatio[i]))
|
||||
})
|
||||
// console.log('writeWords', writeWords)
|
||||
data.write = writeWords;
|
||||
data.write = writeWords
|
||||
}
|
||||
}
|
||||
|
||||
//如果已完成,那么合并写词和复习词
|
||||
if(complete){
|
||||
// data.new = []
|
||||
// data.review = data.review.concat(data.write)
|
||||
// data.write = []
|
||||
}
|
||||
}
|
||||
// console.log('data-new', data.new.map(v => v.word))
|
||||
// console.log('data-review', data.review.map(v => v.word))
|
||||
|
||||
@@ -138,7 +138,7 @@ watch(dict_list, (val) => {
|
||||
v-if="searchList.length "
|
||||
@selectDict="selectDict"
|
||||
:list="searchList"
|
||||
quantifier="个词"
|
||||
quantifier="词"
|
||||
:select-id="'-1'"/>
|
||||
<Empty v-else text="没有相关词典"/>
|
||||
</div>
|
||||
@@ -147,7 +147,7 @@ watch(dict_list, (val) => {
|
||||
v-for="item in groupedByCategoryAndTag"
|
||||
:select-id="store.sdict.id"
|
||||
@selectDict="selectDict"
|
||||
quantifier="个词"
|
||||
quantifier="词"
|
||||
:groupByTag="item[1]"
|
||||
:category="item[0]"
|
||||
/>
|
||||
|
||||
@@ -703,7 +703,7 @@ async function continueStudy() {
|
||||
}
|
||||
|
||||
async function jumpToGroup(group: number) {
|
||||
console.log('没学完,强行跳过',group)
|
||||
console.log('没学完,强行跳过', group)
|
||||
store.sdict.lastLearnIndex = (group - 1) * store.sdict.perDayStudyNumber
|
||||
emitter.emit(EventKey.resetWord)
|
||||
initData(getCurrentStudyWord())
|
||||
@@ -794,14 +794,11 @@ useEvents([
|
||||
<div class="center gap-1">
|
||||
<span>{{ store.sdict.name }}</span>
|
||||
|
||||
<GroupList
|
||||
@click="jumpToGroup"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.Shuffle"
|
||||
/>
|
||||
|
||||
<template v-if="taskWords.new.length">
|
||||
|
||||
|
||||
<GroupList
|
||||
@click="jumpToGroup"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.Shuffle"
|
||||
/>
|
||||
<BaseIcon
|
||||
@click="continueStudy"
|
||||
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
|
||||
|
||||
@@ -73,23 +73,12 @@ watch(model, async newVal => {
|
||||
complete: store.sdict.complete,
|
||||
str: `name:${store.sdict.name},per:${store.sdict.perDayStudyNumber},spend:${Number(statStore.spend / 1000 / 60).toFixed(1)},index:${store.sdict.lastLearnIndex}`,
|
||||
})
|
||||
debugger
|
||||
|
||||
//如果 shuffle 数组不为空,就说明是复习,不用修改 lastLearnIndex
|
||||
//todo
|
||||
if (settingStore.wordPracticeMode !== WordPracticeMode.Shuffle) {
|
||||
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex + statStore.newWordNumber
|
||||
//todo 这里计算不正确,因为有可能有单词被忽略,所以需要计算忽略的单词数
|
||||
// 检查已忽略的单词数量,是否全部完成
|
||||
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][
|
||||
settingStore.ignoreSimpleWord ? 0 : 1
|
||||
]
|
||||
// 忽略单词数
|
||||
const ignoreCount = ignoreList.filter(word =>
|
||||
store.sdict.words.some(w => w.word.toLowerCase() === word)
|
||||
).length
|
||||
// 如果lastLearnIndex已经超过可学单词数,则判定完成
|
||||
if (store.sdict.lastLearnIndex + ignoreCount >= store.sdict.length) {
|
||||
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex + store.sdict.perDayStudyNumber
|
||||
if (store.sdict.lastLearnIndex >= store.sdict.length - 1) {
|
||||
dictIsEnd = true
|
||||
store.sdict.complete = true
|
||||
store.sdict.lastLearnIndex = store.sdict.length
|
||||
@@ -178,30 +167,25 @@ calcWeekList() // 新增:计算本周学习记录
|
||||
<p class="font-medium text-lg">{{ encouragementText }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Main Stats Grid -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<!-- Study Time -->
|
||||
<div class="item">
|
||||
<IconFluentClock20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">学习时长</div>
|
||||
<div class="text-xl font-bold">{{ formattedStudyTime }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Accuracy Rate -->
|
||||
<div class="item">
|
||||
<IconFluentTarget20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">正确率</div>
|
||||
<div class="text-xl font-bold">{{ accuracyRate }}%</div>
|
||||
</div>
|
||||
|
||||
<!-- New Words -->
|
||||
<div class="item">
|
||||
<IconFluentSparkle20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">新词</div>
|
||||
<div class="text-xl font-bold">{{ statStore.newWordNumber }}</div>
|
||||
</div>
|
||||
|
||||
<!-- New Words -->
|
||||
<div class="item">
|
||||
<IconFluentBook20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">复习</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
useNav,
|
||||
} from '@/utils'
|
||||
import BasePage from '@/components/BasePage.vue'
|
||||
import { DictResource, WordPracticeMode } from '@/types/types.ts'
|
||||
import { DictResource, WordPracticeMode, WordPracticeModeNameMap } from '@/types/types.ts'
|
||||
import { watch } from 'vue'
|
||||
import { getCurrentStudyWord } from '@/hooks/dict.ts'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
@@ -204,12 +204,8 @@ function toggleSelect(item) {
|
||||
}
|
||||
|
||||
const progressTextLeft = $computed(() => {
|
||||
if (store.sdict.complete) return '已学完,进入复习阶段'
|
||||
return '已学习' + store.currentStudyProgress + '%'
|
||||
})
|
||||
const progressTextRight = $computed(() => {
|
||||
// if (store.sdict.complete) return store.sdict?.length
|
||||
return store.sdict?.lastLearnIndex
|
||||
if (store.sdict.complete) return '已学完,进入总复习阶段'
|
||||
return '当前进度:已学' + store.currentStudyProgress + '%'
|
||||
})
|
||||
|
||||
function check(cb: Function) {
|
||||
@@ -258,6 +254,7 @@ async function onShufflePracticeSettingOk(total) {
|
||||
async function saveLastPracticeIndex(e) {
|
||||
Toast.success('修改成功')
|
||||
runtimeStore.editDict.lastLearnIndex = e
|
||||
// runtimeStore.editDict.complete = e >= runtimeStore.editDict.length - 1
|
||||
showChangeLastPracticeIndexDialog = false
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
@@ -292,16 +289,9 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
</div>
|
||||
|
||||
<template v-if="store.sdict.id">
|
||||
<div class="mt-4 flex flex-col gap-2">
|
||||
<div class="">当前进度:{{ progressTextLeft }}</div>
|
||||
<Progress
|
||||
size="large"
|
||||
:percentage="store.currentStudyProgress"
|
||||
:show-text="false"
|
||||
></Progress>
|
||||
<div class="mt-4 space-y-2">
|
||||
<div class="text-sm flex justify-between">
|
||||
<span>已完成 {{ progressTextRight }} 词 / 共 {{ store.sdict.words.length }} 词</span>
|
||||
<span v-if="store.sdict.id">
|
||||
<span v-opacity="store.sdict.id && store.sdict.lastLearnIndex < store.sdict.length">
|
||||
预计完成日期:{{
|
||||
_getAccomplishDate(
|
||||
store.sdict.words.length - store.sdict.lastLearnIndex,
|
||||
@@ -310,6 +300,16 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
size="large"
|
||||
:percentage="store.currentStudyProgress"
|
||||
:show-text="false"
|
||||
></Progress>
|
||||
|
||||
<div class="text-sm flex justify-between">
|
||||
<span>{{ progressTextLeft }}</span>
|
||||
<span> {{ store.sdict?.lastLearnIndex }} / {{ store.sdict.words.length }} 词</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-4 gap-4">
|
||||
<BaseButton type="info" size="small" @click="router.push('/dict-list')">
|
||||
@@ -398,37 +398,54 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
size="large"
|
||||
:disabled="!store.sdict.id"
|
||||
:loading="loading"
|
||||
@click="startPractice(WordPracticeMode.System)"
|
||||
@click="startPractice(settingStore.wordPracticeMode)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<span class="line-height-[2]">{{
|
||||
isSaveData
|
||||
? `继续${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`
|
||||
: `开始${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`
|
||||
}}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl" />
|
||||
</div>
|
||||
</BaseButton>
|
||||
<template #options>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.System)">
|
||||
智能
|
||||
<BaseButton
|
||||
class="w-23"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.System"
|
||||
@click="startPractice(WordPracticeMode.System)"
|
||||
>
|
||||
{{ WordPracticeModeNameMap[WordPracticeMode.System] }}
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.FollowWriteOnly)">
|
||||
跟写
|
||||
<BaseButton
|
||||
class="w-23"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.IdentifyOnly"
|
||||
@click="startPractice(WordPracticeMode.IdentifyOnly)"
|
||||
>
|
||||
{{ WordPracticeModeNameMap[WordPracticeMode.IdentifyOnly] }}
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.IdentifyOnly)">
|
||||
自测
|
||||
<BaseButton
|
||||
class="w-23"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.ListenOnly"
|
||||
@click="startPractice(WordPracticeMode.ListenOnly)"
|
||||
>
|
||||
{{ WordPracticeModeNameMap[WordPracticeMode.ListenOnly] }}
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.ListenOnly)">
|
||||
听写
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.DictationOnly)">
|
||||
默写
|
||||
<BaseButton
|
||||
class="w-23"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.DictationOnly"
|
||||
@click="startPractice(WordPracticeMode.DictationOnly)"
|
||||
>
|
||||
{{ WordPracticeModeNameMap[WordPracticeMode.DictationOnly] }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</OptionButton>
|
||||
|
||||
<OptionButton class="flex-1">
|
||||
<OptionButton class="flex-1" v-if="currentStudy.new.length">
|
||||
<BaseButton
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="startPractice(WordPracticeMode.Review, true)"
|
||||
@click="startPractice(WordPracticeMode.Review)"
|
||||
>
|
||||
复习
|
||||
</BaseButton>
|
||||
@@ -438,6 +455,13 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
</BaseButton>
|
||||
</template>
|
||||
</OptionButton>
|
||||
<BaseButton
|
||||
v-else
|
||||
size="large"
|
||||
@click="check(() => (showShufflePracticeSettingDialog = true))"
|
||||
>
|
||||
随机复习
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton size="large" :loading="loading" @click="startPractice(WordPracticeMode.Free)">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -483,7 +507,7 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
<div class="flex gap-4 flex-wrap mt-4">
|
||||
<Book
|
||||
:is-add="false"
|
||||
quantifier="个词"
|
||||
quantifier="词"
|
||||
:item="item"
|
||||
:checked="selectIds.includes(item.id)"
|
||||
@check="() => toggleSelect(item)"
|
||||
@@ -506,7 +530,7 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
<div class="flex gap-4 flex-wrap mt-4 min-h-50">
|
||||
<Book
|
||||
:is-add="false"
|
||||
quantifier="个词"
|
||||
quantifier="词"
|
||||
:item="item as any"
|
||||
v-for="(item, j) in recommendDictList"
|
||||
@click="goDictDetail(item as any)"
|
||||
|
||||
@@ -63,11 +63,11 @@ watch(
|
||||
<div class="target-modal color-main" id="mode">
|
||||
<div class="text-center mt-4">
|
||||
<span
|
||||
>共<span class="target-number">{{ runtimeStore.editDict.length }}</span
|
||||
>共<span class="target-number mx-2">{{ runtimeStore.editDict.length }}</span
|
||||
>个单词,</span
|
||||
>
|
||||
<span
|
||||
>预计<span class="target-number">{{
|
||||
>预计<span class="target-number mx-2">{{
|
||||
_getAccomplishDays(
|
||||
runtimeStore.editDict.length - tempLastLearnIndex,
|
||||
tempPerDayStudyNumber
|
||||
@@ -87,23 +87,30 @@ watch(
|
||||
<BaseInput class="target-number" v-model="tempPerDayStudyNumber" />
|
||||
</div>
|
||||
<span>个新词</span>
|
||||
<template v-if="temPracticeMode === 0">
|
||||
<span>,复习</span>
|
||||
<div class="target-number">
|
||||
{{ tempPerDayStudyNumber * tempWordReviewRatio }}
|
||||
</div>
|
||||
<span>个</span>
|
||||
</template>
|
||||
<span>,复习</span>
|
||||
<div class="target-number mx-2">
|
||||
{{ tempPerDayStudyNumber * tempWordReviewRatio }}
|
||||
</div>
|
||||
<span>个</span>
|
||||
</div>
|
||||
|
||||
<div class="flex mb-4 gap-space" v-if="temPracticeMode === 0">
|
||||
<Tooltip title="复习词与新词的比例">
|
||||
<div class="flex items-center gap-1 w-20">
|
||||
<span>复习比</span>
|
||||
<IconFluentQuestionCircle20Regular />
|
||||
<div class="mb-4 space-y-2">
|
||||
<div class="flex items-center gap-space">
|
||||
<Tooltip title="复习词与新词的比例">
|
||||
<div class="flex items-center gap-1 w-20 break-keep">
|
||||
<span>复习比</span>
|
||||
<IconFluentQuestionCircle20Regular />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<InputNumber :min="0" :max="10" v-model="tempWordReviewRatio" />
|
||||
</div>
|
||||
<div class="flex" v-if="!tempWordReviewRatio">
|
||||
<div class="w-23 flex-shrink-0"></div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<div>未完成学习时,复习数量按照设置的复习比生成,为0则不复习</div>
|
||||
<div>完成学习后,新词数量固定为0,复习数量按照比例生成(若复习比小于1,以 1 计算)</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<InputNumber :min="0" :max="10" v-model="tempWordReviewRatio" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mb-4 gap-space">
|
||||
@@ -156,7 +163,6 @@ watch(
|
||||
.target-modal {
|
||||
width: 35rem;
|
||||
|
||||
|
||||
.mode-item {
|
||||
@apply w-50% border border-blue border-solid p-2 rounded-lg cursor-pointer;
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ export const useBaseStore = defineStore('base', {
|
||||
this.word.bookList[this.word.studyIndex].perDayStudyNumber = val.perDayStudyNumber
|
||||
this.word.bookList[this.word.studyIndex].lastLearnIndex = val.lastLearnIndex
|
||||
this.word.bookList[this.word.studyIndex].userDictId = val.userDictId
|
||||
this.word.bookList[this.word.studyIndex].complete = val.complete
|
||||
} else {
|
||||
this.word.bookList.push(getDefaultDict(val))
|
||||
this.word.studyIndex = this.word.bookList.length - 1
|
||||
|
||||
@@ -349,3 +349,14 @@ export const WordPracticeStageNameMap: Record<WordPracticeStage, string> = {
|
||||
[WordPracticeStage.Complete]: '完成学习',
|
||||
[WordPracticeStage.Shuffle]: '随机复习',
|
||||
}
|
||||
|
||||
export const WordPracticeModeNameMap: Record<WordPracticeMode, string> = {
|
||||
[WordPracticeMode.System]: '智能学习',
|
||||
[WordPracticeMode.Free]: '自由',
|
||||
[WordPracticeMode.IdentifyOnly]: '自测',
|
||||
[WordPracticeMode.DictationOnly]: '默写',
|
||||
[WordPracticeMode.ListenOnly]: '听写',
|
||||
[WordPracticeMode.FollowWriteOnly]: '跟写',
|
||||
[WordPracticeMode.Shuffle]: '随机复习',
|
||||
[WordPracticeMode.Review]: '复习',
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { BaseState, getDefaultBaseState, useBaseStore } from "@/stores/base.ts";
|
||||
import { getDefaultSettingState, SettingState } from "@/stores/setting.ts";
|
||||
import { Dict, DictId, DictResource, DictType } from "@/types/types.ts";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import { BaseState, getDefaultBaseState, useBaseStore } from '@/stores/base.ts'
|
||||
import { getDefaultSettingState, SettingState } from '@/stores/setting.ts'
|
||||
import { Dict, DictId, DictResource, DictType } from '@/types/types.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import dayjs from 'dayjs'
|
||||
import { AppEnv, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
|
||||
import { nextTick } from "vue";
|
||||
import { AppEnv, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from '@/config/env.ts'
|
||||
import { nextTick } from 'vue'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import { getDefaultDict, getDefaultWord } from '@/types/func.ts'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(duration)
|
||||
|
||||
export function no() {
|
||||
Toast.warning('未现实')
|
||||
@@ -60,7 +60,9 @@ export function checkAndUpgradeSaveDict(val: any) {
|
||||
return defaultState
|
||||
} else {
|
||||
// 版本不匹配时,尽量保留数据而不是直接返回默认状态
|
||||
console.warn(`数据版本不匹配: 当前版本 ${version}, 期望版本 ${SAVE_DICT_KEY.version},尝试保留数据`)
|
||||
console.warn(
|
||||
`数据版本不匹配: 当前版本 ${version}, 期望版本 ${SAVE_DICT_KEY.version},尝试保留数据`
|
||||
)
|
||||
try {
|
||||
checkRiskKey(defaultState, state)
|
||||
// 尝试保留 bookList 数据
|
||||
@@ -133,7 +135,8 @@ export function checkAndUpgradeSaveSetting(val: any) {
|
||||
export function shakeCommonDict(n: BaseState): BaseState {
|
||||
let data: BaseState = cloneDeep(n)
|
||||
data.word.bookList.map((v: Dict) => {
|
||||
if (!v.custom && ![DictId.wordKnown, DictId.wordWrong, DictId.wordCollect].includes(v.id)) v.words = []
|
||||
if (!v.custom && ![DictId.wordKnown, DictId.wordWrong, DictId.wordCollect].includes(v.id))
|
||||
v.words = []
|
||||
})
|
||||
data.article.bookList.map((v: Dict) => {
|
||||
if (!v.custom && ![DictId.articleCollect].includes(v.id)) v.articles = []
|
||||
@@ -159,10 +162,10 @@ export function useNav() {
|
||||
if (data) {
|
||||
runtimeStore.routeData = cloneDeep(data)
|
||||
}
|
||||
router.push({path, query})
|
||||
router.push({ path, query })
|
||||
}
|
||||
|
||||
return {nav, push: nav, back: router.back}
|
||||
return { nav, push: nav, back: router.back }
|
||||
}
|
||||
|
||||
export function _dateFormat(val: any, format: string = 'YYYY/MM/DD HH:mm'): string {
|
||||
@@ -175,17 +178,17 @@ export function _dateFormat(val: any, format: string = 'YYYY/MM/DD HH:mm'): stri
|
||||
}
|
||||
|
||||
export function msToHourMinute(ms) {
|
||||
const d = dayjs.duration(ms);
|
||||
const hours = d.hours();
|
||||
const minutes = d.minutes();
|
||||
const seconds = d.seconds();
|
||||
if (hours) return `${hours}小时${minutes}分钟`;
|
||||
if (minutes) return `${minutes}分钟`;
|
||||
return `${seconds}秒`;
|
||||
const d = dayjs.duration(ms)
|
||||
const hours = d.hours()
|
||||
const minutes = d.minutes()
|
||||
const seconds = d.seconds()
|
||||
if (hours) return `${hours}小时${minutes}分钟`
|
||||
if (minutes) return `${minutes}分钟`
|
||||
return `${seconds}秒`
|
||||
}
|
||||
|
||||
export function msToMinute(ms) {
|
||||
return `${Math.floor(dayjs.duration(ms).asMinutes())}分钟`;
|
||||
return `${Math.floor(dayjs.duration(ms).asMinutes())}分钟`
|
||||
}
|
||||
|
||||
//获取完成天数
|
||||
@@ -218,44 +221,47 @@ export function _copy(val: string) {
|
||||
navigator.clipboard.writeText(val)
|
||||
}
|
||||
|
||||
export function _parseLRC(lrc: string): { start: number, end: number, text: string }[] {
|
||||
const lines = lrc.split("\n").filter(line => line.trim() !== "");
|
||||
const regex = /\[(\d{2}):(\d{2}\.\d{2})\](.*)/;
|
||||
let parsed: any = [];
|
||||
export function _parseLRC(lrc: string): { start: number; end: number; text: string }[] {
|
||||
const lines = lrc.split('\n').filter(line => line.trim() !== '')
|
||||
const regex = /\[(\d{2}):(\d{2}\.\d{2})\](.*)/
|
||||
let parsed: any = []
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let match = lines[i].match(regex);
|
||||
let match = lines[i].match(regex)
|
||||
if (match) {
|
||||
let start = parseFloat(match[1]) * 60 + parseFloat(match[2]); // 转换成秒
|
||||
let text = match[3].trim();
|
||||
let start = parseFloat(match[1]) * 60 + parseFloat(match[2]) // 转换成秒
|
||||
let text = match[3].trim()
|
||||
|
||||
// 计算结束时间(下一个时间戳)
|
||||
let nextMatch = lines[i + 1] ? lines[i + 1].match(regex) : null;
|
||||
let end = nextMatch ? parseFloat(nextMatch[1]) * 60 + parseFloat(nextMatch[2]) : null;
|
||||
let nextMatch = lines[i + 1] ? lines[i + 1].match(regex) : null
|
||||
let end = nextMatch ? parseFloat(nextMatch[1]) * 60 + parseFloat(nextMatch[2]) : null
|
||||
|
||||
parsed.push({start, end, text});
|
||||
parsed.push({ start, end, text })
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
return parsed
|
||||
}
|
||||
|
||||
export async function sleep(time: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, time));
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
export async function _getDictDataByUrl(val: DictResource, type: DictType = DictType.word): Promise<Dict> {
|
||||
export async function _getDictDataByUrl(
|
||||
val: DictResource,
|
||||
type: DictType = DictType.word
|
||||
): Promise<Dict> {
|
||||
// await sleep(2000);
|
||||
let dictResourceUrl = `/dicts/${val.language}/word/${val.url}`
|
||||
if (type === DictType.article) {
|
||||
dictResourceUrl = `/dicts/${val.language}/article/${val.url}`;
|
||||
dictResourceUrl = `/dicts/${val.language}/article/${val.url}`
|
||||
}
|
||||
let s = await fetch(resourceWrap(dictResourceUrl, val.version)).then(r => r.json())
|
||||
if (s) {
|
||||
if (type === DictType.word) {
|
||||
return getDefaultDict({...val, words: s})
|
||||
return getDefaultDict({ ...val, words: s })
|
||||
} else {
|
||||
return getDefaultDict({...val, articles: s})
|
||||
return getDefaultDict({ ...val, articles: s })
|
||||
}
|
||||
}
|
||||
return getDefaultDict()
|
||||
@@ -263,97 +269,97 @@ export async function _getDictDataByUrl(val: DictResource, type: DictType = Dict
|
||||
|
||||
//从字符串里面转换为Word格式
|
||||
export function convertToWord(raw: any) {
|
||||
const safeString = (str) => (typeof str === 'string' ? str.trim() : '');
|
||||
const safeString = str => (typeof str === 'string' ? str.trim() : '')
|
||||
const safeSplit = (str, sep) =>
|
||||
safeString(str) ? safeString(str).split(sep).filter(Boolean) : [];
|
||||
safeString(str) ? safeString(str).split(sep).filter(Boolean) : []
|
||||
|
||||
// 1. trans
|
||||
const trans = safeSplit(raw.trans, '\n').map(line => {
|
||||
const match = safeString(line).match(/^([^\s.]+\.?)\s*(.*)$/);
|
||||
const match = safeString(line).match(/^([^\s.]+\.?)\s*(.*)$/)
|
||||
if (match) {
|
||||
let pos = safeString(match[1]);
|
||||
let cn = safeString(match[2]);
|
||||
let pos = safeString(match[1])
|
||||
let cn = safeString(match[2])
|
||||
|
||||
// 如果 pos 不是常规词性(不以字母开头),例如 "【名】"
|
||||
if (!/^[a-zA-Z]+\.?$/.test(pos)) {
|
||||
cn = safeString(line); // 整行放到 cn
|
||||
pos = ''; // pos 置空
|
||||
cn = safeString(line) // 整行放到 cn
|
||||
pos = '' // pos 置空
|
||||
}
|
||||
|
||||
return {pos, cn};
|
||||
return { pos, cn }
|
||||
}
|
||||
return {pos: '', cn: safeString(line)};
|
||||
});
|
||||
return { pos: '', cn: safeString(line) }
|
||||
})
|
||||
|
||||
// 2. sentences
|
||||
const sentences = safeSplit(raw.sentences, '\n\n').map(block => {
|
||||
const [c, cn] = block.split('\n');
|
||||
return {c: safeString(c), cn: safeString(cn)};
|
||||
});
|
||||
const [c, cn] = block.split('\n')
|
||||
return { c: safeString(c), cn: safeString(cn) }
|
||||
})
|
||||
|
||||
// 3. phrases
|
||||
const phrases = safeSplit(raw.phrases, '\n\n').map(block => {
|
||||
const [c, cn] = block.split('\n');
|
||||
return {c: safeString(c), cn: safeString(cn)};
|
||||
});
|
||||
const [c, cn] = block.split('\n')
|
||||
return { c: safeString(c), cn: safeString(cn) }
|
||||
})
|
||||
|
||||
// 4. synos
|
||||
const synos = safeSplit(raw.synos, '\n\n').map(block => {
|
||||
const lines = block.split('\n').map(safeString);
|
||||
const [posCn, wsStr] = lines;
|
||||
let pos = '';
|
||||
let cn = '';
|
||||
const lines = block.split('\n').map(safeString)
|
||||
const [posCn, wsStr] = lines
|
||||
let pos = ''
|
||||
let cn = ''
|
||||
|
||||
if (posCn) {
|
||||
const posMatch = posCn.match(/^([a-zA-Z.]+)(.*)$/);
|
||||
pos = posMatch ? safeString(posMatch[1]) : '';
|
||||
cn = posMatch ? safeString(posMatch[2]) : safeString(posCn);
|
||||
const posMatch = posCn.match(/^([a-zA-Z.]+)(.*)$/)
|
||||
pos = posMatch ? safeString(posMatch[1]) : ''
|
||||
cn = posMatch ? safeString(posMatch[2]) : safeString(posCn)
|
||||
}
|
||||
const ws = wsStr ? wsStr.split('/').map(safeString) : [];
|
||||
const ws = wsStr ? wsStr.split('/').map(safeString) : []
|
||||
|
||||
return {pos, cn, ws};
|
||||
});
|
||||
return { pos, cn, ws }
|
||||
})
|
||||
|
||||
// 5. relWords
|
||||
const relWordsText = safeString(raw.relWords);
|
||||
let root = '';
|
||||
const rels = [];
|
||||
const relWordsText = safeString(raw.relWords)
|
||||
let root = ''
|
||||
const rels = []
|
||||
|
||||
if (relWordsText) {
|
||||
const relLines = relWordsText.split('\n').filter(Boolean);
|
||||
const relLines = relWordsText.split('\n').filter(Boolean)
|
||||
if (relLines.length > 0) {
|
||||
root = safeString(relLines[0].replace(/^词根:/, ''));
|
||||
let currentPos = '';
|
||||
let currentWords = [];
|
||||
root = safeString(relLines[0].replace(/^词根:/, ''))
|
||||
let currentPos = ''
|
||||
let currentWords = []
|
||||
|
||||
for (let i = 1; i < relLines.length; i++) {
|
||||
const line = relLines[i].trim();
|
||||
if (!line) continue;
|
||||
const line = relLines[i].trim()
|
||||
if (!line) continue
|
||||
|
||||
if (/^[a-z]+\./i.test(line)) {
|
||||
if (currentPos && currentWords.length > 0) {
|
||||
rels.push({pos: currentPos, words: currentWords});
|
||||
rels.push({ pos: currentPos, words: currentWords })
|
||||
}
|
||||
currentPos = safeString(line.replace(':', ''));
|
||||
currentWords = [];
|
||||
currentPos = safeString(line.replace(':', ''))
|
||||
currentWords = []
|
||||
} else if (line.includes(':')) {
|
||||
const [c, cn] = line.split(':');
|
||||
currentWords.push({c: safeString(c), cn: safeString(cn)});
|
||||
const [c, cn] = line.split(':')
|
||||
currentWords.push({ c: safeString(c), cn: safeString(cn) })
|
||||
}
|
||||
}
|
||||
if (currentPos && currentWords.length > 0) {
|
||||
rels.push({pos: currentPos, words: currentWords});
|
||||
rels.push({ pos: currentPos, words: currentWords })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. etymology
|
||||
const etymology = safeSplit(raw.etymology, '\n\n').map(block => {
|
||||
const lines = block.split('\n').map(safeString);
|
||||
const t = lines.shift() || '';
|
||||
const d = lines.join('\n').trim();
|
||||
return {t, d};
|
||||
});
|
||||
const lines = block.split('\n').map(safeString)
|
||||
const t = lines.shift() || ''
|
||||
const d = lines.join('\n').trim()
|
||||
return { t, d }
|
||||
})
|
||||
|
||||
return getDefaultWord({
|
||||
id: raw.id,
|
||||
@@ -364,68 +370,68 @@ export function convertToWord(raw: any) {
|
||||
sentences,
|
||||
phrases,
|
||||
synos,
|
||||
relWords: {root, rels},
|
||||
relWords: { root, rels },
|
||||
etymology,
|
||||
custom: true
|
||||
});
|
||||
custom: true,
|
||||
})
|
||||
}
|
||||
|
||||
export function cloneDeep<T>(val: T) {
|
||||
export function cloneDeep<T>(val: T): T {
|
||||
return JSON.parse(JSON.stringify(val))
|
||||
}
|
||||
|
||||
export function shuffle<T>(array: T[]): T[] {
|
||||
const result = array.slice(); // 复制数组,避免修改原数组
|
||||
const result = array.slice() // 复制数组,避免修改原数组
|
||||
for (let i = result.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1)); // 生成 0 ~ i 的随机索引
|
||||
[result[i], result[j]] = [result[j], result[i]]; // 交换元素
|
||||
const j = Math.floor(Math.random() * (i + 1)) // 生成 0 ~ i 的随机索引
|
||||
;[result[i], result[j]] = [result[j], result[i]] // 交换元素
|
||||
}
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
export function last<T>(array: T[]): T | undefined {
|
||||
return array.length > 0 ? array[array.length - 1] : undefined;
|
||||
return array.length > 0 ? array[array.length - 1] : undefined
|
||||
}
|
||||
|
||||
export function debounce<T extends (...args: any[]) => void>(func: T, wait: number) {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
|
||||
if (timer) clearTimeout(timer);
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, wait);
|
||||
};
|
||||
func.apply(this, args)
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
|
||||
export function throttle<T extends (...args: any[]) => void>(func: T, wait: number) {
|
||||
let lastTime = 0;
|
||||
let lastTime = 0
|
||||
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
|
||||
const now = Date.now();
|
||||
const now = Date.now()
|
||||
if (now - lastTime >= wait) {
|
||||
func.apply(this, args);
|
||||
lastTime = now;
|
||||
func.apply(this, args)
|
||||
lastTime = now
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function reverse<T>(array: T[]): T[] {
|
||||
return array.slice().reverse();
|
||||
return array.slice().reverse()
|
||||
}
|
||||
|
||||
export function groupBy<T extends Record<string, any>>(array: T[], key: string) {
|
||||
return array.reduce<Record<string, T[]>>((result, item) => {
|
||||
const groupKey = String(item[key]);
|
||||
(result[groupKey] ||= []).push(item);
|
||||
return result;
|
||||
}, {});
|
||||
const groupKey = String(item[key])
|
||||
;(result[groupKey] ||= []).push(item)
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
//随机取N个
|
||||
export function getRandomN(arr: any[], n: number) {
|
||||
const copy = [...arr]
|
||||
for (let i = copy.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[copy[i], copy[j]] = [copy[j], copy[i]] // 交换
|
||||
const j = Math.floor(Math.random() * (i + 1))
|
||||
;[copy[i], copy[j]] = [copy[j], copy[i]] // 交换
|
||||
}
|
||||
return copy.slice(0, n)
|
||||
}
|
||||
@@ -434,8 +440,8 @@ export function getRandomN(arr: any[], n: number) {
|
||||
export function splitIntoN(arr: any[], n: number) {
|
||||
const result = []
|
||||
const len = arr.length
|
||||
const base = Math.floor(len / n) // 每份至少这么多
|
||||
let extra = len % n // 前几份多 1 个
|
||||
const base = Math.floor(len / n) // 每份至少这么多
|
||||
let extra = len % n // 前几份多 1 个
|
||||
|
||||
let index = 0
|
||||
for (let i = 0; i < n; i++) {
|
||||
@@ -448,43 +454,43 @@ export function splitIntoN(arr: any[], n: number) {
|
||||
}
|
||||
|
||||
export async function loadJsLib(key: string, url: string) {
|
||||
if (window[key]) return window[key];
|
||||
if (window[key]) return window[key]
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
const script = document.createElement('script')
|
||||
// 判断是否是 .mjs 文件,如果是,则使用 type="module"
|
||||
if (url.endsWith(".mjs")) {
|
||||
script.type = "module"; // 需要加上 type="module"
|
||||
script.src = url;
|
||||
if (url.endsWith('.mjs')) {
|
||||
script.type = 'module' // 需要加上 type="module"
|
||||
script.src = url
|
||||
script.onload = async () => {
|
||||
try {
|
||||
// 使用动态 import 加载模块
|
||||
const module = await import(url); // 动态导入 .mjs 模块
|
||||
window[key] = module.default || module; // 将模块挂到 window 对象
|
||||
resolve(window[key]);
|
||||
const module = await import(url) // 动态导入 .mjs 模块
|
||||
window[key] = module.default || module // 将模块挂到 window 对象
|
||||
resolve(window[key])
|
||||
} catch (err) {
|
||||
reject(`${key} 加载失败: ${err.message}`);
|
||||
reject(`${key} 加载失败: ${err.message}`)
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 如果是非 .mjs 文件,直接按原方式加载
|
||||
script.src = url;
|
||||
script.onload = () => resolve(window[key]);
|
||||
script.src = url
|
||||
script.onload = () => resolve(window[key])
|
||||
}
|
||||
script.onerror = () => reject(key + " 加载失败");
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
script.onerror = () => reject(key + ' 加载失败')
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
export function total(arr, key) {
|
||||
return arr.reduce((a, b) => {
|
||||
a += b[key];
|
||||
a += b[key]
|
||||
return a
|
||||
}, 0);
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function resourceWrap(resource: string, version?: number) {
|
||||
if (AppEnv.IS_OFFICIAL) {
|
||||
if (resource.includes('.json')) resource = resource.replace('.json', '');
|
||||
if (resource.includes('.json')) resource = resource.replace('.json', '')
|
||||
if (!resource.includes('http')) resource = RESOURCE_PATH + resource
|
||||
if (version === undefined) {
|
||||
const store = useBaseStore()
|
||||
@@ -492,7 +498,7 @@ export function resourceWrap(resource: string, version?: number) {
|
||||
}
|
||||
return `${resource}_v${version}.json`
|
||||
}
|
||||
return resource;
|
||||
return resource
|
||||
}
|
||||
|
||||
// check if it is a new user
|
||||
@@ -501,9 +507,11 @@ export async function isNewUser() {
|
||||
let base = useBaseStore()
|
||||
console.log(JSON.stringify(base.$state))
|
||||
console.log(JSON.stringify(getDefaultBaseState()))
|
||||
return JSON.stringify(base.$state) === JSON.stringify({...getDefaultBaseState(), ...{load: true}})
|
||||
return (
|
||||
JSON.stringify(base.$state) === JSON.stringify({ ...getDefaultBaseState(), ...{ load: true } })
|
||||
)
|
||||
}
|
||||
|
||||
export function jump2Feedback() {
|
||||
window.open('https://v.wjx.cn/vm/ev0W7fv.aspx#', '_blank');
|
||||
window.open('https://v.wjx.cn/vm/ev0W7fv.aspx#', '_blank')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user