fix:fix getCurrentStudyWord function,fix Setting page
This commit is contained in:
@@ -164,6 +164,7 @@ html, body {
|
||||
overflow-x: hidden;
|
||||
color: var(--color-main-text);
|
||||
font-family: var(--font-family);
|
||||
background: var(--color-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {Article, Word} 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";
|
||||
|
||||
export function useWordOptions() {
|
||||
const store = useBaseStore()
|
||||
@@ -83,105 +86,98 @@ export function useArticleOptions() {
|
||||
}
|
||||
|
||||
export function getCurrentStudyWord() {
|
||||
// console.time()
|
||||
console.log('getCurrentStudyWord')
|
||||
const store = useBaseStore()
|
||||
let data = {new: [], review: [], write: []}
|
||||
let dict = store.sdict;
|
||||
if (dict.words?.length) {
|
||||
const getList = (startIndex: number, endIndex: number) => dict.words.slice(startIndex, endIndex)
|
||||
|
||||
const perDay = store.sdict.perDayStudyNumber;
|
||||
const totalNeed = perDay * 3;
|
||||
let isTest = false
|
||||
let words = dict.words.slice()
|
||||
if (isTest) {
|
||||
words = Array.from({length: 10}).map((v, i) => {
|
||||
return getDefaultWord({word: String(i)})
|
||||
})
|
||||
}
|
||||
if (words?.length) {
|
||||
const settingStore = useSettingStore()
|
||||
//忽略时是否加上自定义的简单词
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
|
||||
const perDay = dict.perDayStudyNumber;
|
||||
let start = dict.lastLearnIndex;
|
||||
let end = start + dict.perDayStudyNumber
|
||||
|
||||
if (dict.complete) {
|
||||
let complete = dict.complete;
|
||||
if (isTest) {
|
||||
start = 1
|
||||
complete = true
|
||||
}
|
||||
let end = start
|
||||
let list = dict.words.slice(start)
|
||||
if (complete) {
|
||||
//如果是已完成,那么把应该学的新词放到复习词组里面
|
||||
dict.words.slice(start, end).map(item => {
|
||||
if (!store.knownWords.includes(item.word)) {
|
||||
data.review.push(item)
|
||||
for (let item of list) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
if (data.review.length < perDay) {
|
||||
data.review.push(item)
|
||||
} else break
|
||||
}
|
||||
})
|
||||
//如果起点index 减去总默写不足的话,那就直接从最后面取
|
||||
//todo 这里有空了,优化成往前滚动取值
|
||||
if (start - totalNeed < 0) {
|
||||
end = dict.length
|
||||
} else {
|
||||
end = start
|
||||
end++
|
||||
}
|
||||
} else {
|
||||
dict.words.slice(start, end).map(item => {
|
||||
if (!store.knownWords.includes(item.word)) {
|
||||
data.new.push(item)
|
||||
//从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 = start
|
||||
start = start - dict.perDayStudyNumber
|
||||
if (start < 0) start = 0
|
||||
//取上一次学习的单词用于复习
|
||||
let list = getList(start, end)
|
||||
list.map(item => {
|
||||
if (!store.knownWords.includes(item.word)) {
|
||||
data.review.push(item)
|
||||
end++
|
||||
}
|
||||
//从start往前取perDay个单词,作为当前复习单词,取到0为止
|
||||
list = dict.words.slice(0, start).toReversed()
|
||||
for (let item of list) {
|
||||
if (!ignoreList.includes(item.word.toLowerCase())) {
|
||||
if (data.review.length < perDay) {
|
||||
data.review.push(item)
|
||||
} else break
|
||||
}
|
||||
})
|
||||
|
||||
end = start
|
||||
start--
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(start,end)
|
||||
// end = start
|
||||
// start = start - dict.perDayStudyNumber * 3
|
||||
// //在上次学习再往前取前3次学习的单词用于默写
|
||||
// list = getList(start, end)
|
||||
// list.map(item => {
|
||||
// if (!store.knownWords.includes(item.word)) {
|
||||
// data.write.push(item)
|
||||
// }
|
||||
// })
|
||||
|
||||
//write数组放的是上上次之前的单词,总的数量为perDayStudyNumber * 3,取单词的规则为:从后往前取6个perDayStudyNumber的,越靠前的取的单词越多。
|
||||
|
||||
// 上上次更早的单词
|
||||
if (end > 0) {
|
||||
const allWords = dict.words;
|
||||
const candidateWords = allWords.slice(0, end).filter(w => !store.knownWords.includes(w.word));
|
||||
//默认只取start之前的单词
|
||||
let candidateWords = dict.words.slice(0, start).toReversed()
|
||||
//但如果已完成,则滚动取值
|
||||
if (complete) candidateWords = candidateWords.concat(dict.words.slice(end).toReversed())
|
||||
candidateWords = candidateWords.filter(w => !ignoreList.includes(w.word.toLowerCase()));
|
||||
// console.log(candidateWords.map(v => v.word))
|
||||
//最终要获取的单词数量
|
||||
const totalNeed = perDay * 3;
|
||||
if (candidateWords.length <= totalNeed) {
|
||||
data.write = candidateWords
|
||||
} else {
|
||||
//write数组放的是上上次之前的单词,总的数量为perDayStudyNumber * 3,取单词的规则为:从后往前取6个perDayStudyNumber的,越靠前的取的单词越多。
|
||||
let days = 6
|
||||
// 分6组,每组最多 perDay 个
|
||||
const groups: Word[][] = splitIntoN(candidateWords.slice(0, days * perDay), 6)
|
||||
// console.log('groups', groups)
|
||||
|
||||
// 分6组,每组 perDay 个
|
||||
const groupCount = 6;
|
||||
const groupSize = perDay;
|
||||
const groups: Word[][] = [];
|
||||
for (let i = 0; i < groupCount; i++) {
|
||||
const start = candidateWords.length - (i + 1) * groupSize;
|
||||
const end = candidateWords.length - i * groupSize;
|
||||
if (start < 0 && end <= 0) break;
|
||||
groups.unshift(candidateWords.slice(Math.max(0, start), Math.max(0, end)));
|
||||
}
|
||||
|
||||
// 分配数量,靠前组多,靠后组少
|
||||
// 例如分配比例 [1,2,3,4,5,6]
|
||||
const ratio = [1, 2, 3, 4, 5, 6];
|
||||
// 分配数量,靠前组多,靠后组少,例如分配比例 [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 * totalNeed / ratioSum));
|
||||
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[] = [];
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
writeWords = writeWords.concat(groups[i].slice(-realRatio[i]));
|
||||
}
|
||||
// 如果数量不够,补足
|
||||
if (writeWords.length < totalNeed) {
|
||||
const extra = candidateWords.filter(w => !writeWords.includes(w)).slice(-(totalNeed - writeWords.length));
|
||||
writeWords = writeWords.concat(extra);
|
||||
}
|
||||
// 最终数量截断
|
||||
writeWords = writeWords.slice(-totalNeed);
|
||||
|
||||
//这里需要反转一下,越靠近今天的单词,越先练习
|
||||
data.write = writeWords.reverse();
|
||||
groups.map((v, i) => {
|
||||
writeWords = writeWords.concat(getRandomN(v, realRatio[i]))
|
||||
})
|
||||
// console.log('writeWords', writeWords)
|
||||
data.write = writeWords;
|
||||
}
|
||||
}
|
||||
// console.timeEnd()
|
||||
// console.log('data', data)
|
||||
// console.log('data-new', data.new.map(v => v.word))
|
||||
// console.log('data-review', data.review.map(v => v.word))
|
||||
// console.log('data-write', data.write.map(v => v.word))
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ function setArticle(val: Article) {
|
||||
articleData.article.sections.map((v, i) => {
|
||||
v.map((w, j) => {
|
||||
w.words.map(s => {
|
||||
if (!store.knownWordsWithSimpleWords.includes(s.word.toLowerCase()) && !s.isSymbol) {
|
||||
if (!store.allIgnoreWords.includes(s.word.toLowerCase()) && !s.isSymbol) {
|
||||
statisticsStore.total++
|
||||
}
|
||||
})
|
||||
@@ -199,13 +199,13 @@ function wrong(word: Word) {
|
||||
if (!store.wrong.words.find((v: Word) => v.word.toLowerCase() === lowerName)) {
|
||||
store.wrong.words.push(word)
|
||||
}
|
||||
if (!store.knownWordsWithSimpleWords.includes(lowerName)) {
|
||||
if (!store.allIgnoreWords.includes(lowerName)) {
|
||||
//todo
|
||||
}
|
||||
}
|
||||
|
||||
function nextWord(word: ArticleWord) {
|
||||
if (!store.knownWordsWithSimpleWords.includes(word.word.toLowerCase()) && !word.isSymbol) {
|
||||
if (!store.allIgnoreWords.includes(word.word.toLowerCase()) && !word.isSymbol) {
|
||||
statisticsStore.inputWordNumber++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ function nextSentence() {
|
||||
input = wrong = ''
|
||||
|
||||
//todo 计得把略过的单词加上统计里面去
|
||||
// if (!store.knownWordsWithSimpleWords.includes(currentWord.word.toLowerCase()) && !currentWord.isSymbol) {
|
||||
// if (!store.allIgnoreWords.includes(currentWord.word.toLowerCase()) && !currentWord.isSymbol) {
|
||||
// statisticsStore.inputNumber++
|
||||
// }
|
||||
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watch} from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
interface IProps {
|
||||
modelValue: boolean;
|
||||
disabled?: boolean;
|
||||
width?: number; // 开关宽度,默认 40px
|
||||
activeText?: string; // 开启状态显示文字
|
||||
inactiveText?: string;// 关闭状态显示文字
|
||||
}>();
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
activeText: '开',
|
||||
inactiveText: '关',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change']);
|
||||
|
||||
const isChecked = ref(props.modelValue);
|
||||
|
||||
@@ -21,6 +26,7 @@ const toggle = () => {
|
||||
if (props.disabled) return;
|
||||
isChecked.value = !isChecked.value;
|
||||
emit('update:modelValue', isChecked.value);
|
||||
emit('change', isChecked.value);
|
||||
};
|
||||
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
@@ -96,7 +102,6 @@ const ballSize = computed(() => switchHeight.value - 4);
|
||||
font-size: 0.75rem;
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
top: 0;
|
||||
|
||||
&.left {
|
||||
margin-left: 6px;
|
||||
|
||||
@@ -23,17 +23,15 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, watch, computed, nextTick} from "vue"
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
placeholder: String,
|
||||
maxlength: Number,
|
||||
rows: {type: Number, default: 1},
|
||||
autosize: {
|
||||
type: [Boolean, Object] as () => boolean | { minRows?: number; maxRows?: number },
|
||||
default: false
|
||||
},
|
||||
showWordLimit: Boolean
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string,
|
||||
placeholder?: string,
|
||||
maxlength?: number,
|
||||
rows?: number,
|
||||
autosize: boolean | { minRows?: number; maxRows?: number }
|
||||
showWordLimit?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
@@ -44,7 +42,7 @@ const textareaRef = ref<HTMLTextAreaElement>()
|
||||
|
||||
// 样式(用于控制高度)
|
||||
const textareaStyle = computed(() => {
|
||||
return props.autosize ? { height: "auto" } : {}
|
||||
return props.autosize ? {height: "auto"} : {}
|
||||
})
|
||||
|
||||
// 输入处理
|
||||
@@ -64,7 +62,7 @@ const resizeTextarea = () => {
|
||||
let overflow = "hidden"
|
||||
|
||||
if (typeof props.autosize === "object") {
|
||||
const { minRows, maxRows } = props.autosize
|
||||
const {minRows, maxRows} = props.autosize
|
||||
const lineHeight = 24 // 行高约等于 24px
|
||||
if (minRows) height = Math.max(height, minRows * lineHeight)
|
||||
if (maxRows) {
|
||||
@@ -90,6 +88,7 @@ textarea {
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-input-color);
|
||||
background: var(--color-input-bg);
|
||||
@apply text-base;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
@@ -52,6 +52,7 @@ function onClick() {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -30,7 +30,7 @@ watch(() => props.value, () => {}, { immediate: true });
|
||||
|
||||
<template>
|
||||
<li
|
||||
class="el-option"
|
||||
class="option"
|
||||
:class="{
|
||||
'is-selected': isSelected,
|
||||
'is-disabled': disabled
|
||||
@@ -38,13 +38,13 @@ watch(() => props.value, () => {}, { immediate: true });
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot>
|
||||
<span class="el-option__label">{{ label }}</span>
|
||||
<span class="option__label">{{ label }}</span>
|
||||
</slot>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-option {
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem 1rem;
|
||||
|
||||
@@ -150,16 +150,16 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="custom-select"
|
||||
class="select"
|
||||
v-bind="attrs"
|
||||
:class="{ 'is-disabled': disabled, 'is-active': isOpen, 'is-reverse': isReverse }"
|
||||
ref="selectRef"
|
||||
>
|
||||
<div class="custom-select__wrapper" @click="toggleDropdown">
|
||||
<div class="custom-select__label" :class="{ 'is-placeholder': !selectedOption }">
|
||||
<div class="select__wrapper" @click="toggleDropdown">
|
||||
<div class="select__label" :class="{ 'is-placeholder': !selectedOption }">
|
||||
{{ displayValue }}
|
||||
</div>
|
||||
<div class="custom-select__suffix">
|
||||
<div class="select__suffix">
|
||||
<IconMdiChevronDown
|
||||
:class="{ 'is-reverse': isOpen }"
|
||||
width="16"
|
||||
@@ -170,17 +170,17 @@ onBeforeUnmount(() => {
|
||||
<teleport to="body">
|
||||
<transition :name="isReverse ? 'zoom-in-bottom' : 'zoom-in-top'" :key="isReverse ? 'bottom' : 'top'">
|
||||
<div
|
||||
class="custom-select__dropdown"
|
||||
class="select__dropdown"
|
||||
v-if="isOpen"
|
||||
ref="dropdownRef"
|
||||
:style="dropdownStyle"
|
||||
>
|
||||
<ul class="custom-select__options">
|
||||
<ul class="select__options">
|
||||
<li
|
||||
v-if="options"
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
class="custom-select__option"
|
||||
class="select__option"
|
||||
:class="{
|
||||
'is-selected': option.value === modelValue,
|
||||
'is-disabled': option.disabled
|
||||
@@ -198,7 +198,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-select {
|
||||
.select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
@@ -243,7 +243,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.custom-select__dropdown {
|
||||
.select__dropdown {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
@@ -252,13 +252,13 @@ onBeforeUnmount(() => {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.custom-select__options {
|
||||
.select__options {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.custom-select__option {
|
||||
.select__option {
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound } from "@/hooks/sound.ts";
|
||||
import { getShortcutKey, useEventListener } from "@/hooks/event.ts";
|
||||
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, shakeCommonDict } from "@/utils";
|
||||
import { DefaultShortcutKeyMap, ShortcutKey } from "@/types/types.ts";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
|
||||
import {getShortcutKey, useEventListener} from "@/hooks/event.ts";
|
||||
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, shakeCommonDict} from "@/utils";
|
||||
import {DefaultShortcutKeyMap, ShortcutKey} from "@/types/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions } from "@/utils/const.ts";
|
||||
import {APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions} from "@/utils/const.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { saveAs } from "file-saver";
|
||||
import { GITHUB } from "@/config/ENV.ts";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {saveAs} from "file-saver";
|
||||
import {GITHUB} from "@/config/ENV.ts";
|
||||
import dayjs from "dayjs";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
|
||||
import { Option, Select } from "@/pages/pc/components/base/select";
|
||||
import {Option, Select} from "@/pages/pc/components/base/select";
|
||||
import Switch from "@/pages/pc/components/base/Switch.vue";
|
||||
import Slider from "@/pages/pc/components/base/Slider.vue";
|
||||
import RadioGroup from "@/pages/pc/components/base/radio/RadioGroup.vue";
|
||||
import Radio from "@/pages/pc/components/base/radio/Radio.vue";
|
||||
import InputNumber from "@/pages/pc/components/base/InputNumber.vue";
|
||||
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
|
||||
import { get, set } from "idb-keyval";
|
||||
import {get, set} from "idb-keyval";
|
||||
import BaseInput from "@/pages/pc/components/base/BaseInput.vue";
|
||||
import Textarea from "@/pages/pc/components/base/Textarea.vue";
|
||||
import SettingItem from "@/pages/pc/setting/SettingItem.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
@@ -32,7 +35,16 @@ const settingStore = useSettingStore()
|
||||
const store = useBaseStore()
|
||||
//@ts-ignore
|
||||
const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
|
||||
const simpleWords = $computed({
|
||||
get: () => store.simpleWords.join(','),
|
||||
set: v => {
|
||||
try {
|
||||
store.simpleWords = v.split(',');
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
useWatchAllSound()
|
||||
|
||||
let editShortcutKey = $ref('')
|
||||
@@ -93,7 +105,7 @@ function exportData(notice = '导出成功!') {
|
||||
}
|
||||
}
|
||||
}
|
||||
let blob = new Blob([JSON.stringify(data)], { type: "text/plain;charset=utf-8" });
|
||||
let blob = new Blob([JSON.stringify(data)], {type: "text/plain;charset=utf-8"});
|
||||
saveAs(blob, `${APP_NAME}-User-Data-${dayjs().format('YYYY-MM-DD HH-mm-ss')}.json`);
|
||||
Toast.success(notice)
|
||||
}
|
||||
@@ -117,8 +129,10 @@ function importData(e) {
|
||||
obj = JSON.parse(str)
|
||||
let data = obj.val
|
||||
let settingState = checkAndUpgradeSaveSetting(data.setting)
|
||||
settingState.load = true
|
||||
settingStore.setState(settingState)
|
||||
let baseState = checkAndUpgradeSaveDict(data.dict)
|
||||
baseState.load = true
|
||||
store.setState(baseState)
|
||||
Toast.success('导入成功!')
|
||||
} catch (err) {
|
||||
@@ -136,7 +150,7 @@ function importOldData() {
|
||||
if (oldDataStr) {
|
||||
try {
|
||||
let obj = JSON.parse(oldDataStr)
|
||||
let data = {
|
||||
let data = {
|
||||
version: 3,
|
||||
val: obj
|
||||
}
|
||||
@@ -187,215 +201,151 @@ function importOldData() {
|
||||
<div class="content">
|
||||
<div class="page-title text-align-center">设置</div>
|
||||
<div v-if="tabIndex === 0">
|
||||
<div class="row">
|
||||
<label class="main-title">所有音效</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.allSound"
|
||||
@change="useChangeAllSound"
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">单词/句子自动发音</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.wordSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">单词/句子发音口音</label>
|
||||
<div class="wrapper">
|
||||
<Select v-model="settingStore.wordSoundType"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
>
|
||||
<Option label="美音" value="us"/>
|
||||
<Option label="英音" value="uk"/>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">音量</label>
|
||||
<div class="wrapper">
|
||||
<Slider v-model="settingStore.wordSoundVolume"/>
|
||||
<span>{{ settingStore.wordSoundVolume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">倍速</label>
|
||||
<div class="wrapper">
|
||||
<Slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
|
||||
<span>{{ settingStore.wordSoundSpeed }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">按键音</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.keyboardSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="item-title">按键音效</label>
|
||||
<div class="wrapper">
|
||||
<Select v-model="settingStore.keyboardSoundFile"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
>
|
||||
<Option
|
||||
v-for="item in SoundFileOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div class="el-option-row">
|
||||
<span>{{ item.label }}</span>
|
||||
<VolumeIcon
|
||||
:time="100"
|
||||
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
|
||||
</div>
|
||||
</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">音量</label>
|
||||
<div class="wrapper">
|
||||
<Slider v-model="settingStore.keyboardSoundVolume"/>
|
||||
<span>{{ settingStore.keyboardSoundVolume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">效果音(输入错误、完成时的音效)</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.effectSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">音量</label>
|
||||
<div class="wrapper">
|
||||
<Slider v-model="settingStore.effectSoundVolume"/>
|
||||
<span>{{ settingStore.effectSoundVolume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 1">
|
||||
<div class="row">
|
||||
<label class="item-title">单词循环设置</label>
|
||||
<div class="wrapper">
|
||||
<RadioGroup v-model="settingStore.repeatCount">
|
||||
<Radio :value="1" size="default">1</Radio>
|
||||
<Radio :value="2" size="default">2</Radio>
|
||||
<Radio :value="3" size="default">3</Radio>
|
||||
<Radio :value="5" size="default">5</Radio>
|
||||
<Radio :value="100" size="default">自定义</Radio>
|
||||
</RadioGroup>
|
||||
<div class="mini-row" v-if="settingStore.repeatCount === 100">
|
||||
<label class="item-title">循环次数</label>
|
||||
<InputNumber v-model="settingStore.repeatCustomCount"
|
||||
:min="6"
|
||||
:max="15"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="item-title">显示上一个/下一个单词</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.showNearWord"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
开启后,练习中会在上方显示上一个/下一个单词
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">忽略大小写</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.ignoreCase"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
开启后,输入时不区分大小写,如输入“hello”和“Hello”都会被认为是正确的
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">允许默写模式下显示提示</label>
|
||||
<div class="wrapper">
|
||||
<Switch v-model="settingStore.allowWordTip"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
开启后,可以通过鼠标 hover 单词或者按 {{ settingStore.shortcutKeyMap[ShortcutKey.ShowWord] }} 显示正确答案
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">字体设置(仅可调整单词练习)</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">外语字体</label>
|
||||
<div class="wrapper">
|
||||
<Slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordForeignFontSize"/>
|
||||
<span>{{ settingStore.fontSize.wordForeignFontSize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">中文字体</label>
|
||||
<div class="wrapper">
|
||||
<Slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordTranslateFontSize"/>
|
||||
<span>{{ settingStore.fontSize.wordTranslateFontSize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<SettingItem mainTitle="所有音效">
|
||||
<Switch v-model="settingStore.allSound" @change="useChangeAllSound"/>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">其他设置</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">切换下一个单词时间</label>
|
||||
<div class="wrapper">
|
||||
<InputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="10"
|
||||
:max="100"
|
||||
<SettingItem title="单词/句子自动发音">
|
||||
<Switch v-model="settingStore.wordSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="单词/句子发音口音">
|
||||
<Select v-model="settingStore.wordSoundType"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
>
|
||||
<Option label="美音" value="us"/>
|
||||
<Option label="英音" value="uk"/>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.wordSoundVolume"/>
|
||||
<span class="w-10 pl-5">{{ settingStore.wordSoundVolume }}%</span>
|
||||
</SettingItem>
|
||||
<SettingItem title="倍速">
|
||||
<Slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
|
||||
<span class="w-10 pl-5">{{ settingStore.wordSoundSpeed }}</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="按键音">
|
||||
<Switch v-model="settingStore.keyboardSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="按键音效">
|
||||
<Select v-model="settingStore.keyboardSoundFile"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
>
|
||||
<Option
|
||||
v-for="item in SoundFileOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div class="flex justify-between items-center w-full">
|
||||
<span>{{ item.label }}</span>
|
||||
<VolumeIcon
|
||||
:time="100"
|
||||
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
|
||||
</div>
|
||||
</Option>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.keyboardSoundVolume"/>
|
||||
<span class="w-10 pl-5">{{ settingStore.keyboardSoundVolume }}%</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="效果音(输入错误、完成时的音效)">
|
||||
<Switch v-model="settingStore.effectSound"/>
|
||||
</SettingItem>
|
||||
<SettingItem title="音量">
|
||||
<Slider v-model="settingStore.effectSoundVolume"/>
|
||||
<span class="w-10 pl-5">{{ settingStore.effectSoundVolume }}%</span>
|
||||
</SettingItem>
|
||||
</div>
|
||||
<div v-if="tabIndex === 1">
|
||||
<SettingItem title="单词循环设置" class="gap-0!">
|
||||
<RadioGroup v-model="settingStore.repeatCount">
|
||||
<Radio :value="1" size="default">1</Radio>
|
||||
<Radio :value="2" size="default">2</Radio>
|
||||
<Radio :value="3" size="default">3</Radio>
|
||||
<Radio :value="5" size="default">5</Radio>
|
||||
<Radio :value="100" size="default">自定义</Radio>
|
||||
</RadioGroup>
|
||||
<div class="ml-2 center gap-space" v-if="settingStore.repeatCount === 100">
|
||||
<span>循环次数</span>
|
||||
<InputNumber v-model="settingStore.repeatCustomCount"
|
||||
:min="6"
|
||||
:max="15"
|
||||
type="number"
|
||||
/>
|
||||
<span>毫秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="显示上一个/下一个单词"
|
||||
desc="开启后,练习中会在上方显示上一个/下一个单词"
|
||||
>
|
||||
<Switch v-model="settingStore.showNearWord"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="忽略大小写"
|
||||
desc="开启后,输入时不区分大小写,如输入“hello”和“Hello”都会被认为是正确的"
|
||||
>
|
||||
<Switch v-model="settingStore.ignoreCase"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="允许默写模式下显示提示"
|
||||
:desc="`开启后,可以通过鼠标 hover 单词或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
|
||||
>
|
||||
<Switch v-model="settingStore.allowWordTip"/>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="字体设置(仅可调整单词练习)"/>
|
||||
<SettingItem title="外语字体">
|
||||
<Slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordForeignFontSize"/>
|
||||
<span class="w-10 pl-5">{{ settingStore.fontSize.wordForeignFontSize }}px</span>
|
||||
</SettingItem>
|
||||
<SettingItem title="中文字体">
|
||||
<Slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordTranslateFontSize"/>
|
||||
<span class="w-10 pl-5">{{ settingStore.fontSize.wordTranslateFontSize }}px</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<SettingItem title="自动切换下一个单词时间"
|
||||
desc="正确输入单词后,自动跳转下一个单词的时间"
|
||||
>
|
||||
<InputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="10"
|
||||
:max="100"
|
||||
type="number"
|
||||
/>
|
||||
<span class="ml-4">毫秒</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="简单词过滤"
|
||||
desc="开启后,练习的单词中不会再出现简单词"
|
||||
>
|
||||
<Switch v-model="settingStore.ignoreSimpleWord"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="简单词列表"
|
||||
class="items-start!"
|
||||
>
|
||||
<Textarea
|
||||
placeholder="多个单词用英文逗号隔号"
|
||||
v-model="simpleWords" :autosize="{minRows: 6, maxRows: 10}"/>
|
||||
</SettingItem>
|
||||
</div>
|
||||
<div class="body" v-if="tabIndex === 2">
|
||||
<div class="row">
|
||||
@@ -425,55 +375,35 @@ function importOldData() {
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 3">
|
||||
<div class="row">
|
||||
<div class="main-title">数据导出</div>
|
||||
<div>
|
||||
目前用户的所有数据(自定义设置、自定义词典、自定义文章、学习进度等)
|
||||
<b class="text-red">仅保存在本地</b>。如果您需要在不同的设备、浏览器或者其他非官方部署上使用 {{ APP_NAME }},
|
||||
您需要手动进行数据同步和保存。
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">
|
||||
目前用户的所有数据(自定义设置、自定义词典、练习进度等)
|
||||
<b>仅保存在本地</b>
|
||||
。如果您需要在不同的设备、浏览器或者其他非官方部署上使用 {{ APP_NAME }}, 您需要手动进行数据同步和保存。
|
||||
</label>
|
||||
<BaseButton class="mt-3" @click="exportData()">导出数据</BaseButton>
|
||||
|
||||
<div class="line my-3"></div>
|
||||
|
||||
<div>请注意,导入数据后将<b class="text-red"> 完全覆盖 </b>当前所有数据(自定义设置、自定义词典、自定义文章、学习进度等),请谨慎操作。
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<BaseButton @click="exportData">数据导出</BaseButton>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="main-title">数据导入</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">
|
||||
请注意,导入数据将
|
||||
<b style="color: red"> 完全覆盖 </b>
|
||||
当前数据。请谨慎操作。
|
||||
</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="flex gap-space mt-3">
|
||||
<div class="import hvr-grow">
|
||||
<BaseButton>数据导入</BaseButton>
|
||||
<BaseButton>导入数据</BaseButton>
|
||||
<input type="file"
|
||||
accept="application/json"
|
||||
@change="importData">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="main-title">老版本数据导入</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="import hvr-grow">
|
||||
<PopConfirm
|
||||
title="导入老版本数据前,请先备份当前数据。确定要导入老版本数据吗?"
|
||||
@confirm="importOldData">
|
||||
<BaseButton>老版本数据导入</BaseButton>
|
||||
</PopConfirm>
|
||||
</div>
|
||||
<PopConfirm
|
||||
title="导入老版本数据前,请先备份当前数据,确定要导入老版本数据吗?"
|
||||
@confirm="importOldData">
|
||||
<BaseButton>老版本数据导入</BaseButton>
|
||||
</PopConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 4" class="feedback-modal">
|
||||
<div v-if="tabIndex === 4">
|
||||
<div>
|
||||
给我发Email:<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
</div>
|
||||
<p>or</p>
|
||||
<span>在<a :href="GITHUB" target="_blank"> Github </a>上给作者提一个
|
||||
<a :href="`${GITHUB}/issues`" target="_blank"> Issue </a>
|
||||
</span>
|
||||
@@ -493,7 +423,6 @@ function importOldData() {
|
||||
<div class="text-md color-gray">
|
||||
Build {{ gitLastCommitHash }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -501,7 +430,9 @@ function importOldData() {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.setting {
|
||||
@apply text-lg;
|
||||
display: flex;
|
||||
color: var(--color-font-1);
|
||||
|
||||
@@ -533,7 +464,6 @@ function importOldData() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -609,28 +539,12 @@ function importOldData() {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-bottom: .6rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.line {
|
||||
border-bottom: 1px solid #c4c3c3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-option-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.icon-wrapper {
|
||||
transform: translateX(10rem);
|
||||
}
|
||||
}
|
||||
|
||||
.import {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
@@ -642,23 +556,4 @@ function importOldData() {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.feedback-modal {
|
||||
//height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--space);
|
||||
//justify-content: center;
|
||||
color: var(--color-font-1);
|
||||
|
||||
p {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.about {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
22
src/pages/pc/setting/SettingItem.vue
Normal file
22
src/pages/pc/setting/SettingItem.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
mainTitle?: string,
|
||||
title?: string,
|
||||
desc?: string,
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-40" :class="desc ? 'mt-3' : 'my-3'" v-bind="$attrs">
|
||||
<span v-if="title">{{ title }}</span>
|
||||
<span class="text-xl font-bold" v-if="mainTitle">{{ mainTitle }}</span>
|
||||
<div class="flex flex-1 justify-end">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm mb-3" v-if="desc">{{ desc }}</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -31,9 +31,9 @@ let currentStudy = $ref({
|
||||
write: []
|
||||
})
|
||||
|
||||
//todo 当选完词返回时,计算今日任务时,还是老的词典
|
||||
onMounted(init)
|
||||
watch(() => store.load, init)
|
||||
watch(() => store.load, n => {
|
||||
if (n) init()
|
||||
}, {immediate: true})
|
||||
|
||||
async function init() {
|
||||
if (store.word.studyIndex >= 3) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import StudyWord from "@/pages/pc/word/StudyWord.vue";
|
||||
import BookDetail from "@/pages/pc/article/BookDetail.vue";
|
||||
import DictList from "@/pages/pc/word/DictList.vue";
|
||||
import BookList from "@/pages/pc/article/BookList.vue";
|
||||
import Setting from "@/pages/pc/Setting.vue";
|
||||
import Setting from "@/pages/pc/setting/Setting.vue";
|
||||
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
|
||||
@@ -77,8 +77,8 @@ export const useBaseStore = defineStore('base', {
|
||||
knownWords(): string[] {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase())
|
||||
},
|
||||
knownWordsWithSimpleWords() {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords)
|
||||
allIgnoreWords() {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
|
||||
},
|
||||
currentStudyWordDict(): Dict {
|
||||
if (this.word.studyIndex >= 0) {
|
||||
|
||||
@@ -22,7 +22,6 @@ export interface SettingState {
|
||||
repeatCustomCount?: number,
|
||||
dictation: boolean,
|
||||
translate: boolean,
|
||||
detail: boolean,
|
||||
showNearWord: boolean
|
||||
ignoreCase: boolean
|
||||
allowWordTip: boolean
|
||||
@@ -36,17 +35,14 @@ export interface SettingState {
|
||||
showPanel: boolean,
|
||||
sideExpand: boolean,
|
||||
theme: string,
|
||||
collapse: boolean,
|
||||
chapterWordNumber: number,
|
||||
shortcutKeyMap: Record<string, string>,
|
||||
first: boolean
|
||||
firstTime: number
|
||||
load: boolean
|
||||
conflictNotice: boolean // 其他脚本/插件冲突提示
|
||||
ignoreSimpleWord: boolean // 忽略简单词
|
||||
}
|
||||
|
||||
export const DefaultChapterWordNumber = 30
|
||||
|
||||
export const getDefaultSettingState = (): SettingState => ({
|
||||
showToolbar: true,
|
||||
show: false,
|
||||
@@ -67,7 +63,6 @@ export const getDefaultSettingState = (): SettingState => ({
|
||||
repeatCustomCount: null,
|
||||
dictation: false,
|
||||
translate: true,
|
||||
detail: false,
|
||||
|
||||
showNearWord: true,
|
||||
ignoreCase: true,
|
||||
@@ -80,13 +75,12 @@ export const getDefaultSettingState = (): SettingState => ({
|
||||
},
|
||||
waitTimeForChangeWord: 300,
|
||||
theme: 'auto',
|
||||
collapse: false,
|
||||
chapterWordNumber: DefaultChapterWordNumber,
|
||||
shortcutKeyMap: cloneDeep(DefaultShortcutKeyMap),
|
||||
first: true,
|
||||
firstTime: Date.now(),
|
||||
load: false,
|
||||
conflictNotice: true
|
||||
conflictNotice: true,
|
||||
ignoreSimpleWord: false
|
||||
})
|
||||
|
||||
export const useSettingStore = defineStore('setting', {
|
||||
|
||||
@@ -14,7 +14,7 @@ export const SAVE_DICT_KEY = {
|
||||
}
|
||||
export const SAVE_SETTING_KEY = {
|
||||
key: 'typing-word-setting',
|
||||
version: 9
|
||||
version: 10
|
||||
}
|
||||
export const EXPORT_DATA_KEY = {
|
||||
key: 'typing-word-export',
|
||||
|
||||
@@ -587,3 +587,30 @@ export function groupBy<T extends Record<string, any>>(array: T[], key: string)
|
||||
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]] // 交换
|
||||
}
|
||||
return copy.slice(0, n)
|
||||
}
|
||||
|
||||
//数组分成N份
|
||||
export function splitIntoN(arr: any[], n: number) {
|
||||
const result = []
|
||||
const len = arr.length
|
||||
const base = Math.floor(len / n) // 每份至少这么多
|
||||
let extra = len % n // 前几份多 1 个
|
||||
|
||||
let index = 0
|
||||
for (let i = 0; i < n; i++) {
|
||||
const size = base + (extra > 0 ? 1 : 0)
|
||||
result.push(arr.slice(index, index + size))
|
||||
index += size
|
||||
if (extra > 0) extra--
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user