wip
This commit is contained in:
139
index.html
139
index.html
@@ -2,9 +2,6 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<!-- 百度站长HTML标签验证 -->
|
||||
<meta name="baidu-site-verification" content="codeva-NoSMtV313P" />
|
||||
|
||||
<title>Type Words 官网 - 词文记 | 单词跟打 · 文章跟打</title>
|
||||
<!-- 搜索引擎描述 -->
|
||||
<meta name="description"
|
||||
@@ -51,12 +48,49 @@
|
||||
<!-- color-scheme 告诉浏览器支持亮/暗模式-->
|
||||
<meta name="color-scheme" content="light dark"/>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-50T6DRD837"></script>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
if (!location.href.includes('localhost')
|
||||
&& !location.href.includes('192.168')
|
||||
&& !location.href.includes('172.16')
|
||||
&& !location.href.includes('10.0')
|
||||
) {
|
||||
//https://51.la/
|
||||
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"3OH8ITYRgwzo58L2",ck:"3OH8ITYRgwzo58L2",autoTrack:true,hashMode:true});
|
||||
|
||||
// Cloudflare
|
||||
(function () {
|
||||
var cf = document.createElement("script");
|
||||
cf.src = 'https://static.cloudflareinsights.com/beacon.min.js'
|
||||
cf.setAttribute("data-cf-beacon", '{"token": "e5119992696d4155814400dd69781d68"}');
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(cf, s);
|
||||
})();
|
||||
|
||||
// google
|
||||
(function () {
|
||||
var ana = document.createElement("script");
|
||||
ana.src = 'https://www.googletagmanager.com/gtag/js?id=G-50T6DRD837'
|
||||
ana.onload = function () {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-50T6DRD837');
|
||||
}
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(ana, s);
|
||||
})();
|
||||
|
||||
|
||||
// baidu
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
@@ -65,6 +99,16 @@
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
|
||||
// umami
|
||||
(function () {
|
||||
var umami = document.createElement("script");
|
||||
umami.src = 'https://typewords.cc/libs/s.js'
|
||||
umami.setAttribute("data-website-id", "160308c9-7900-4b1d-a0b1-c3b25a9530f6");
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(umami, s);
|
||||
})();
|
||||
|
||||
// umami-saas
|
||||
(function () {
|
||||
var umami2 = document.createElement("script");
|
||||
umami2.src = 'https://stat.typewords.cc/script.js'
|
||||
@@ -82,45 +126,56 @@
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
(function(){
|
||||
var ua = navigator.userAgent || ''
|
||||
var isIE = !!document.documentMode || /MSIE|Trident/i.test(ua)
|
||||
if (!isIE) return
|
||||
var style = document.createElement('style')
|
||||
style.type = 'text/css'
|
||||
style.appendChild(document.createTextNode(
|
||||
'.ie-mask{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:9998}'+
|
||||
'.ie-dialog{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:28rem;max-width:90vw;background:#fff;color:#111;border-radius:.6rem;box-shadow:0 10px 30px rgba(0,0,0,.15);z-index:9999;padding:1.2rem}'+
|
||||
'.ie-dialog .title{font-size:1.2rem;font-weight:700;margin-bottom:.6rem}'+
|
||||
'.ie-dialog .desc{font-size:.95rem;line-height:1.6;color:#555}'+
|
||||
'.ie-dialog .actions{display:flex;justify-content:flex-end;margin-top:1rem}'+
|
||||
'.ie-dialog .actions > * + *{margin-left:.6rem}'+
|
||||
'.ie-dialog .btn{display:inline-flex;align-items:center;justify-content:center;height:2.2rem;padding:0 1rem;border-radius:.4rem;background:#0C8CE9;color:#fff;text-decoration:none}'+
|
||||
'.ie-dialog .btn-secondary{display:inline-flex;align-items:center;justify-content:center;height:2.2rem;padding:0 .9rem;border-radius:.4rem;background:#eee;color:#333;border:1px solid #ddd}'+
|
||||
'@media (prefers-color-scheme: dark){.ie-dialog{background:#1e1f22;color:#e6e6e6}.ie-dialog .desc{color:#c6c6c6}.ie-dialog .btn-secondary{background:#2a2b2f;color:#e6e6e6;border-color:#3a3b3f}}'
|
||||
))
|
||||
document.head.appendChild(style)
|
||||
var mask = document.createElement('div')
|
||||
mask.className = 'ie-mask'
|
||||
var dialog = document.createElement('div')
|
||||
dialog.className = 'ie-dialog'
|
||||
dialog.innerHTML = '<div class="title">不支持 IE 浏览器</div>'+
|
||||
'<div class="desc">Type Words 使用现代技术构建,请使用 Chrome、Edge、Firefox 或 Safari 等现代浏览器访问。</div>'+
|
||||
'<div class="actions">'+
|
||||
'<a class="btn" href="https://www.google.cn/chrome/" target="_blank" rel="noreferrer">下载 Chrome</a>'+
|
||||
'<button class="btn-secondary" type="button">我知道了</button>'+
|
||||
'</div>'
|
||||
function close(){
|
||||
try{document.body.removeChild(mask)}catch(e){}
|
||||
try{document.body.removeChild(dialog)}catch(e){}
|
||||
}
|
||||
mask.addEventListener('click', close)
|
||||
var btn = null
|
||||
try{btn = dialog.querySelector('.btn-secondary')}catch(e){}
|
||||
if (btn) btn.addEventListener('click', close)
|
||||
document.body.appendChild(mask)
|
||||
document.body.appendChild(dialog)
|
||||
})()
|
||||
(function () {
|
||||
var ua = navigator.userAgent || ''
|
||||
var isIE = !!document.documentMode || /MSIE|Trident/i.test(ua)
|
||||
if (!isIE) return
|
||||
var style = document.createElement('style')
|
||||
style.type = 'text/css'
|
||||
style.appendChild(document.createTextNode(
|
||||
'.ie-mask{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:9998}' +
|
||||
'.ie-dialog{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:28rem;max-width:90vw;background:#fff;color:#111;border-radius:.6rem;box-shadow:0 10px 30px rgba(0,0,0,.15);z-index:9999;padding:1.2rem}' +
|
||||
'.ie-dialog .title{font-size:1.2rem;font-weight:700;margin-bottom:.6rem}' +
|
||||
'.ie-dialog .desc{font-size:.95rem;line-height:1.6;color:#555}' +
|
||||
'.ie-dialog .actions{display:flex;justify-content:flex-end;margin-top:1rem}' +
|
||||
'.ie-dialog .actions > * + *{margin-left:.6rem}' +
|
||||
'.ie-dialog .btn{display:inline-flex;align-items:center;justify-content:center;height:2.2rem;padding:0 1rem;border-radius:.4rem;background:#0C8CE9;color:#fff;text-decoration:none}' +
|
||||
'.ie-dialog .btn-secondary{display:inline-flex;align-items:center;justify-content:center;height:2.2rem;padding:0 .9rem;border-radius:.4rem;background:#eee;color:#333;border:1px solid #ddd}' +
|
||||
'@media (prefers-color-scheme: dark){.ie-dialog{background:#1e1f22;color:#e6e6e6}.ie-dialog .desc{color:#c6c6c6}.ie-dialog .btn-secondary{background:#2a2b2f;color:#e6e6e6;border-color:#3a3b3f}}'
|
||||
))
|
||||
document.head.appendChild(style)
|
||||
var mask = document.createElement('div')
|
||||
mask.className = 'ie-mask'
|
||||
var dialog = document.createElement('div')
|
||||
dialog.className = 'ie-dialog'
|
||||
dialog.innerHTML = '<div class="title">不支持 IE 浏览器</div>' +
|
||||
'<div class="desc">Type Words 使用现代技术构建,请使用 Chrome、Edge、Firefox 或 Safari 等现代浏览器访问。</div>' +
|
||||
'<div class="actions">' +
|
||||
'<a class="btn" href="https://www.google.cn/chrome/" target="_blank" rel="noreferrer">下载 Chrome</a>' +
|
||||
'<button class="btn-secondary" type="button">我知道了</button>' +
|
||||
'</div>'
|
||||
|
||||
function close() {
|
||||
try {
|
||||
document.body.removeChild(mask)
|
||||
} catch (e) {
|
||||
}
|
||||
try {
|
||||
document.body.removeChild(dialog)
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
mask.addEventListener('click', close)
|
||||
var btn = null
|
||||
try {
|
||||
btn = dialog.querySelector('.btn-secondary')
|
||||
} catch (e) {
|
||||
}
|
||||
if (btn) btn.addEventListener('click', close)
|
||||
document.body.appendChild(mask)
|
||||
document.body.appendChild(dialog)
|
||||
})()
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -461,7 +461,7 @@ a {
|
||||
.book {
|
||||
@extend .anim;
|
||||
@apply p-3 rounded-md relative cursor-pointer bg-third hover:bg-card-active flex flex-col justify-between shrink-0;
|
||||
$w: 6rem;
|
||||
$w: 7rem;
|
||||
width: $w;
|
||||
height: calc($w * 1.4);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,11 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const progress = $computed(() => {
|
||||
if (props.item?.complete) return 100
|
||||
return Number(((props.item?.lastLearnIndex / props.item?.length) * 100).toFixed())
|
||||
})
|
||||
|
||||
const studyProgress = $computed(() => {
|
||||
if (!props.showProgress) return
|
||||
if (props.item.complete) return props.item?.length + '/'
|
||||
return props.item?.lastLearnIndex ? props.item?.lastLearnIndex + '/' : ''
|
||||
})
|
||||
</script>
|
||||
@@ -41,14 +39,14 @@ const studyProgress = $computed(() => {
|
||||
<div>{{ studyProgress }}{{ item?.length }}{{ quantifier }}</div>
|
||||
</div>
|
||||
<div class="absolute bottom-2 left-3 right-3">
|
||||
<Progress v-if="(item?.lastLearnIndex || item.complete) && showProgress" class="mt-1"
|
||||
<Progress v-if="(item?.lastLearnIndex) && showProgress" class="mt-1"
|
||||
:percentage="progress"
|
||||
:show-text="false"></Progress>
|
||||
</div>
|
||||
<Checkbox v-if="showCheckbox"
|
||||
:model-value="checked"
|
||||
@change="$emit('check')"
|
||||
class="absolute left-3 bottom-3"/>
|
||||
class="absolute left-3 bottom-3 z-2"/>
|
||||
<div class="custom z-1" v-if="item.custom">自定义</div>
|
||||
<div class="custom bg-red! color-white z-1" v-else-if="item.update">更新中</div>
|
||||
</div>
|
||||
|
||||
@@ -189,7 +189,7 @@ const sentence = $computed(() => {
|
||||
|
||||
<div class="bg-gray-900/30 py-4 center flex-col rounded-2xl">
|
||||
<div class="text-center mb-2 text-xl">
|
||||
我在 {{ APP_NAME }} 学习了 {{ studyStats.time }}
|
||||
我学习了{{ studyStats.time }} {{ baseStore.sdict.name }}
|
||||
</div>
|
||||
<!-- Progress Overview -->
|
||||
<div class="w-90/100 flex items-center gap-space">
|
||||
|
||||
@@ -216,7 +216,7 @@ const simpleWords = $computed({
|
||||
<div class="line"></div>
|
||||
<SettingItem mainTitle="自动切换"/>
|
||||
<SettingItem title="自动切换下一个单词"
|
||||
desc="仅在 **跟写** 时生效,听写、辨认、默写均不会自动切换,需要手动按 **空格键** 切换"
|
||||
desc="仅在 **跟写** 时生效,听写、自测、默写均不会自动切换,需要手动按 **空格键** 切换"
|
||||
>
|
||||
<Switch v-model="settingStore.autoNextWord"/>
|
||||
</SettingItem>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { loadJsLib, shakeCommonDict } from "@/utils";
|
||||
import {loadJsLib, shakeCommonDict} from "@/utils";
|
||||
import {
|
||||
APP_NAME,
|
||||
APP_VERSION,
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
SAVE_DICT_KEY,
|
||||
SAVE_SETTING_KEY
|
||||
} from "@/config/env.ts";
|
||||
import { get } from "idb-keyval";
|
||||
import { saveAs } from "file-saver";
|
||||
import {get} from "idb-keyval";
|
||||
import {saveAs} from "file-saver";
|
||||
import dayjs from "dayjs";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { ref } from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {ref} from "vue";
|
||||
|
||||
export function useExport() {
|
||||
const store = useBaseStore()
|
||||
@@ -24,7 +24,7 @@ export function useExport() {
|
||||
|
||||
let loading = ref(false)
|
||||
|
||||
async function exportData(notice = '导出成功!') {
|
||||
async function exportData(notice = '导出成功!', fileName = `${APP_NAME}-User-Data-${dayjs().format('YYYY-MM-DD HH-mm-ss')}.zip`) {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -77,7 +77,7 @@ export function useExport() {
|
||||
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`);
|
||||
saveAs(content, fileName);
|
||||
notice && Toast.success(notice)
|
||||
return content
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref, watch } from "vue";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { getAudioFileUrl, usePlayAudio } from "@/hooks/sound.ts";
|
||||
import { getShortcutKey, useEventListener } from "@/hooks/event.ts";
|
||||
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, loadJsLib, shakeCommonDict } from "@/utils";
|
||||
import { DefaultShortcutKeyMap, ShortcutKey, WordPracticeMode } from "@/types/types.ts";
|
||||
import {nextTick, onMounted, ref, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {getAudioFileUrl, usePlayAudio} from "@/hooks/sound.ts";
|
||||
import {getShortcutKey, useEventListener} from "@/hooks/event.ts";
|
||||
import {
|
||||
checkAndUpgradeSaveDict,
|
||||
checkAndUpgradeSaveSetting,
|
||||
cloneDeep,
|
||||
loadJsLib,
|
||||
shakeCommonDict,
|
||||
sleep
|
||||
} from "@/utils";
|
||||
import {DefaultShortcutKeyMap, ShortcutKey, WordPracticeMode} from "@/types/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { saveAs } from "file-saver";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {saveAs} from "file-saver";
|
||||
import {
|
||||
APP_NAME, APP_VERSION, EMAIL,
|
||||
EXPORT_DATA_KEY, GITHUB, Host,
|
||||
@@ -20,7 +27,7 @@ import {
|
||||
import dayjs from "dayjs";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { Option, Select } from "@/components/base/select";
|
||||
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";
|
||||
@@ -29,10 +36,10 @@ import InputNumber from "@/components/base/InputNumber.vue";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Textarea from "@/components/base/Textarea.vue";
|
||||
import SettingItem from "@/pages/setting/SettingItem.vue";
|
||||
import { get, set } from "idb-keyval";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import { useUserStore } from "@/stores/user.ts";
|
||||
import { useExport } from "@/hooks/export.ts";
|
||||
import {get, set} from "idb-keyval";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useUserStore} from "@/stores/user.ts";
|
||||
import {useExport} from "@/hooks/export.ts";
|
||||
import MigrateDialog from "@/components/MigrateDialog.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -99,7 +106,7 @@ useEventListener('keydown', (e: KeyboardEvent) => {
|
||||
} else {
|
||||
// 忽略单独的修饰键
|
||||
if (shortcutKey === 'Ctrl+' || shortcutKey === 'Alt+' || shortcutKey === 'Shift+' ||
|
||||
e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift') {
|
||||
e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,6 +181,7 @@ let importLoading = $ref(false)
|
||||
const {loading: exportLoading, exportData} = useExport()
|
||||
|
||||
function importJson(str: string, notice: boolean = true) {
|
||||
importLoading = true
|
||||
let obj = {
|
||||
version: -1,
|
||||
val: {
|
||||
@@ -223,10 +231,15 @@ function importJson(str: string, notice: boolean = true) {
|
||||
notice && Toast.success('导入成功!')
|
||||
} catch (err) {
|
||||
return Toast.error('导入失败!')
|
||||
}finally {
|
||||
importLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function importData(e) {
|
||||
importLoading = true
|
||||
await exportData('已自动备份数据', 'TypeWords数据备份.zip')
|
||||
await sleep(1500)
|
||||
let file = e.target.files[0]
|
||||
if (!file) return
|
||||
if (file.name.endsWith(".json")) {
|
||||
@@ -240,7 +253,6 @@ async function importData(e) {
|
||||
reader.readAsText(file);
|
||||
} else if (file.name.endsWith(".zip")) {
|
||||
try {
|
||||
importLoading = true
|
||||
const JSZip = await loadJsLib('JSZip', `${Origin}/libs/jszip.min.js`);
|
||||
const zip = await JSZip.loadAsync(file);
|
||||
|
||||
@@ -276,6 +288,7 @@ async function importData(e) {
|
||||
} else {
|
||||
Toast.error("不支持的文件类型");
|
||||
}
|
||||
importLoading = false
|
||||
}
|
||||
|
||||
let isNewHost = $ref(window.location.host === Host)
|
||||
@@ -335,7 +348,7 @@ function transferOk() {
|
||||
<input ref="shortcutInput" :value="item[1]?item[1]:'未设置快捷键'" readonly type="text"
|
||||
@blur="handleInputBlur">
|
||||
<span @click.stop="editShortcutKey = ''">按键盘进行设置,<span
|
||||
class="text-red!">设置完成点击这里</span></span>
|
||||
class="text-red!">设置完成点击这里</span></span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="item[1]">{{ item[1] }}</div>
|
||||
@@ -362,7 +375,7 @@ function transferOk() {
|
||||
|
||||
<div class="line my-3"></div>
|
||||
|
||||
<div>请注意,导入数据后将<b class="text-red"> 完全覆盖 </b>当前所有数据,请谨慎操作。
|
||||
<div>请注意,导入数据后将<b class="text-red"> 完全覆盖 </b>当前所有数据,请谨慎操作。执行导入操作时,会先自动备份当前数据到您的电脑中,供您随时恢复
|
||||
</div>
|
||||
<div class="flex gap-space mt-3">
|
||||
<div class="import hvr-grow">
|
||||
@@ -462,7 +475,7 @@ function transferOk() {
|
||||
<div class="mb-2">
|
||||
<div>
|
||||
<div>日期:2025/11/16</div>
|
||||
<div>内容:辨认单词时,不认识单词可以直接输入,自动标识为错误单词,无需按2</div>
|
||||
<div>内容:自测单词时,不认识单词可以直接输入,自动标识为错误单词,无需按2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -518,7 +531,7 @@ function transferOk() {
|
||||
<ol>
|
||||
<li>
|
||||
<div class="title"><b>智能模式优化</b></div>
|
||||
<div class="desc">练习时新增四种练习模式:学习、辨认、听写、默写。</div>
|
||||
<div class="desc">练习时新增四种练习模式:学习、自测、听写、默写。</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>学习模式</b></div>
|
||||
@@ -531,7 +544,7 @@ function transferOk() {
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title"><b>辨认模式(新增)</b></div>
|
||||
<div class="title"><b>自测模式(新增)</b></div>
|
||||
<div class="desc">
|
||||
<ul>
|
||||
<li>仅在复习已学单词时出现。</li>
|
||||
@@ -620,8 +633,8 @@ function transferOk() {
|
||||
</BasePage>
|
||||
|
||||
<MigrateDialog
|
||||
v-model="showTransfer"
|
||||
@ok="transferOk"
|
||||
v-model="showTransfer"
|
||||
@ok="transferOk"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -359,9 +359,9 @@ async function next(isTyping: boolean = true) {
|
||||
return goNextStep(shuffle(taskWords.write), WordPracticeType.Listen, '开始听写之前')
|
||||
}
|
||||
|
||||
//开始辨认之前
|
||||
//开始自测之前
|
||||
if (statStore.step === 5) {
|
||||
return goNextStep(taskWords.write, WordPracticeType.Identify, '开始辨认之前')
|
||||
return goNextStep(taskWords.write, WordPracticeType.Identify, '开始自测之前')
|
||||
}
|
||||
|
||||
//开始默写上次
|
||||
@@ -374,9 +374,9 @@ async function next(isTyping: boolean = true) {
|
||||
return goNextStep(shuffle(taskWords.review), WordPracticeType.Listen, '开始听写上次')
|
||||
}
|
||||
|
||||
//开始辨认昨日
|
||||
//开始自测昨日
|
||||
if (statStore.step === 2) {
|
||||
return goNextStep(taskWords.review, WordPracticeType.Identify, '开始辨认昨日')
|
||||
return goNextStep(taskWords.review, WordPracticeType.Identify, '开始自测昨日')
|
||||
}
|
||||
|
||||
//开始默写新词
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {
|
||||
_getAccomplishDate,
|
||||
_getDictDataByUrl,
|
||||
_getDictDataByUrl, _getStudyProgress,
|
||||
_nextTick,
|
||||
isMobile,
|
||||
loadJsLib,
|
||||
@@ -446,9 +446,14 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
</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="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>
|
||||
|
||||
@@ -51,7 +51,7 @@ function getStepStr(step: number) {
|
||||
str += `默写新词`
|
||||
break
|
||||
case 3:
|
||||
str += `辨认上次学习`
|
||||
str += `自测上次学习`
|
||||
break
|
||||
case 4:
|
||||
str += '听写上次学习'
|
||||
@@ -60,7 +60,7 @@ function getStepStr(step: number) {
|
||||
str += '默写上次学习'
|
||||
break
|
||||
case 6:
|
||||
str += '辨认之前学习'
|
||||
str += '自测之前学习'
|
||||
break
|
||||
case 7:
|
||||
str += '听写之前学习'
|
||||
@@ -96,6 +96,11 @@ const progress = $computed(() => {
|
||||
</Tooltip>
|
||||
|
||||
<div class="bottom">
|
||||
<Progress :percentage="progress"
|
||||
:stroke-width="8"
|
||||
color="#69b1ff"
|
||||
:show-text="false"/>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
@@ -246,7 +251,7 @@ const progress = $computed(() => {
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
top: -60%;
|
||||
top: -40%;
|
||||
left: 50%;
|
||||
cursor: pointer;
|
||||
transition: all .5s;
|
||||
@@ -255,7 +260,7 @@ const progress = $computed(() => {
|
||||
font-size: 1.2rem;
|
||||
|
||||
&.down {
|
||||
top: -120%;
|
||||
top: -90%;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { _getAccomplishDays } from "@/utils";
|
||||
import {_getAccomplishDays} from "@/utils";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
|
||||
import Slider from "@/components/base/Slider.vue";
|
||||
import { defineAsyncComponent, watch } from "vue";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import {defineAsyncComponent, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
@@ -56,9 +57,11 @@ watch(() => model.value, (n) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model="model" title="学习设置" :footer="true"
|
||||
<Dialog
|
||||
v-model="model"
|
||||
title="学习设置" :footer="true"
|
||||
@ok="changePerDayStudyNumber">
|
||||
<div class="target-modal color-main" id="mode">
|
||||
<div class="target-modal color-main" id="mode">
|
||||
<div class="center">
|
||||
<div class="flex gap-4 text-center h-30 w-85">
|
||||
<div class="mode-item" :class="temPracticeMode == 0 && 'active'" @click=" temPracticeMode = 0">
|
||||
@@ -71,15 +74,22 @@ watch(() => model.value, (n) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-2 mb-8">
|
||||
<span>从第<span class="text-3xl mx-2 lh">{{ tempLastLearnIndex }}</span>个开始,</span>
|
||||
<span>每日<span class="text-3xl mx-2 lh">{{ tempPerDayStudyNumber }}</span>个,</span>
|
||||
<div class="text-center mt-4 mb-8 flex gap-1 items-end justify-center">
|
||||
<span>从第</span>
|
||||
<div class="w-18">
|
||||
<BaseInput v-model="tempLastLearnIndex"/>
|
||||
</div>
|
||||
<span>个开始,每日</span>
|
||||
<div class="w-18">
|
||||
<BaseInput v-model="tempPerDayStudyNumber"/>
|
||||
</div>
|
||||
<span>个,</span>
|
||||
<span>预计<span
|
||||
class="text-3xl mx-2 lh">{{
|
||||
class="text-3xl mx-2 inner">{{
|
||||
_getAccomplishDays(runtimeStore.editDict.length - tempLastLearnIndex, tempPerDayStudyNumber)
|
||||
}}</span>天完成</span>
|
||||
</div>
|
||||
|
||||
<div class="flex mb-4 gap-space">
|
||||
<span class="shrink-0">每日学习</span>
|
||||
<Slider :min="10"
|
||||
@@ -121,18 +131,19 @@ watch(() => model.value, (n) => {
|
||||
<style scoped lang="scss">
|
||||
|
||||
.target-modal {
|
||||
width: 30rem;
|
||||
width: 35rem;
|
||||
padding: 0 var(--space);
|
||||
|
||||
.lh {
|
||||
:deep(.inner){
|
||||
font-size: 2rem;
|
||||
color: rgb(176, 116, 211)
|
||||
}
|
||||
|
||||
.mode-item{
|
||||
.mode-item {
|
||||
@apply w-50% border border-blue border-solid p-2 rounded-lg cursor-pointer;
|
||||
}
|
||||
|
||||
.active{
|
||||
.active {
|
||||
@apply bg-blue color-white;
|
||||
}
|
||||
}
|
||||
@@ -143,53 +154,53 @@ watch(() => model.value, (n) => {
|
||||
width: 90vw !important;
|
||||
max-width: 400px;
|
||||
padding: 0 1rem;
|
||||
|
||||
|
||||
// 模式选择
|
||||
.center .flex.gap-4 {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
gap: 0.8rem;
|
||||
|
||||
|
||||
.mode-item {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
|
||||
.title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.desc {
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 统计显示
|
||||
.text-center {
|
||||
font-size: 0.9rem;
|
||||
|
||||
|
||||
.text-3xl {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 滑块控件
|
||||
.flex.mb-4, .flex.mb-6 {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.flex-1 {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 按钮
|
||||
.base-button {
|
||||
width: 100%;
|
||||
@@ -202,10 +213,10 @@ watch(() => model.value, (n) => {
|
||||
.target-modal {
|
||||
width: 95vw !important;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
|
||||
.text-center {
|
||||
font-size: 0.8rem;
|
||||
|
||||
|
||||
.text-3xl {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ async function onTyping(e: KeyboardEvent) {
|
||||
updateCurrentWordInfo();
|
||||
inputLock = false
|
||||
} else if (settingStore.wordPracticeType === WordPracticeType.Identify && !showWordResult) {
|
||||
//当辨认模式下,按1和2会单独处理,如果按其他键则自动默认为不认识
|
||||
//当自测模式下,按1和2会单独处理,如果按其他键则自动默认为不认识
|
||||
showWordResult = true
|
||||
emit('wrong')
|
||||
if (settingStore.wordSound) volumeIconRef?.play()
|
||||
@@ -409,7 +409,7 @@ useEvents([
|
||||
<template>
|
||||
<div class="typing-word" ref="typingWordRef" v-if="word.word.length">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex gap-1 mt-26">
|
||||
<div class="flex gap-1 mt-30">
|
||||
<div class="phonetic"
|
||||
:class="!(!settingStore.dictation || showFullWord || showWordResult) && 'word-shadow'"
|
||||
v-if="settingStore.soundType === 'us' && word.phonetic0">[{{ word.phonetic0 }}]
|
||||
@@ -481,7 +481,7 @@ useEvents([
|
||||
</div>
|
||||
<div class="other anim"
|
||||
v-opacity="![WordPracticeType.Listen,WordPracticeType.Dictation,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || showFullWord || showWordResult">
|
||||
<div class="line-white my-2"></div>
|
||||
<div class="line-white my-3"></div>
|
||||
<template v-if="word?.sentences?.length">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="sentence" v-for="item in word.sentences">
|
||||
@@ -492,10 +492,10 @@ useEvents([
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-white my-2 mb-5"></div>
|
||||
</template>
|
||||
|
||||
<template v-if="word?.phrases?.length">
|
||||
<div class="line-white my-3"></div>
|
||||
<div class="flex">
|
||||
<div class="label">短语</div>
|
||||
<div class="flex flex-col">
|
||||
@@ -508,11 +508,11 @@ useEvents([
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-white mt-3 mb-2"></div>
|
||||
</template>
|
||||
|
||||
<template v-if="(settingStore.translate || !settingStore.dictation)">
|
||||
<template v-if="word?.synos?.length">
|
||||
<div class="line-white my-3"></div>
|
||||
<div class="flex">
|
||||
<div class='label'>同近义词</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
@@ -531,13 +531,14 @@ useEvents([
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-white my-2"></div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div class="anim"
|
||||
v-opacity="(settingStore.translate && !settingStore.dictation) || showFullWord || showWordResult">
|
||||
<template v-if="word?.etymology?.length">
|
||||
<div class="line-white my-3"></div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="label">词源</div>
|
||||
<div class="text-base">
|
||||
|
||||
@@ -86,7 +86,6 @@ export const useBaseStore = defineStore('base', {
|
||||
},
|
||||
currentStudyProgress(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
if (this.sdict.complete) return 100
|
||||
return _getStudyProgress(this.sdict.lastLearnIndex, this.sdict.length)
|
||||
},
|
||||
getDictCompleteDate(): number {
|
||||
@@ -191,11 +190,12 @@ export const useBaseStore = defineStore('base', {
|
||||
let rIndex = this.article.bookList.findIndex((v: Dict) => v.id === val.id)
|
||||
if (rIndex > -1) {
|
||||
this.article.studyIndex = rIndex
|
||||
this.article.bookList[this.article.studyIndex] = getDefaultDict(val)
|
||||
// this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
|
||||
// this.article.bookList[this.article.studyIndex].cover = val.cover
|
||||
// this.article.bookList[this.article.studyIndex].name = val.name
|
||||
// this.article.bookList[this.article.studyIndex].description = val.description
|
||||
//不要整个等于,不然统计没了
|
||||
// this.article.bookList[this.article.studyIndex] = getDefaultDict(val)
|
||||
this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
|
||||
this.article.bookList[this.article.studyIndex].cover = val.cover
|
||||
this.article.bookList[this.article.studyIndex].name = val.name
|
||||
this.article.bookList[this.article.studyIndex].description = val.description
|
||||
} else {
|
||||
this.article.bookList.push(getDefaultDict(val))
|
||||
this.article.studyIndex = this.article.bookList.length - 1
|
||||
|
||||
Reference in New Issue
Block a user