impr:change the word and article Settings to pop-up boxes
This commit is contained in:
@@ -53,8 +53,13 @@ export function levelBenefits(params) {
|
||||
}
|
||||
|
||||
export function orderCreate(params) {
|
||||
return http<{ orderNo: string,result:string, }>('/member/orderCreate', params, null, 'post')
|
||||
return http<{ orderNo: string, result: string, }>('/member/orderCreate', params, null, 'post')
|
||||
}
|
||||
|
||||
export function alipayQuery(params) {
|
||||
return http('/member/alipayQuery', null, params, 'get')
|
||||
}
|
||||
|
||||
export function testPay() {
|
||||
return http('/member/testPay', null, null, 'get')
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import {defineAsyncComponent, onMounted, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import { jump2Feedback } from "@/utils";
|
||||
import {jump2Feedback} from "@/utils";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
@@ -17,41 +18,33 @@ watch(() => settingStore.load, (n) => {
|
||||
}
|
||||
}, {immediate: true})
|
||||
|
||||
useDisableEventListener(() => show)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model="show"
|
||||
title="提示"
|
||||
footer
|
||||
:closeOnClickBg="false"
|
||||
cancel-button-text="不再提醒"
|
||||
confirm-button-text="关闭"
|
||||
@cancel="settingStore.conflictNotice = false"
|
||||
<Dialog
|
||||
v-model="show"
|
||||
title="重要提示"
|
||||
footer
|
||||
:closeOnClickBg="false"
|
||||
cancel-button-text="不再提醒"
|
||||
confirm-button-text="关闭"
|
||||
@cancel="settingStore.conflictNotice = false"
|
||||
>
|
||||
<div class="card w-120 center flex-col color-main py-0 mb-0">
|
||||
<div>
|
||||
<div class="text">
|
||||
<div>
|
||||
1、 如果您安装了 <span class="font-bold text-red">“调速” “Vim”</span> 等插件/脚本,将导致本网站无法正常使用。
|
||||
</div>
|
||||
<div>
|
||||
因为它们会强行接管键盘按下事件,<span class="font-bold text-red">导致使用本网站时按 'A'、 'S' 等等按钮无反应</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-4">
|
||||
<div>①:在对应插件/脚本的设置里面排除本网站</div>
|
||||
<div>②:临时禁用对应插件/脚本</div>
|
||||
<div>③:请打开浏览器无痕模式尝试</div>
|
||||
</div>
|
||||
<div class="text mt-2">
|
||||
2、如果您未安装以上插件/脚本,还是无法使用
|
||||
</div>
|
||||
<div class="pl-4">
|
||||
<div>①:请打开浏览器无痕模式尝试</div>
|
||||
<div>②:无痕模式下无法正常使用,请给<span class="color-link mx-1 cp" @click="jump2Feedback">点此</span>给作者反馈
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-150 center flex-col color-main py-0 mb-0">
|
||||
<div class="text">
|
||||
如果您安装了 <span class="font-bold text-red">“调速” “Vim”</span> 等插件/脚本,它们会拦截键盘按下事件,<span
|
||||
class="font-bold text-red">导致在本网站练习时按 'A'、 'S' 、'D' 等键无反应</span>,您可以根据以下步骤解决冲突:
|
||||
</div>
|
||||
<ul class="m-0">
|
||||
<li>用浏览器无痕模式打开本网站,确认能否正常输入?</li>
|
||||
<li>无痕模式下无法输入,请给<span class="color-link mx-1 cp" @click="jump2Feedback">点此</span>反馈</li>
|
||||
<li>无痕模式下可以输入,则是插件/脚本导致的冲突</li>
|
||||
<li>临时禁用对应插件/脚本,或在对应插件/脚本的设置里面排除本网站</li>
|
||||
<li>可安装 <a
|
||||
href="https://chromewebstore.google.com/detail/one-click-extensions-mana/pbgjpgbpljobkekbhnnmlikbbfhbhmem" target="_blank">此插件</a> 来快速激活、禁用其他插件</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@@ -123,10 +123,9 @@ function startStudy() {
|
||||
}
|
||||
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
|
||||
s:`name:${base.sbook.name},index:${base.sbook.lastLearnIndex},title:${base.sbook.articles[base.sbook.lastLearnIndex].title}`,
|
||||
})
|
||||
nav('/practice-articles/' + store.sbook.id)
|
||||
} else {
|
||||
|
||||
@@ -63,9 +63,9 @@ async function addMyStudyList() {
|
||||
|
||||
window.umami?.track('startStudyArticle', {
|
||||
name: sbook.name,
|
||||
index: sbook.lastLearnIndex,
|
||||
custom: sbook.custom,
|
||||
complete: sbook.complete,
|
||||
s:`name:${sbook.name},index:${sbook.lastLearnIndex},title:${sbook.articles[sbook.lastLearnIndex].title}`,
|
||||
})
|
||||
nav('/practice-articles/' + sbook.id)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, onMounted, onUnmounted, provide, watch } from "vue";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import {computed, onMounted, onUnmounted, provide, watch} from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {
|
||||
Article,
|
||||
ArticleItem,
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
Statistics,
|
||||
Word
|
||||
} from "@/types/types.ts";
|
||||
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
|
||||
import {useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { _getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total } from "@/utils";
|
||||
import { usePracticeStore } from "@/stores/practice.ts";
|
||||
import { useArticleOptions } from "@/hooks/dict.ts";
|
||||
import { genArticleSectionData, usePlaySentenceAudio } from "@/hooks/article.ts";
|
||||
import { getDefaultArticle, getDefaultDict, getDefaultWord } from "@/types/func.ts";
|
||||
import {_getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total} from "@/utils";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
import {getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types/func.ts";
|
||||
import TypingArticle from "@/pages/article/components/TypingArticle.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Panel from "@/components/Panel.vue";
|
||||
@@ -30,13 +30,13 @@ import ArticleList from "@/components/list/ArticleList.vue";
|
||||
import EditSingleArticleModal from "@/pages/article/components/EditSingleArticleModal.vue";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import ConflictNotice from "@/components/ConflictNotice.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import PracticeLayout from "@/components/PracticeLayout.vue";
|
||||
import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
|
||||
import VolumeSetting from "@/pages/article/components/VolumeSetting.vue";
|
||||
import { AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
|
||||
import { addStat, setDictProp } from "@/apis";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import {AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig} from "@/config/env.ts";
|
||||
import {addStat, setDictProp} from "@/apis";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
@@ -591,14 +591,15 @@ provide('currentPractice', currentPractice)
|
||||
></ArticleAudio>
|
||||
<div class="flex flex-col items-center justify-center gap-1">
|
||||
<div class="flex gap-2 center">
|
||||
<VolumeSetting/>
|
||||
<SettingDialog type="article"/>
|
||||
|
||||
<BaseIcon
|
||||
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
@click="skip">
|
||||
<IconFluentArrowBounce20Regular class="transform-rotate-180"/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
:title="`重听(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
:title="`播放当前句子(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
@click="play">
|
||||
<IconFluentReplay20Regular/>
|
||||
</BaseIcon>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import MiniDialog from "@/components/dialog/MiniDialog.vue";
|
||||
import { useWindowClick } from "@/hooks/event.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Switch from "@/components/base/Switch.vue";
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
let show = $ref(false)
|
||||
useWindowClick(() => show = false)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative"
|
||||
@click.stop="null"
|
||||
>
|
||||
<BaseIcon
|
||||
title="播放设置"
|
||||
@click="show = !show">
|
||||
<IconFluentSpeakerSettings20Regular/>
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
width="12rem"
|
||||
v-model="show">
|
||||
<div class="mini-row-title">
|
||||
播放设置
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<label class="">自动播放句子</label>
|
||||
<Switch v-model="settingStore.articleSound"/>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<label class="">自动播放下一篇</label>
|
||||
<Switch v-model="settingStore.articleAutoPlayNext"/>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -39,7 +39,7 @@ const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
}>()
|
||||
|
||||
const tabIndex = $ref(0)
|
||||
const tabIndex = $ref(3)
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const store = useBaseStore()
|
||||
@@ -296,18 +296,6 @@ function transferOk() {
|
||||
<div class="flex flex-1 overflow-hidden gap-4">
|
||||
<div class="left">
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
|
||||
<IconFluentSettings20Regular width="20"/>
|
||||
<span>通用练习设置</span>
|
||||
</div>
|
||||
<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 === 3 && 'active'" @click="tabIndex = 3">
|
||||
<IconFluentKeyboardLayoutFloat20Regular width="20"/>
|
||||
<span>快捷键设置</span>
|
||||
@@ -333,224 +321,6 @@ function transferOk() {
|
||||
</div>
|
||||
<div class="col-line"></div>
|
||||
<div class="flex-1 overflow-y-auto overflow-x-hidden pr-4 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 class="body" v-if="tabIndex === 3">
|
||||
<div class="row">
|
||||
@@ -615,6 +385,14 @@ function transferOk() {
|
||||
|
||||
<!-- 日志-->
|
||||
<div v-if="tabIndex === 5">
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/12/3</div>
|
||||
<div>内容:单词、文章设置修改为弹框,更方便</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
@@ -829,9 +607,6 @@ function transferOk() {
|
||||
<p>
|
||||
GitHub地址:<a :href="GITHUB" target="_blank">{{ GITHUB }}</a>
|
||||
</p>
|
||||
<p>
|
||||
反馈:<a :href="`${GITHUB}/issues`" target="_blank">{{ GITHUB }}/issues</a>
|
||||
</p>
|
||||
<p>
|
||||
作者邮箱:<a :href="`mailto:${EMAIL}`">{{ EMAIL }}</a>
|
||||
</p>
|
||||
@@ -879,17 +654,18 @@ function transferOk() {
|
||||
@apply cursor-pointer flex items-center relative;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: .5rem;
|
||||
width: 10rem;
|
||||
gap: .6rem;
|
||||
transition: all .5s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
import {FormInstance} from "@/components/base/form/types.ts";
|
||||
import {codeRules, emailRules, passwordRules, phoneRules} from "@/utils/validation.ts";
|
||||
import {_dateFormat, cloneDeep} from "@/utils";
|
||||
import {_dateFormat, cloneDeep, jump2Feedback} from "@/utils";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import Code from "@/pages/user/Code.vue";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
@@ -37,10 +37,6 @@ const contactSupport = () => {
|
||||
console.log('Contact support')
|
||||
}
|
||||
|
||||
const goIssues = () => {
|
||||
window.open(GITHUB + '/issues', '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
userStore.fetchUserInfo()
|
||||
})
|
||||
@@ -541,7 +537,7 @@ function onFileChange(e) {
|
||||
|
||||
<!-- 去github issue-->
|
||||
<div class="item cp"
|
||||
@click="goIssues">
|
||||
@click="jump2Feedback()">
|
||||
<div class="flex-1">
|
||||
给 {{ APP_NAME }} 提交意见
|
||||
</div>
|
||||
@@ -554,7 +550,7 @@ function onFileChange(e) {
|
||||
<BaseButton
|
||||
@click="handleLogout"
|
||||
size="large"
|
||||
class="w-[80%]"
|
||||
class="w-[40%]"
|
||||
>
|
||||
登出
|
||||
</BaseButton>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {User} from "@/apis/user.ts";
|
||||
import {computed, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import Header from "@/components/Header.vue";
|
||||
import {
|
||||
alipayQuery,
|
||||
CouponInfo,
|
||||
couponInfo,
|
||||
LevelBenefits,
|
||||
@@ -202,6 +203,8 @@ let orderNo = $ref('')
|
||||
let timer: number = $ref()
|
||||
let showCouponInput = $ref(false)
|
||||
let coupon = $ref<CouponInfo>({code: ''} as CouponInfo)
|
||||
let checkLoading = $ref(false)
|
||||
let showCheckBtn = $ref(false)
|
||||
|
||||
watch(() => startLoop, (n) => {
|
||||
if (n) {
|
||||
@@ -221,8 +224,12 @@ watch(() => startLoop, (n) => {
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
setTimeout(() => {
|
||||
showCheckBtn = true
|
||||
}, 3000)
|
||||
} else {
|
||||
clearInterval(timer)
|
||||
showCheckBtn = false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -269,6 +276,16 @@ async function handlePayment() {
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function checkOrderStatus() {
|
||||
if (checkLoading) return
|
||||
checkLoading = true
|
||||
let res = await alipayQuery({orderNo})
|
||||
if (!res.success) {
|
||||
Toast.info(res.msg || '未付款')
|
||||
}
|
||||
checkLoading = false
|
||||
}
|
||||
|
||||
let couponLoading = $ref(false)
|
||||
|
||||
async function getCouponInfo() {
|
||||
@@ -302,7 +319,7 @@ async function getCouponInfo() {
|
||||
<div class="card-white">
|
||||
<Header title="会员介绍"></Header>
|
||||
<div class="grid grid-cols-3 grid-rows-3 gap-3">
|
||||
<div class="text-lg items-center" v-for="f in data.benefits" :key="f.name">
|
||||
<div class="text-lg flex items-center" v-for="f in data.benefits" :key="f.name">
|
||||
<IconFluentCheckmarkCircle20Regular class="mr-2 text-green-600"/>
|
||||
<span>
|
||||
<span>{{ f.name }}</span>
|
||||
@@ -371,7 +388,7 @@ async function getCouponInfo() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pay" class="mb-50">
|
||||
<div id="pay" class="mb-50" v-if="selectedPlanId">
|
||||
<!-- Page Header -->
|
||||
<div class="text-center mb-6">
|
||||
<h1 class="text-xl font-semibold mb-2">安全支付</h1>
|
||||
@@ -509,9 +526,15 @@ async function getCouponInfo() {
|
||||
</div>
|
||||
|
||||
<iframe id="payFrame" class="w-[205px] h-[205px] center border-none"></iframe>
|
||||
<div class="text-center mt-4">
|
||||
<div class="text-center my-4">
|
||||
请使用支付宝扫码支付
|
||||
</div>
|
||||
<BaseButton size="large"
|
||||
v-if="showCheckBtn"
|
||||
:loading="checkLoading"
|
||||
@click="checkOrderStatus">
|
||||
我已付款
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -295,7 +295,7 @@ enum ImportStep {
|
||||
}
|
||||
|
||||
const {exportData} = useExport()
|
||||
let importStep = $ref<ImportStep>(ImportStep.SUCCESS)
|
||||
let importStep = $ref<ImportStep>(ImportStep.CONFIRMATION)
|
||||
let isImporting = $ref(false)
|
||||
let reason = $ref('')
|
||||
let timer = $ref(-1)
|
||||
|
||||
@@ -1,509 +1 @@
|
||||
<script setup lang="ts">
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { useRouter } from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {
|
||||
_getAccomplishDate,
|
||||
_getDictDataByUrl,
|
||||
_nextTick,
|
||||
isMobile,
|
||||
loadJsLib,
|
||||
resourceWrap,
|
||||
shuffle,
|
||||
useNav
|
||||
} from "@/utils";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import { DictResource, WordPracticeMode } from "@/types/types.ts";
|
||||
import { watch } from "vue";
|
||||
import { getCurrentStudyWord } from "@/hooks/dict.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import Book from "@/components/Book.vue";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Progress from '@/components/base/Progress.vue';
|
||||
import Toast from '@/components/base/toast/Toast.ts';
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { getDefaultDict } from "@/types/func.ts";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
|
||||
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { useFetch } from "@vueuse/core";
|
||||
import { AppEnv, DICT_LIST, Host, LIB_JS_URL, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
|
||||
import { myDictList } from "@/apis";
|
||||
import PracticeWordListDialog from "@/pages/word/components/PracticeWordListDialog.vue";
|
||||
import ShufflePracticeSettingDialog from "@/pages/word/components/ShufflePracticeSettingDialog.vue";
|
||||
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const router = useRouter()
|
||||
const {nav} = useNav()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let loading = $ref(true)
|
||||
let isSaveData = $ref(false)
|
||||
|
||||
let currentStudy = $ref({
|
||||
new: [],
|
||||
review: [],
|
||||
write: [],
|
||||
shuffle: [],
|
||||
})
|
||||
|
||||
watch(() => store.load, n => {
|
||||
if (n) {
|
||||
init()
|
||||
_nextTick(async () => {
|
||||
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
|
||||
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() {
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await myDictList({type: "word"})
|
||||
if (res.success) {
|
||||
store.setState(Object.assign(store.$state, res.data))
|
||||
}
|
||||
}
|
||||
if (store.word.studyIndex >= 3) {
|
||||
if (!store.sdict.custom && !store.sdict.words.length) {
|
||||
store.word.bookList[store.word.studyIndex] = await _getDictDataByUrl(store.sdict)
|
||||
}
|
||||
}
|
||||
if (!currentStudy.new.length && store.sdict.words.length) {
|
||||
let d = localStorage.getItem(PracticeSaveWordKey.key)
|
||||
if (d) {
|
||||
try {
|
||||
let obj = JSON.parse(d)
|
||||
currentStudy = obj.val.taskWords
|
||||
isSaveData = true
|
||||
} catch (e) {
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
} else {
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
function startPractice() {
|
||||
if (store.sdict.id) {
|
||||
if (!store.sdict.words.length) {
|
||||
return Toast.warning('没有单词可学习!')
|
||||
}
|
||||
window.umami?.track('startStudyWord', {
|
||||
name: store.sdict.name,
|
||||
index: store.sdict.lastLearnIndex,
|
||||
perDayStudyNumber: store.sdict.perDayStudyNumber,
|
||||
custom: store.sdict.custom,
|
||||
complete: store.sdict.complete,
|
||||
wordPracticeMode: settingStore.wordPracticeMode
|
||||
})
|
||||
//把是否是第一次设置为false
|
||||
settingStore.first = false
|
||||
nav('practice-words/' + store.sdict.id, {}, {taskWords: currentStudy})
|
||||
} else {
|
||||
window.umami?.track('no-dict')
|
||||
Toast.warning('请先选择一本词典')
|
||||
}
|
||||
}
|
||||
|
||||
let showPracticeSettingDialog = $ref(false)
|
||||
let showShufflePracticeSettingDialog = $ref(false)
|
||||
let showChangeLastPracticeIndexDialog = $ref(false)
|
||||
let showPracticeWordListDialog = $ref(false)
|
||||
|
||||
async function goDictDetail(val: DictResource) {
|
||||
if (!val.id) return nav('dict-list')
|
||||
runtimeStore.editDict = getDefaultDict(val)
|
||||
nav('dict-detail', {})
|
||||
}
|
||||
|
||||
let isManageDict = $ref(false)
|
||||
let selectIds = $ref([])
|
||||
|
||||
function handleBatchDel() {
|
||||
selectIds.forEach(id => {
|
||||
let r = store.word.bookList.findIndex(v => v.id === id)
|
||||
if (r !== -1) {
|
||||
if (store.word.studyIndex === r) {
|
||||
store.word.studyIndex = -1
|
||||
}
|
||||
if (store.word.studyIndex > r) {
|
||||
store.word.studyIndex--
|
||||
}
|
||||
store.word.bookList.splice(r, 1)
|
||||
}
|
||||
})
|
||||
selectIds = []
|
||||
Toast.success("删除成功!")
|
||||
}
|
||||
|
||||
function toggleSelect(item) {
|
||||
let rIndex = selectIds.findIndex(v => v === item.id)
|
||||
if (rIndex > -1) {
|
||||
selectIds.splice(rIndex, 1)
|
||||
} else {
|
||||
selectIds.push(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
function check(cb: Function) {
|
||||
if (!store.sdict.id) {
|
||||
Toast.warning('请先选择一本词典')
|
||||
} else {
|
||||
runtimeStore.editDict = getDefaultDict(store.sdict)
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
async function savePracticeSetting() {
|
||||
Toast.success('修改成功')
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
await store.changeDict(runtimeStore.editDict)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
async function onShufflePracticeSettingOk(total) {
|
||||
window.umami?.track('startShuffleStudyWord', {
|
||||
name: store.sdict.name,
|
||||
index: store.sdict.lastLearnIndex,
|
||||
perDayStudyNumber: store.sdict.perDayStudyNumber,
|
||||
total,
|
||||
custom: store.sdict.custom,
|
||||
complete: store.sdict.complete,
|
||||
})
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
|
||||
currentStudy.shuffle = shuffle(store.sdict.words.slice(0, store.sdict.lastLearnIndex).filter(v => !ignoreList.includes(v.word))).slice(0, total)
|
||||
nav('practice-words/' + store.sdict.id, {}, {
|
||||
taskWords: currentStudy,
|
||||
total //用于再来一组时,随机出正确的长度,因为练习中可能会点击已掌握,导致重学一遍之后长度变少,如果再来一组,此时长度就不正确
|
||||
})
|
||||
}
|
||||
|
||||
async function saveLastPracticeIndex(e) {
|
||||
Toast.success('修改成功')
|
||||
runtimeStore.editDict.lastLearnIndex = e
|
||||
showChangeLastPracticeIndexDialog = false
|
||||
isSaveData = false
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
await store.changeDict(runtimeStore.editDict)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
const {
|
||||
data: recommendDictList,
|
||||
isFetching
|
||||
} = useFetch(resourceWrap(DICT_LIST.WORD.RECOMMENDED)).json()
|
||||
|
||||
let isNewHost = $ref(window.location.host === Host)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="mb-4" v-if="!isNewHost">
|
||||
新域名已启用,后续请访问 <a href="https://typewords.cc/words?from_old_site=1">https://typewords.cc</a>。当前
|
||||
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">
|
||||
<div class="p-1 center rounded-full bg-white">
|
||||
<IconFluentBookNumber20Filled class="text-xl color-link"/>
|
||||
</div>
|
||||
<div
|
||||
@click="goDictDetail(store.sdict)"
|
||||
class="text-2xl font-bold cursor-pointer">
|
||||
{{ store.sdict.name || '当前无正在学习的词典' }}
|
||||
</div>
|
||||
</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="text-sm flex justify-between">
|
||||
<span>已完成 {{ progressTextRight }} 词 / 共 {{ store.sdict.words.length }} 词</span>
|
||||
<span v-if="store.sdict.id">
|
||||
预计完成日期:{{ _getAccomplishDate(store.sdict.words.length, store.sdict.perDayStudyNumber) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-4 gap-4">
|
||||
<BaseButton type="info"
|
||||
size="small"
|
||||
@click="router.push('/dict-list')">
|
||||
<div class="center gap-1">
|
||||
<IconFluentArrowSwap20Regular/>
|
||||
<span>选择词典</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
<PopConfirm
|
||||
:disabled="!isSaveData"
|
||||
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
|
||||
@confirm="check(()=>showChangeLastPracticeIndexDialog = true)">
|
||||
<BaseButton type="info"
|
||||
size="small"
|
||||
v-if="store.sdict.id"
|
||||
>
|
||||
<div class="center gap-1">
|
||||
<IconFluentSlideTextTitleEdit20Regular/>
|
||||
<span>更改进度</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</PopConfirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex items-center gap-4 mt-2 flex-1" v-else>
|
||||
<div class="title">请选择一本词典开始学习</div>
|
||||
<BaseButton id="step1" type="primary" size="large" @click="router.push('/dict-list')">
|
||||
<div class="center gap-1">
|
||||
<IconFluentAdd16Regular/>
|
||||
<span>选择词典</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 w-full mt-4 md:mt-0" :class="!store.sdict.id && 'opacity-30 cursor-not-allowed'">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="p-2 center rounded-full bg-white ">
|
||||
<IconFluentStar20Filled class="text-lg color-amber"/>
|
||||
</div>
|
||||
<div class="text-xl font-bold">
|
||||
{{ isSaveData ? '上次任务' : '今日任务' }}
|
||||
</div>
|
||||
<span class="color-link cursor-pointer"
|
||||
v-if="store.sdict.id"
|
||||
@click="showPracticeWordListDialog = true">词表</span>
|
||||
|
||||
</div>
|
||||
<div class="flex gap-1 items-center"
|
||||
v-if="store.sdict.id"
|
||||
>
|
||||
每日目标
|
||||
<div style="color:#ac6ed1;"
|
||||
class="bg-third px-2 h-10 flex center text-2xl rounded">
|
||||
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
|
||||
</div>
|
||||
个单词
|
||||
<PopConfirm
|
||||
:disabled="!isSaveData"
|
||||
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
|
||||
@confirm="check(()=>showPracticeSettingDialog = true)">
|
||||
<BaseButton
|
||||
type="info" size="small">更改
|
||||
</BaseButton>
|
||||
</PopConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-4 justify-between">
|
||||
<div class="stat">
|
||||
<div class="num">{{ currentStudy.new.length }}</div>
|
||||
<div class="txt">新词数</div>
|
||||
</div>
|
||||
<template v-if="settingStore.wordPracticeMode === WordPracticeMode.System">
|
||||
<div class="stat">
|
||||
<div class="num">{{ currentStudy.review.length }}</div>
|
||||
<div class="txt">复习上次</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="num">{{ currentStudy.write.length }}</div>
|
||||
<div class="txt">复习之前</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-end mt-4">
|
||||
<BaseButton size="large"
|
||||
class="flex-1"
|
||||
:disabled="!store.sdict.id"
|
||||
:loading="loading"
|
||||
@click="startPractice">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
|
||||
<div
|
||||
v-if="false"
|
||||
class="w-full flex box-border cp color-white">
|
||||
<div
|
||||
@click="startPractice"
|
||||
class="flex-1 rounded-l-lg center gap-2 py-1 bg-[var(--btn-primary)] hover:opacity-50">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl"/>
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="w-10 rounded-r-lg h-full center bg-[var(--btn-primary)] hover:bg-gray border-solid border-2 border-l-gray border-transparent box-border">
|
||||
<IconFluentChevronDown20Regular/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-2 pt-2 absolute z-2 right-0 border rounded opacity-0 scale-95
|
||||
group-hover:opacity-100 group-hover:scale-100
|
||||
transition-all duration-150 pointer-events-none group-hover:pointer-events-auto"
|
||||
>
|
||||
<div>
|
||||
<BaseButton
|
||||
size="large" type="orange"
|
||||
:loading="loading"
|
||||
@click="check(()=>showShufflePracticeSettingDialog = true)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">随机复习</span>
|
||||
<IconFluentArrowShuffle20Filled class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div>
|
||||
<BaseButton
|
||||
size="large" type="orange"
|
||||
:loading="loading"
|
||||
@click="check(()=>showShufflePracticeSettingDialog = true)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">重新学习</span>
|
||||
<IconFluentArrowShuffle20Filled class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-if="store.sdict.id && store.sdict.lastLearnIndex"
|
||||
size="large" type="orange"
|
||||
:loading="loading"
|
||||
@click="check(()=>showShufflePracticeSettingDialog = true)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">随机复习</span>
|
||||
<IconFluentArrowShuffle20Filled class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col">
|
||||
<div class="flex justify-between">
|
||||
<div class="title">我的词典</div>
|
||||
<div class="flex gap-4 items-center">
|
||||
<PopConfirm title="确认删除所有选中词典?" @confirm="handleBatchDel" v-if="selectIds.length">
|
||||
<BaseIcon class="del" title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
|
||||
<div class="color-link cursor-pointer" v-if="store.word.bookList.length > 3"
|
||||
@click="isManageDict = !isManageDict; selectIds = []">{{ isManageDict ? '取消' : '管理词典' }}
|
||||
</div>
|
||||
<div class="color-link cursor-pointer" @click="nav('dict-detail', { isAdd: true })">创建个人词典</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4 flex-wrap mt-4">
|
||||
<Book :is-add="false" quantifier="个词" :item="item" :checked="selectIds.includes(item.id)"
|
||||
@check="() => toggleSelect(item)" :show-checkbox="isManageDict && j >= 3"
|
||||
v-for="(item, j) in store.word.bookList" @click="goDictDetail(item)"/>
|
||||
<Book :is-add="true" @click="router.push('/dict-list')"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col overflow-hidden" v-loading="isFetching">
|
||||
<div class="flex justify-between">
|
||||
<div class="title">推荐</div>
|
||||
<div class="flex gap-4 items-center">
|
||||
<div class="color-link cursor-pointer" @click="router.push('/dict-list')">更多</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 flex-wrap mt-4 min-h-50">
|
||||
<Book :is-add="false"
|
||||
quantifier="个词"
|
||||
:item="item as any"
|
||||
v-for="(item, j) in recommendDictList" @click="goDictDetail(item as any)"/>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
|
||||
<PracticeSettingDialog
|
||||
:show-left-option="false"
|
||||
v-model="showPracticeSettingDialog"
|
||||
@ok="savePracticeSetting"/>
|
||||
|
||||
<ChangeLastPracticeIndexDialog
|
||||
v-model="showChangeLastPracticeIndexDialog"
|
||||
@ok="saveLastPracticeIndex"
|
||||
/>
|
||||
|
||||
<PracticeWordListDialog
|
||||
:data="currentStudy"
|
||||
v-model="showPracticeWordListDialog"
|
||||
/>
|
||||
|
||||
<ShufflePracticeSettingDialog
|
||||
v-model="showShufflePracticeSettingDialog"
|
||||
@ok="onShufflePracticeSettingOk"/>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.stat {
|
||||
@apply w-31% box-border flex flex-col items-center justify-center rounded-xl p-2 bg-[var(--bg-history)];
|
||||
border: 1px solid gainsboro;
|
||||
|
||||
.num {
|
||||
@apply color-[#409eff] text-4xl font-bold;
|
||||
}
|
||||
|
||||
.txt {
|
||||
@apply color-gray-500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
WordsPage.vue
|
||||
@@ -7,6 +7,7 @@ import { PracticeData, WordPracticeType, ShortcutKey, TaskWords } from "@/types/
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import Progress from '@/components/base/Progress.vue'
|
||||
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
|
||||
|
||||
const statStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -123,6 +124,8 @@ const progress = $computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-center items-center" id="toolbar-icons">
|
||||
<SettingDialog type="word"/>
|
||||
|
||||
<BaseIcon
|
||||
v-if="statStore.step < 9"
|
||||
@click="emit('skipStep')"
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<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 {ShortcutKey} 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";
|
||||
@@ -14,12 +13,19 @@ 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";
|
||||
import {defineAsyncComponent} from "vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
|
||||
const tabIndex = $ref(1)
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
const props = defineProps<{
|
||||
type: 'article' | 'word'
|
||||
}>()
|
||||
|
||||
const tabIndex = $ref(props.type === 'word' ? 1 : 2)
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const store = useBaseStore()
|
||||
let show = $ref(false)
|
||||
|
||||
const simpleWords = $computed({
|
||||
get: () => store.simpleWords.join(','),
|
||||
@@ -35,23 +41,22 @@ const simpleWords = $computed({
|
||||
</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>
|
||||
<Dialog v-model="show" title="设置">
|
||||
<div class="setting text-lg w-200 h-[50vh] text-md flex flex-col">
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<div class="left">
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1" v-if="type === 'word'">
|
||||
<IconFluentTextUnderlineDouble20Regular width="20"/>
|
||||
<span>单词练习设置</span>
|
||||
<span>单词</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
|
||||
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2" v-if="type === 'article'">
|
||||
<IconFluentBookLetter20Regular width="20"/>
|
||||
<span>文章练习设置</span>
|
||||
<span>文章</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
|
||||
<IconFluentSettings20Regular width="20"/>
|
||||
<span>通用练习设置</span>
|
||||
<span>通用</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,7 +98,9 @@ const simpleWords = $computed({
|
||||
<!-- 音效-->
|
||||
<div class="line"></div>
|
||||
<SettingItem main-title="音效"/>
|
||||
<SettingItem title="单词/句子发音口音">
|
||||
<SettingItem title="单词/句子发音口音"
|
||||
desc="仅单词生效,文章固定美音"
|
||||
>
|
||||
<Select v-model="settingStore.soundType"
|
||||
placeholder="请选择"
|
||||
class="w-50!"
|
||||
@@ -138,12 +145,12 @@ const simpleWords = $computed({
|
||||
<!-- 单词练习设置-->
|
||||
<!-- 单词练习设置-->
|
||||
<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="练习模式">-->
|
||||
<!-- <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="开启后,练习中会在上方显示上一个/下一个单词"
|
||||
@@ -277,7 +284,10 @@ const simpleWords = $computed({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</Dialog>
|
||||
<BaseIcon title="设置" @click="show = true;tabIndex = props.type === 'word' ? 1 : 2">
|
||||
<IconFluentSettings20Regular/>
|
||||
</BaseIcon>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -289,10 +299,10 @@ const simpleWords = $computed({
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-right: 2px solid gainsboro;
|
||||
border-right: 1px solid gainsboro;
|
||||
|
||||
.tabs {
|
||||
padding: .6rem 1.6rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem;
|
||||
@@ -302,17 +312,18 @@ const simpleWords = $computed({
|
||||
@apply cursor-pointer flex items-center relative;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: .5rem;
|
||||
width: 10rem;
|
||||
gap: .6rem;
|
||||
transition: all .5s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-select-bg);
|
||||
color: var(--color-select-text);
|
||||
background: var(--btn-primary);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user