This commit is contained in:
Zyronon
2025-11-28 18:50:05 +08:00
committed by GitHub
parent 8e8048f249
commit eb2ac5a555
10 changed files with 470 additions and 238 deletions

View File

@@ -9,6 +9,7 @@ const props = defineProps<{
disabled?: boolean;
showText?: boolean;
showValue?: boolean; // 是否显示当前值
unit?: string
}>();
const emit = defineEmits(['update:modelValue']);
@@ -134,20 +135,21 @@ onMounted(() => {
</script>
<template>
<div class="w-full">
<div
<div class="w-full flex">
<div class="flex-1">
<div
ref="sliderRef"
class="custom-slider"
:class="{ 'is-disabled': disabled }"
@mousedown="onClickTrack"
@touchstart.prevent="onClickTrack"
>
<div class="custom-slider__track"></div>
<div
>
<div class="custom-slider__track"></div>
<div
class="custom-slider__fill"
:style="{ width: valueToPercent(currentValue) + '%' }"
></div>
<div
></div>
<div
class="custom-slider__thumb"
:style="{ left: valueToPercent(currentValue) + '%' }"
@mousedown.stop.prevent="onMouseDown"
@@ -158,13 +160,14 @@ onMounted(() => {
:aria-valuemax="max"
:aria-valuenow="currentValue"
:aria-disabled="disabled"
></div>
<div v-if="showValue" class="custom-slider__value">{{ currentValue }}</div>
</div>
<div class="text flex justify-between text-sm color-gray" v-if="showText">
<span>{{ min }}</span>
<span>{{ max }}</span>
></div>
</div>
<div class="text flex justify-between text-sm color-gray" v-if="showText">
<span>{{ min }}</span>
<span>{{ max }}</span>
</div>
</div>
<div v-if="showValue" class="w-10 pl-5 ">{{ currentValue }}{{ unit}}</div>
</div>
</template>
@@ -222,15 +225,5 @@ onMounted(() => {
box-shadow: 0 0 5px #409eff;
cursor: grabbing;
}
&__value {
position: absolute;
top: 100%;
left: 50%;
transform: translate(-50%, 4px);
font-size: 0.75rem;
color: #666;
user-select: none;
}
}
</style>

View File

@@ -27,59 +27,64 @@ export function useExport() {
async function exportData(notice = '导出成功!') {
if (loading.value) return
loading.value = true
const JSZip = await loadJsLib('JSZip', `${Origin}/libs/jszip.min.js`);
let data = {
version: EXPORT_DATA_KEY.version,
val: {
setting: {
version: SAVE_SETTING_KEY.version,
val: settingStore.$state
},
dict: {
version: SAVE_DICT_KEY.version,
val: shakeCommonDict(store.$state)
},
[PracticeSaveWordKey.key]: {
version: PracticeSaveWordKey.version,
val: {}
},
[PracticeSaveArticleKey.key]: {
version: PracticeSaveArticleKey.version,
val: {}
},
[APP_VERSION.key]: -1
try {
const JSZip = await loadJsLib('JSZip', `${Origin}/libs/jszip.min.js`);
let data = {
version: EXPORT_DATA_KEY.version,
val: {
setting: {
version: SAVE_SETTING_KEY.version,
val: settingStore.$state
},
dict: {
version: SAVE_DICT_KEY.version,
val: shakeCommonDict(store.$state)
},
[PracticeSaveWordKey.key]: {
version: PracticeSaveWordKey.version,
val: {}
},
[PracticeSaveArticleKey.key]: {
version: PracticeSaveArticleKey.version,
val: {}
},
[APP_VERSION.key]: -1
}
}
}
let d = localStorage.getItem(PracticeSaveWordKey.key)
if (d) {
try {
data.val[PracticeSaveWordKey.key] = JSON.parse(d)
} catch (e) {
let d = localStorage.getItem(PracticeSaveWordKey.key)
if (d) {
try {
data.val[PracticeSaveWordKey.key] = JSON.parse(d)
} catch (e) {
}
}
}
let d1 = localStorage.getItem(PracticeSaveArticleKey.key)
if (d1) {
try {
data.val[PracticeSaveArticleKey.key] = JSON.parse(d1)
} catch (e) {
let d1 = localStorage.getItem(PracticeSaveArticleKey.key)
if (d1) {
try {
data.val[PracticeSaveArticleKey.key] = JSON.parse(d1)
} catch (e) {
}
}
}
let r = await get(APP_VERSION.key)
data.val[APP_VERSION.key] = r
let r = await get(APP_VERSION.key)
data.val[APP_VERSION.key] = r
const zip = new JSZip();
zip.file("data.json", JSON.stringify(data));
const zip = new JSZip();
zip.file("data.json", JSON.stringify(data));
const mp3 = zip.folder("mp3");
const allRecords = await get(LOCAL_FILE_KEY);
for (const rec of allRecords ?? []) {
mp3.file(rec.id + ".mp3", rec.file);
const mp3 = zip.folder("mp3");
const allRecords = await get(LOCAL_FILE_KEY);
for (const rec of allRecords ?? []) {
mp3.file(rec.id + ".mp3", rec.file);
}
let content = await zip.generateAsync({type: "blob"})
saveAs(content, `${APP_NAME}-User-Data-${dayjs().format('YYYY-MM-DD HH-mm-ss')}.zip`);
notice && Toast.success(notice)
return content
} catch (e) {
Toast.error(e?.message || e || '导出失败')
} finally {
loading.value = false
}
let content = await zip.generateAsync({type: "blob"})
saveAs(content, `${APP_NAME}-User-Data-${dayjs().format('YYYY-MM-DD HH-mm-ss')}.zip`);
notice && Toast.success(notice)
loading.value = false
return content
}
return {

View File

@@ -1,27 +1,27 @@
<script setup lang="ts">
import { useBaseStore } from "@/stores/base.ts";
import { useRouter } from "vue-router";
import {useBaseStore} from "@/stores/base.ts";
import {useRouter} from "vue-router";
import BasePage from "@/components/BasePage.vue";
import {_getDictDataByUrl, _nextTick, isMobile, msToHourMinute, resourceWrap, total, useNav} from "@/utils";
import { DictResource, DictType } from "@/types/types.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import {DictResource, DictType} from "@/types/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Book from "@/components/Book.vue";
import Progress from '@/components/base/Progress.vue';
import Toast from '@/components/base/toast/Toast.ts'
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import { watch } from "vue";
import { getDefaultDict } from "@/types/func.ts";
import {watch} from "vue";
import {getDefaultDict} from "@/types/func.ts";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import isoWeek from 'dayjs/plugin/isoWeek'
import { useFetch } from "@vueuse/core";
import { AppEnv, DICT_LIST, Host, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
import { myDictList } from "@/apis";
import {useFetch} from "@vueuse/core";
import {AppEnv, DICT_LIST, Host, PracticeSaveArticleKey, TourConfig} from "@/config/env.ts";
import {myDictList} from "@/apis";
import Shepherd from "shepherd.js";
import { useSettingStore } from "@/stores/setting.ts";
import {useSettingStore} from "@/stores/setting.ts";
dayjs.extend(isoWeek)
dayjs.extend(isBetween);
@@ -57,9 +57,9 @@ async function init() {
let data = obj.val
//如果全是0说明未进行练习直接重置
if (
data.practiceData.sectionIndex === 0 &&
data.practiceData.sentenceIndex === 0 &&
data.practiceData.wordIndex === 0
data.practiceData.sectionIndex === 0 &&
data.practiceData.sentenceIndex === 0 &&
data.practiceData.wordIndex === 0
) {
throw new Error()
}
@@ -112,6 +112,13 @@ function startStudy() {
if (!base.sbook.articles.length) {
return Toast.warning('没有文章可学习!')
}
window.umami?.track('startStudyArticle', {
name: base.sbook.name,
index: base.sbook.lastLearnIndex,
custom: base.sbook.custom,
complete: base.sbook.complete,
title: base.sbook.articles[base.sbook.lastLearnIndex].title
})
nav('/practice-articles/' + store.sbook.id)
} else {
window.umami?.track('no-book')
@@ -213,12 +220,12 @@ let isNewHost = $ref(window.location.host === Host)
<div class="card flex flex-col md:flex-row justify-between gap-space p-4 md:p-6">
<div class="">
<Book
v-if="base.sbook.id"
:is-add="false"
quantifier="篇"
:item="base.sbook"
:show-progress="false"
@click="goBookDetail(base.sbook)"/>
v-if="base.sbook.id"
:is-add="false"
quantifier="篇"
:item="base.sbook"
:show-progress="false"
@click="goBookDetail(base.sbook)"/>
<Book v-else
:is-add="true"
@click="router.push('/book-list')"/>
@@ -228,27 +235,27 @@ let isNewHost = $ref(window.location.host === Host)
<div class="title mr-4 truncate">本周学习记录</div>
<div class="flex gap-4 color-gray">
<div
class="w-6 h-6 md:w-8 md:h-8 rounded-md center text-sm md:text-base"
:class="item ? 'bg-[#409eff] color-white' : 'bg-gray-200'"
v-for="(item, i) in weekList"
:key="i"
class="w-6 h-6 md:w-8 md:h-8 rounded-md center text-sm md:text-base"
:class="item ? 'bg-[#409eff] color-white' : 'bg-gray-200'"
v-for="(item, i) in weekList"
:key="i"
>{{ i + 1 }}
</div>
</div>
</div>
<div class="flex flex-col sm:flex-row gap-4 items-center mt-3 gap-space w-full">
<div
class="w-full sm:flex-1 rounded-xl p-4 box-border relative bg-[var(--bg-history)] border border-gray-200">
class="w-full sm:flex-1 rounded-xl p-4 box-border relative bg-[var(--bg-history)] border border-gray-200">
<div class="text-[#409eff] text-xl font-bold">{{ todayTotalSpend }}</div>
<div class="text-gray-500">今日学习时长</div>
</div>
<div
class="w-full sm:flex-1 rounded-xl p-4 box-border relative bg-[var(--bg-history)] border border-gray-200">
class="w-full sm:flex-1 rounded-xl p-4 box-border relative bg-[var(--bg-history)] border border-gray-200">
<div class="text-[#409eff] text-xl font-bold">{{ totalDay }}</div>
<div class="text-gray-500">总学习天数</div>
</div>
<div
class="w-full sm:flex-1 rounded-xl p-4 box-border relative bg-[var(--bg-history)] border border-gray-200">
class="w-full sm:flex-1 rounded-xl p-4 box-border relative bg-[var(--bg-history)] border border-gray-200">
<div class="text-[#409eff] text-xl font-bold">{{ totalSpend }}</div>
<div class="text-gray-500">总学习时长</div>
</div>

View File

@@ -295,20 +295,8 @@ function setArticle(val: Article) {
}, 1000)
_nextTick(typingArticleRef?.init)
window.umami?.track('startStudyArticle', {
name: store.sbook.name,
index: store.sbook.lastLearnIndex,
custom: store.sbook.custom,
complete: store.sbook.complete,
title: articleData.article.title,
})
}
watch(() => articleData.article.id, n => {
console.log('articleData.article.id', n)
})
async function complete() {
clearInterval(timer)
setTimeout(() => {

View File

@@ -47,7 +47,7 @@ function goHome() {
</div>
<div class="row" @click="jump2Feedback">
<IconFluentCommentEdit20Regular/>
<span v-if="settingStore.sideExpand">建议反馈</span>
<span v-if="settingStore.sideExpand">反馈</span>
</div>
<!-- <div class="row" @click="router.push('/user')">-->
<!-- <IconFluentPerson20Regular/>-->

View File

@@ -269,7 +269,7 @@ async function importData(e) {
Toast.success("导入成功!");
} catch (e) {
Toast.error("导入失败");
Toast.error(e?.message || e || '导入失败')
} finally {
importLoading = false
}
@@ -278,29 +278,6 @@ async function importData(e) {
}
}
function importOldData() {
exportData('已为您自动保存当前数据!稍后将进行老数据导入操作')
setTimeout(() => {
let oldDataStr = localStorage.getItem('type-word-dict-v3')
if (oldDataStr) {
try {
let obj = JSON.parse(oldDataStr)
let data = {
version: 3,
val: obj
}
let baseState = checkAndUpgradeSaveDict(data)
store.setState(baseState)
Toast.success('导入成功')
} catch (err) {
Toast.error('导入失败')
}
} else {
Toast.error('导入失败!原因:本地无老数据备份')
}
}, 1000)
}
let isNewHost = $ref(window.location.host === Host)
let showTransfer = $ref(false)
@@ -630,11 +607,6 @@ function transferOk() {
accept="application/json,.zip,application/zip"
@change="importData">
</div>
<PopConfirm
title="导入老版本数据前,请先备份当前数据,确定要导入老版本数据吗?"
@confirm="importOldData">
<BaseButton>老版本数据导入</BaseButton>
</PopConfirm>
</div>
<template v-if="isNewHost">
@@ -652,7 +624,7 @@ function transferOk() {
<div class="mb-2">
<div>
<div>日期2025/11/28</div>
<div>内容新增引导框新增词典测试模式大佬 hebeihang 开发</div>
<div>内容新增引导框 新增<a href="https://github.com/zyronon/TypeWords/pull/175" target="_blank">词典测试模式大佬 hebeihang 开发</a></div>
</div>
</div>
</div>
@@ -856,74 +828,6 @@ function transferOk() {
margin-bottom: 1rem;
}
// 用户信息样式
.user-info-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
border: 1px solid var(--color-input-border);
border-radius: 8px;
background: var(--color-bg);
width: 100%;
max-width: 400px;
.user-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
border: 3px solid var(--color-select-bg);
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 2rem;
font-weight: bold;
}
}
h3 {
margin: 0;
color: var(--color-font-1);
}
.text-sm {
font-size: 0.9rem;
margin: 0.25rem 0;
}
.color-gray {
color: #666;
}
.mb-1 {
margin-bottom: 0.25rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mt-4 {
margin-top: 1rem;
}
}
.setting {
@apply text-lg;

View File

@@ -33,7 +33,7 @@ defineProps<{
.setting-item__main {
display: flex;
align-items: center;
gap: 2.5rem;
gap: 2rem;
width: 100%;
}

View File

@@ -24,6 +24,7 @@ import { myDictList } from "@/apis";
import PracticeWordListDialog from "@/pages/word/components/PracticeWordListDialog.vue";
import ShufflePracticeSettingDialog from "@/pages/word/components/ShufflePracticeSettingDialog.vue";
import Shepherd from "shepherd.js";
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
const store = useBaseStore()
@@ -42,7 +43,34 @@ let currentStudy = $ref({
})
watch(() => store.load, n => {
if (n) init()
if (n) {
init()
_nextTick(() => {
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');
});
tour.addStep({
id: 'step1',
text: '点击这里选择一本词典开始学习',
attachTo: {
element: '#step1',
on: 'bottom'
},
buttons: [
{
text: `下一步1/${TourConfig.total}`,
action() {
tour.next()
router.push('/dict-list')
}
}
]
});
const r = localStorage.getItem('tour-guide');
if (settingStore.first && !r && !isMobile()) tour.start();
}, 500)
}
}, {immediate: true})
async function init() {
@@ -200,33 +228,6 @@ const {
let isNewHost = $ref(window.location.host === Host)
onMounted(() => {
_nextTick(() => {
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');
});
tour.addStep({
id: 'step1',
text: '点击这里选择一本词典开始学习',
attachTo: {
element: '#step1',
on: 'bottom'
},
buttons: [
{
text: `下一步1/${TourConfig.total}`,
action() {
tour.next()
router.push('/dict-list')
}
}
]
});
const r = localStorage.getItem('tour-guide');
if (settingStore.first && !r && !isMobile()) tour.start();
}, 500)
})
</script>
<template>
@@ -236,6 +237,8 @@ onMounted(() => {
2study.top 域名将在不久后停止使用
</div>
<SettingDialog/>
<div class="card flex flex-col md:flex-row gap-8">
<div class="flex-1 w-full flex flex-col justify-between">
<div class="flex gap-3">

View File

@@ -0,0 +1,332 @@
<script setup lang="ts">
import {useSettingStore} from "@/stores/setting.ts";
import {getAudioFileUrl, usePlayAudio} from "@/hooks/sound.ts";
import {ShortcutKey, WordPracticeMode} from "@/types/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {SoundFileOptions} from "@/config/env.ts";
import BasePage from "@/components/BasePage.vue";
import {Option, Select} from "@/components/base/select";
import Switch from "@/components/base/Switch.vue";
import Slider from "@/components/base/Slider.vue";
import RadioGroup from "@/components/base/radio/RadioGroup.vue";
import Radio from "@/components/base/radio/Radio.vue";
import InputNumber from "@/components/base/InputNumber.vue";
import Textarea from "@/components/base/Textarea.vue";
import SettingItem from "@/pages/setting/SettingItem.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
const tabIndex = $ref(1)
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const store = useBaseStore()
const simpleWords = $computed({
get: () => store.simpleWords.join(','),
set: v => {
try {
store.simpleWords = v.split(',');
} catch (e) {
}
}
})
</script>
<template>
<BasePage>
<div class="setting text-lg w-200 h-200 bg-white text-md flex flex-col">
<div class="page-title text-align-center">设置</div>
<div class="flex flex-1 overflow-hidden">
<div class="left">
<div class="tabs">
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
<IconFluentTextUnderlineDouble20Regular width="20"/>
<span>单词练习设置</span>
</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
<IconFluentBookLetter20Regular width="20"/>
<span>文章练习设置</span>
</div>
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
<IconFluentSettings20Regular width="20"/>
<span>通用练习设置</span>
</div>
</div>
</div>
<div class="content">
<!-- 通用练习设置-->
<!-- 通用练习设置-->
<!-- 通用练习设置-->
<div v-if="tabIndex === 0">
<SettingItem title="忽略大小写"
desc="开启后输入时不区分大小写如输入“hello”和“Hello”都会被认为是正确的"
>
<Switch v-model="settingStore.ignoreCase"/>
</SettingItem>
<SettingItem title="允许默写模式下显示提示"
:desc="`开启后,可以通过将鼠标移动到单词上或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
>
<Switch v-model="settingStore.allowWordTip"/>
</SettingItem>
<div class="line"></div>
<SettingItem title="简单词过滤"
desc="开启后,练习的单词中不会包含简单词;文章统计的总词数中不会包含简单词"
>
<Switch v-model="settingStore.ignoreSimpleWord"/>
</SettingItem>
<SettingItem title="简单词列表"
class="items-start!"
v-if="settingStore.ignoreSimpleWord"
>
<Textarea
placeholder="多个单词用英文逗号隔号"
v-model="simpleWords" :autosize="{minRows: 6, maxRows: 10}"/>
</SettingItem>
<!-- 音效-->
<!-- 音效-->
<!-- 音效-->
<div class="line"></div>
<SettingItem main-title="音效"/>
<SettingItem title="单词/句子发音口音">
<Select v-model="settingStore.soundType"
placeholder="请选择"
class="w-50!"
>
<Option label="美音" value="us"/>
<Option label="英音" value="uk"/>
</Select>
</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" showText showValue unit="%"/>
</SettingItem>
</div>
<!-- 单词练习设置-->
<!-- 单词练习设置-->
<!-- 单词练习设置-->
<div v-if="tabIndex === 1">
<SettingItem title="练习模式">
<RadioGroup v-model="settingStore.wordPracticeMode" class="flex-col gap-0!">
<Radio :value="WordPracticeMode.System" label="智能模式:自动规划学习、复习、听写、默写"/>
<Radio :value="WordPracticeMode.Free" label="自由模式:系统不强制复习与默写"/>
</RadioGroup>
</SettingItem>
<SettingItem title="显示上一个/下一个单词"
desc="开启后,练习中会在上方显示上一个/下一个单词"
>
<Switch v-model="settingStore.showNearWord"/>
</SettingItem>
<SettingItem title="不默认显示练习设置弹框"
desc="在词典详情页面,点击学习按钮后,是否显示练习设置弹框"
>
<Switch v-model="settingStore.disableShowPracticeSettingDialog"/>
</SettingItem>
<SettingItem title="输入错误时,清空已输入内容"
>
<Switch v-model="settingStore.inputWrongClear"/>
</SettingItem>
<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"
/>
</div>
</SettingItem>
<!-- 发音-->
<!-- 发音-->
<!-- 发音-->
<div class="line"></div>
<SettingItem mainTitle="音效"/>
<SettingItem title="单词自动发音">
<Switch v-model="settingStore.wordSound"/>
</SettingItem>
<SettingItem title="音量">
<Slider v-model="settingStore.wordSoundVolume" showText showValue unit="%"/>
</SettingItem>
<SettingItem title="倍速">
<Slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3" showText showValue/>
</SettingItem>
<div class="line"></div>
<SettingItem title="效果音(输入错误、完成时的音效)">
<Switch v-model="settingStore.effectSound"/>
</SettingItem>
<SettingItem title="音量">
<Slider v-model="settingStore.effectSoundVolume" showText showValue unit="%"/>
</SettingItem>
<!-- 自动切换-->
<!-- 自动切换-->
<!-- 自动切换-->
<div class="line"></div>
<SettingItem mainTitle="自动切换"/>
<SettingItem title="自动切换下一个单词"
desc="仅在 **跟写** 时生效,听写、辨认、默写均不会自动切换,需要手动按 **空格键** 切换"
>
<Switch v-model="settingStore.autoNextWord"/>
</SettingItem>
<SettingItem title="自动切换下一个单词时间"
desc="正确输入单词后,自动跳转下一个单词的时间"
>
<InputNumber v-model="settingStore.waitTimeForChangeWord"
:disabled="!settingStore.autoNextWord"
:min="0"
:max="10000"
:step="100"
type="number"
/>
<span class="ml-4">毫秒</span>
</SettingItem>
<!-- 字体设置-->
<!-- 字体设置-->
<!-- 字体设置-->
<div class="line"></div>
<SettingItem mainTitle="字体设置"/>
<SettingItem title="外语字体">
<Slider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordForeignFontSize" showText showValue unit="px"/>
</SettingItem>
<SettingItem title="中文字体">
<Slider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordTranslateFontSize" showText showValue unit="px"/>
</SettingItem>
</div>
<!-- 文章练习设置-->
<!-- 文章练习设置-->
<!-- 文章练习设置-->
<div v-if="tabIndex === 2">
<!-- 发音-->
<!-- 发音-->
<!-- 发音-->
<SettingItem mainTitle="音效"/>
<SettingItem title="自动播放句子">
<Switch v-model="settingStore.articleSound"/>
</SettingItem>
<SettingItem title="自动播放下一篇">
<Switch v-model="settingStore.articleAutoPlayNext"/>
</SettingItem>
<SettingItem title="音量">
<Slider v-model="settingStore.articleSoundVolume" showText showValue unit="%"/>
</SettingItem>
<SettingItem title="倍速">
<Slider v-model="settingStore.articleSoundSpeed" :step="0.1" :min="0.5" :max="3" showText showValue/>
</SettingItem>
<div class="line"></div>
<SettingItem title="输入时忽略符号/数字/人名">
<Switch v-model="settingStore.ignoreSymbol"/>
</SettingItem>
</div>
</div>
</div>
</div>
</BasePage>
</template>
<style scoped lang="scss">
.setting {
.left {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
border-right: 2px solid gainsboro;
.tabs {
padding: .6rem 1.6rem;
display: flex;
flex-direction: column;
gap: .6rem;
//color: #0C8CE9;
.tab {
@apply cursor-pointer flex items-center relative;
padding: .6rem .9rem;
border-radius: .5rem;
gap: .6rem;
transition: all .5s;
&:hover {
background: var(--color-select-bg);
color: var(--color-select-text);
}
&.active {
background: var(--color-select-bg);
color: var(--color-select-text);
}
}
}
}
.content {
flex: 1;
height: 100%;
overflow: auto;
padding: 0 1.6rem;
.line {
border-bottom: 1px solid #c4c3c3;
}
}
}
</style>

View File

@@ -426,7 +426,7 @@ export async function loadJsLib(key: string, url: string) {
const script = document.createElement("script");
script.src = url;
script.onload = () => resolve(window[key]);
script.onerror = reject;
script.onerror = () => reject(key + ' 加载失败')
document.head.appendChild(script);
});
}
@@ -460,6 +460,6 @@ export async function isNewUser() {
return JSON.stringify(base.$state) === JSON.stringify({...getDefaultBaseState(), ...{load: true}})
}
export function jump2Feedback(){
export function jump2Feedback() {
window.open('https://v.wjx.cn/vm/ev0W7fv.aspx#', '_blank');
}