refactor
This commit is contained in:
4
components.d.ts
vendored
4
components.d.ts
vendored
@@ -93,7 +93,6 @@ declare module 'vue' {
|
||||
IconFluentMyLocation20Regular: typeof import('~icons/fluent/my-location20-regular')['default']
|
||||
IconFluentNumberSymbol20Regular: typeof import('~icons/fluent/number-symbol20-regular')['default']
|
||||
IconFluentPaddingLeft20Regular: typeof import('~icons/fluent/padding-left20-regular')['default']
|
||||
IconFluentPen20Regular: typeof import('~icons/fluent/pen20-regular')['default']
|
||||
IconFluentPerson20Regular: typeof import('~icons/fluent/person20-regular')['default']
|
||||
IconFluentPhone20Regular: typeof import('~icons/fluent/phone20-regular')['default']
|
||||
IconFluentPlay20Regular: typeof import('~icons/fluent/play20-regular')['default']
|
||||
@@ -116,7 +115,6 @@ declare module 'vue' {
|
||||
IconFluentTextBulletListSquare20Regular: typeof import('~icons/fluent/text-bullet-list-square20-regular')['default']
|
||||
IconFluentTextEditStyle20Regular: typeof import('~icons/fluent/text-edit-style20-regular')['default']
|
||||
IconFluentTextListAbcUppercaseLtr20Regular: typeof import('~icons/fluent/text-list-abc-uppercase-ltr20-regular')['default']
|
||||
IconFluentTextParagraph16Regular: typeof import('~icons/fluent/text-paragraph16-regular')['default']
|
||||
IconFluentTextPositionThrough20Regular: typeof import('~icons/fluent/text-position-through20-regular')['default']
|
||||
IconFluentTextUnderlineDouble20Regular: typeof import('~icons/fluent/text-underline-double20-regular')['default']
|
||||
IconFluentTranslate16Regular: typeof import('~icons/fluent/translate16-regular')['default']
|
||||
@@ -124,7 +122,6 @@ declare module 'vue' {
|
||||
IconFluentWeatherMoon16Regular: typeof import('~icons/fluent/weather-moon16-regular')['default']
|
||||
IconFluentWeatherSunny16Regular: typeof import('~icons/fluent/weather-sunny16-regular')['default']
|
||||
IconIconParkOutlineAddMusic: typeof import('~icons/icon-park-outline/add-music')['default']
|
||||
IconIconParkOutlineVolumeNotice: typeof import('~icons/icon-park-outline/volume-notice')['default']
|
||||
IconIxWechatLogo: typeof import('~icons/ix/wechat-logo')['default']
|
||||
IconMaterialSymbolsMail: typeof import('~icons/material-symbols/mail')['default']
|
||||
IconMdiSparkles: typeof import('~icons/mdi/sparkles')['default']
|
||||
@@ -172,7 +169,6 @@ declare module 'vue' {
|
||||
WeChat: typeof import('./src/components/ChannelIcons/WeChat.vue')['default']
|
||||
WordItem: typeof import('./src/components/WordItem.vue')['default']
|
||||
WordList: typeof import('./src/components/list/WordList.vue')['default']
|
||||
WordList2: typeof import('./src/components/list/WordList2.vue')['default']
|
||||
WordSetting: typeof import('./src/components/setting/WordSetting.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
70
src/App.vue
70
src/App.vue
@@ -1,24 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, watch} from "vue";
|
||||
import {BaseState, useBaseStore} from "@/stores/base.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import {loadJsLib, shakeCommonDict} from "@/utils";
|
||||
import {get, set} from 'idb-keyval'
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { BaseState, useBaseStore } from '@/stores/base.ts'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import useTheme from '@/hooks/theme.ts'
|
||||
import { loadJsLib, shakeCommonDict } from '@/utils'
|
||||
import { get, set } from 'idb-keyval'
|
||||
|
||||
import {useRoute} from "vue-router";
|
||||
import {DictId} from "@/types/types.ts";
|
||||
import {APP_VERSION, AppEnv, LOCAL_FILE_KEY, Origin, SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/config/env.ts";
|
||||
import {syncSetting} from "@/apis";
|
||||
import {useUserStore} from "@/stores/user.ts";
|
||||
import MigrateDialog from "@/components/MigrateDialog.vue";
|
||||
import { useRoute } from 'vue-router'
|
||||
import { APP_VERSION, AppEnv, DictId, LOCAL_FILE_KEY, Origin, SAVE_DICT_KEY, SAVE_SETTING_KEY } from '@/config/env.ts'
|
||||
import { syncSetting } from '@/apis'
|
||||
import { useUserStore } from '@/stores/user.ts'
|
||||
import MigrateDialog from '@/components/MigrateDialog.vue'
|
||||
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const settingStore = useSettingStore()
|
||||
const userStore = useUserStore()
|
||||
const {setTheme} = useTheme()
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
let lastAudioFileIdList = []
|
||||
let isInitializing = true // 标记是否正在初始化
|
||||
@@ -26,22 +25,24 @@ watch(store.$state, (n: BaseState) => {
|
||||
// 如果正在初始化,不保存数据,避免覆盖
|
||||
if (isInitializing) return
|
||||
let data = shakeCommonDict(n)
|
||||
set(SAVE_DICT_KEY.key, JSON.stringify({val: data, version: SAVE_DICT_KEY.version}))
|
||||
set(SAVE_DICT_KEY.key, JSON.stringify({ val: data, version: SAVE_DICT_KEY.version }))
|
||||
|
||||
//筛选自定义和收藏
|
||||
let bookList = data.article.bookList.filter(v => v.custom || [DictId.articleCollect].includes(v.id))
|
||||
let audioFileIdList = []
|
||||
bookList.forEach(v => {
|
||||
//筛选 audioFileId 字体有值的
|
||||
v.articles.filter(s => !s.audioSrc && s.audioFileId).forEach(a => {
|
||||
//所有 id 存起来,下次直接判断字符串是否相等,因为这个watch会频繁调用
|
||||
audioFileIdList.push(a.audioFileId)
|
||||
})
|
||||
v.articles
|
||||
.filter(s => !s.audioSrc && s.audioFileId)
|
||||
.forEach(a => {
|
||||
//所有 id 存起来,下次直接判断字符串是否相等,因为这个watch会频繁调用
|
||||
audioFileIdList.push(a.audioFileId)
|
||||
})
|
||||
})
|
||||
if (audioFileIdList.toString() !== lastAudioFileIdList.toString()) {
|
||||
let result = []
|
||||
//删除未使用到的文件
|
||||
get(LOCAL_FILE_KEY).then((fileList: Array<{ id: string, file: Blob }>) => {
|
||||
get(LOCAL_FILE_KEY).then((fileList: Array<{ id: string; file: Blob }>) => {
|
||||
if (fileList && fileList.length > 0) {
|
||||
audioFileIdList.forEach(a => {
|
||||
let item = fileList.find(b => b.id === a)
|
||||
@@ -54,13 +55,17 @@ watch(store.$state, (n: BaseState) => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => settingStore.$state, (n) => {
|
||||
if (isInitializing) return
|
||||
set(SAVE_SETTING_KEY.key, JSON.stringify({val: n, version: SAVE_SETTING_KEY.version}))
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
syncSetting(null, settingStore.$state)
|
||||
}
|
||||
}, {deep: true})
|
||||
watch(
|
||||
() => settingStore.$state,
|
||||
n => {
|
||||
if (isInitializing) return
|
||||
set(SAVE_SETTING_KEY.key, JSON.stringify({ val: n, version: SAVE_SETTING_KEY.version }))
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
syncSetting(null, settingStore.$state)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
async function init() {
|
||||
isInitializing = true // 开始初始化
|
||||
@@ -76,10 +81,10 @@ async function init() {
|
||||
set(APP_VERSION.key, APP_VERSION.version)
|
||||
} else {
|
||||
get(APP_VERSION.key).then(r => {
|
||||
runtimeStore.isNew = r ? (APP_VERSION.version > Number(r)) : true
|
||||
runtimeStore.isNew = r ? APP_VERSION.version > Number(r) : true
|
||||
})
|
||||
}
|
||||
window.umami?.track('host', {host: window.location.host})
|
||||
window.umami?.track('host', { host: window.location.host })
|
||||
}
|
||||
|
||||
onMounted(init)
|
||||
@@ -88,7 +93,7 @@ onMounted(init)
|
||||
let showTransfer = $ref(false)
|
||||
onMounted(() => {
|
||||
if (new URLSearchParams(window.location.search).get('from_old_site') === '1' && location.origin === Origin) {
|
||||
if (localStorage.getItem('__migrated_from_2study_top__')) return;
|
||||
if (localStorage.getItem('__migrated_from_2study_top__')) return
|
||||
setTimeout(() => {
|
||||
showTransfer = true
|
||||
}, 1000)
|
||||
@@ -127,8 +132,5 @@ onMounted(() => {
|
||||
<!-- </transition>-->
|
||||
<!-- </router-view>-->
|
||||
<router-view></router-view>
|
||||
<MigrateDialog
|
||||
v-model="showTransfer"
|
||||
@ok="init"
|
||||
/>
|
||||
<MigrateDialog v-model="showTransfer" @ok="init" />
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http.ts'
|
||||
import { Dict } from '@/types/types.ts'
|
||||
import type { Dict } from '@/types/types.ts'
|
||||
|
||||
export function copyOfficialDict(params?, data?) {
|
||||
return http<Dict>('dict/copyOfficialDict', data, params, 'post')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http, {axiosInstance, AxiosResponse} from "@/utils/http.ts";
|
||||
import { Dict } from "@/types/types.ts";
|
||||
import type { Dict } from "@/types/types.ts";
|
||||
import { cloneDeep } from "@/utils";
|
||||
|
||||
function remove(data?: any) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import http from '@/utils/http.ts'
|
||||
import { CodeType } from "@/types/types.ts";
|
||||
|
||||
import {CodeType} from "@/types/enum.ts";
|
||||
|
||||
// 用户登录接口
|
||||
export interface LoginParams {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http from '@/utils/http.ts'
|
||||
import { Dict } from '@/types/types.ts'
|
||||
import type { Dict } from '@/types/types.ts'
|
||||
|
||||
export function wordDelete(params?, data?) {
|
||||
return http<Dict>('word/delete', data, params, 'post')
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="tsx">
|
||||
import { nextTick, onMounted, useSlots } from 'vue'
|
||||
import { Sort } from '@/types/types.ts'
|
||||
import MiniDialog from '@/components/dialog/MiniDialog.vue'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
@@ -13,6 +12,7 @@ import DeleteIcon from '@/components/icon/DeleteIcon.vue'
|
||||
import Dialog from '@/components/dialog/Dialog.vue'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import { Host } from '@/config/env.ts'
|
||||
import { Sort } from '@/types/enum.ts'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Dict } from "@/types/types.ts";
|
||||
import type { Dict } from "@/types/types.ts";
|
||||
import Progress from '@/components/base/Progress.vue'
|
||||
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, provide} from "vue"
|
||||
import {ShortcutKey} from "@/types/types.ts"
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Close from "@/components/icon/Close.vue";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import {ShortcutKey} from "@/types/enum.ts";
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
let tabIndex = $ref(0)
|
||||
|
||||
@@ -1,46 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import type { Word } from '@/types/types.ts'
|
||||
import VolumeIcon from '@/components/icon/VolumeIcon.vue'
|
||||
import { usePlayWordAudio } from '@/hooks/sound.ts'
|
||||
import Tooltip from '@/components/base/Tooltip.vue'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import { useWordOptions } from '@/hooks/dict.ts'
|
||||
|
||||
import { Word } from "@/types/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import { usePlayWordAudio } from "@/hooks/sound.ts";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import { useWordOptions } from "@/hooks/dict.ts";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
item: Word,
|
||||
showTranslate?: boolean
|
||||
showWord?: boolean
|
||||
showTransPop?: boolean
|
||||
showOption?: boolean
|
||||
showCollectIcon?: boolean
|
||||
showMarkIcon?: boolean
|
||||
index?: number
|
||||
active?: boolean
|
||||
}>(), {
|
||||
showTranslate: true,
|
||||
showWord: true,
|
||||
showTransPop: true,
|
||||
showOption: true,
|
||||
showCollectIcon: true,
|
||||
showMarkIcon: true,
|
||||
active: false,
|
||||
})
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
item: Word
|
||||
showTranslate?: boolean
|
||||
showWord?: boolean
|
||||
showTransPop?: boolean
|
||||
showOption?: boolean
|
||||
showCollectIcon?: boolean
|
||||
showMarkIcon?: boolean
|
||||
index?: number
|
||||
active?: boolean
|
||||
}>(),
|
||||
{
|
||||
showTranslate: true,
|
||||
showWord: true,
|
||||
showTransPop: true,
|
||||
showOption: true,
|
||||
showCollectIcon: true,
|
||||
showMarkIcon: true,
|
||||
active: false,
|
||||
}
|
||||
)
|
||||
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
|
||||
const {
|
||||
isWordCollect,
|
||||
toggleWordCollect,
|
||||
isWordSimple,
|
||||
toggleWordSimple
|
||||
} = useWordOptions()
|
||||
const { isWordCollect, toggleWordCollect, isWordSimple, toggleWordSimple } = useWordOptions()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="common-list-item"
|
||||
:class="{active}"
|
||||
>
|
||||
<div class="common-list-item" :class="{ active }">
|
||||
<div class="left">
|
||||
<slot name="prefix" :item="item"></slot>
|
||||
<div class="title-wrapper">
|
||||
@@ -52,10 +47,7 @@ const {
|
||||
</div>
|
||||
<div class="item-sub-title flex flex-col gap-2" v-if="item.trans.length && showTranslate">
|
||||
<div v-for="v in item.trans">
|
||||
<Tooltip
|
||||
v-if="v.cn.length > 30 && showTransPop"
|
||||
:title="v.pos + ' ' + v.cn"
|
||||
>
|
||||
<Tooltip v-if="v.cn.length > 30 && showTransPop" :title="v.pos + ' ' + v.cn">
|
||||
<span>{{ v.pos + ' ' + v.cn.slice(0, 30) + '...' }}</span>
|
||||
</Tooltip>
|
||||
<span v-else>{{ v.pos + ' ' + v.cn }}</span>
|
||||
@@ -66,26 +58,26 @@ const {
|
||||
<div class="right" v-if="showOption">
|
||||
<slot name="suffix" :item="item"></slot>
|
||||
<BaseIcon
|
||||
v-if="showCollectIcon"
|
||||
:class="!isWordCollect(item)?'collect':'fill'"
|
||||
@click.stop="toggleWordCollect(item)"
|
||||
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
|
||||
<IconFluentStar16Regular v-if="!isWordCollect(item)"/>
|
||||
<IconFluentStar16Filled v-else/>
|
||||
v-if="showCollectIcon"
|
||||
:class="!isWordCollect(item) ? 'collect' : 'fill'"
|
||||
@click.stop="toggleWordCollect(item)"
|
||||
:title="!isWordCollect(item) ? '收藏' : '取消收藏'"
|
||||
>
|
||||
<IconFluentStar16Regular v-if="!isWordCollect(item)" />
|
||||
<IconFluentStar16Filled v-else />
|
||||
</BaseIcon>
|
||||
|
||||
<BaseIcon
|
||||
v-if="showMarkIcon"
|
||||
:class="!isWordSimple(item)?'collect':'fill'"
|
||||
@click.stop="toggleWordSimple(item)"
|
||||
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
|
||||
<IconFluentCheckmarkCircle16Regular v-if="!isWordSimple(item)"/>
|
||||
<IconFluentCheckmarkCircle16Filled v-else/>
|
||||
v-if="showMarkIcon"
|
||||
:class="!isWordSimple(item) ? 'collect' : 'fill'"
|
||||
@click.stop="toggleWordSimple(item)"
|
||||
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'"
|
||||
>
|
||||
<IconFluentCheckmarkCircle16Regular v-if="!isWordSimple(item)" />
|
||||
<IconFluentCheckmarkCircle16Filled v-else />
|
||||
</BaseIcon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Article } from '@/types/types.ts'
|
||||
import type { Article } from '@/types/types.ts'
|
||||
import BaseList from '@/components/list/BaseList.vue'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import { useArticleOptions } from '@/hooks/dict.ts'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import {watch} from "vue";
|
||||
import {DictResource} from "@/types/types.ts";
|
||||
import type {DictResource} from "@/types/types.ts";
|
||||
import DictList from "@/components/list/DictList.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -69,16 +69,16 @@ watch(() => props.groupByTag, () => {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
|
||||
|
||||
.category {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.tags {
|
||||
margin: 0.5rem 0;
|
||||
gap: 0.3rem;
|
||||
|
||||
|
||||
.tag {
|
||||
padding: 0.3rem 0.8rem;
|
||||
font-size: 0.9rem;
|
||||
@@ -98,7 +98,7 @@ watch(() => props.groupByTag, () => {
|
||||
.category {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.tags {
|
||||
.tag {
|
||||
padding: 0.2rem 0.6rem;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {Dict} from "@/types/types.ts";
|
||||
import type {Dict} from "@/types/types.ts";
|
||||
import Book from "@/components/Book.vue";
|
||||
|
||||
defineProps<{
|
||||
@@ -38,7 +38,7 @@ const emit = defineEmits<{
|
||||
@media (max-width: 768px) {
|
||||
.flex.gap-4.flex-wrap {
|
||||
gap: 0.5rem;
|
||||
|
||||
|
||||
.book {
|
||||
width: 5rem;
|
||||
height: calc(5rem * 1.4);
|
||||
@@ -46,33 +46,33 @@ const emit = defineEmits<{
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
|
||||
.text-base {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.2;
|
||||
word-break: break-word;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
.absolute.bottom-4.right-3 {
|
||||
bottom: 0.8rem;
|
||||
right: 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
|
||||
.absolute.bottom-2.left-3.right-3 {
|
||||
bottom: 0.2rem;
|
||||
left: 0.3rem;
|
||||
right: 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
.absolute.left-3.bottom-3 {
|
||||
left: 0.3rem;
|
||||
bottom: 0.3rem;
|
||||
@@ -85,22 +85,22 @@ const emit = defineEmits<{
|
||||
@media (max-width: 480px) {
|
||||
.flex.gap-4.flex-wrap {
|
||||
gap: 0.3rem;
|
||||
|
||||
|
||||
.book {
|
||||
width: 4.5rem;
|
||||
height: calc(4.5rem * 1.4);
|
||||
padding: 0.4rem;
|
||||
|
||||
|
||||
.text-base {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.6rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
|
||||
.absolute.bottom-4.right-3 {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import { cloneDeep, throttle } from "@/utils";
|
||||
import { Article } from "@/types/types.ts";
|
||||
import type { Article } from "@/types/types.ts";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BaseList from "@/components/list/BaseList.vue";
|
||||
import { Word } from "@/types/types.ts";
|
||||
import type { Word } from "@/types/types.ts";
|
||||
import WordItem from "../WordItem.vue";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { ShortcutKey } from '@/types/types.ts'
|
||||
import { SoundFileOptions } from '@/config/env.ts'
|
||||
import { getAudioFileUrl, usePlayAudio } from '@/hooks/sound.ts'
|
||||
import Switch from '@/components/base/Switch.vue'
|
||||
@@ -10,6 +9,7 @@ import Slider from '@/components/base/Slider.vue'
|
||||
import SettingItem from '@/pages/setting/SettingItem.vue'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import {ShortcutKey} from "@/types/enum.ts";
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const store = useBaseStore()
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from "./common";
|
||||
import {SlideType} from "@/types/types.ts";
|
||||
|
||||
import {SlideType} from "@/config/env";
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {emitter as bus} from "@/utils/eventBus.ts";
|
||||
import Utils from '@/utils/gm.js'
|
||||
import {SlideType} from "@/types/types.ts";
|
||||
import GM from "@/utils/gm.js";
|
||||
import {SlideType} from "@/config/env";
|
||||
|
||||
export function slideInit(el, state, type) {
|
||||
state.wrapper.width = GM.$getCss(el, 'width')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { offset } from '@floating-ui/dom'
|
||||
import { ShortcutKey, WordPracticeMode, WordPracticeStage } from '@/types/enum.ts'
|
||||
|
||||
export const GITHUB = 'https://github.com/zyronon/TypeWords'
|
||||
export const Host = 'typewords.cc'
|
||||
@@ -93,3 +94,103 @@ export const LIB_JS_URL = {
|
||||
JSZIP: `${Origin}/libs/jszip.min.js`,
|
||||
XLSX: `${Origin}/libs/xlsx.full.min.js`,
|
||||
}
|
||||
export const PronunciationApi = 'https://dict.youdao.com/dictvoice?audio='
|
||||
export const DefaultShortcutKeyMap = {
|
||||
[ShortcutKey.EditArticle]: 'Ctrl+E',
|
||||
[ShortcutKey.ShowWord]: 'Escape',
|
||||
[ShortcutKey.Previous]: 'Alt+⬅',
|
||||
[ShortcutKey.Next]: 'Tab',
|
||||
[ShortcutKey.ToggleSimple]: '`',
|
||||
[ShortcutKey.ToggleCollect]: 'Enter',
|
||||
[ShortcutKey.PreviousChapter]: 'Ctrl+⬅',
|
||||
[ShortcutKey.NextChapter]: 'Ctrl+➡',
|
||||
[ShortcutKey.RepeatChapter]: 'Ctrl+Enter',
|
||||
[ShortcutKey.DictationChapter]: 'Alt+Enter',
|
||||
[ShortcutKey.PlayWordPronunciation]: 'Ctrl+P',
|
||||
[ShortcutKey.ToggleShowTranslate]: 'Ctrl+Z',
|
||||
[ShortcutKey.ToggleDictation]: 'Ctrl+I',
|
||||
[ShortcutKey.ToggleTheme]: 'Ctrl+Q',
|
||||
[ShortcutKey.ToggleConciseMode]: 'Ctrl+M',
|
||||
[ShortcutKey.TogglePanel]: 'Ctrl+L',
|
||||
[ShortcutKey.RandomWrite]: 'Ctrl+R',
|
||||
[ShortcutKey.KnowWord]: '1',
|
||||
[ShortcutKey.UnknownWord]: '2',
|
||||
}
|
||||
export const SlideType = {
|
||||
HORIZONTAL: 0,
|
||||
VERTICAL: 1,
|
||||
}
|
||||
export const WordPracticeModeStageMap: Record<WordPracticeMode, WordPracticeStage[]> = {
|
||||
[WordPracticeMode.Free]: [WordPracticeStage.FollowWriteNewWord, WordPracticeStage.Complete],
|
||||
[WordPracticeMode.IdentifyOnly]: [
|
||||
WordPracticeStage.IdentifyNewWord,
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.DictationOnly]: [
|
||||
WordPracticeStage.DictationNewWord,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.ListenOnly]: [
|
||||
WordPracticeStage.ListenNewWord,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.System]: [
|
||||
WordPracticeStage.FollowWriteNewWord,
|
||||
WordPracticeStage.ListenNewWord,
|
||||
WordPracticeStage.DictationNewWord,
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.Shuffle]: [WordPracticeStage.Shuffle, WordPracticeStage.Complete],
|
||||
[WordPracticeMode.Review]: [
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
}
|
||||
export const WordPracticeStageNameMap: Record<WordPracticeStage, string> = {
|
||||
[WordPracticeStage.FollowWriteNewWord]: '跟写新词',
|
||||
[WordPracticeStage.IdentifyNewWord]: '自测新词',
|
||||
[WordPracticeStage.ListenNewWord]: '听写新词',
|
||||
[WordPracticeStage.DictationNewWord]: '默写新词',
|
||||
[WordPracticeStage.FollowWriteReview]: '跟写上次学习',
|
||||
[WordPracticeStage.IdentifyReview]: '自测上次学习',
|
||||
[WordPracticeStage.ListenReview]: '听写上次学习',
|
||||
[WordPracticeStage.DictationReview]: '默写上次学习',
|
||||
[WordPracticeStage.FollowWriteReviewAll]: '跟写之前学习',
|
||||
[WordPracticeStage.IdentifyReviewAll]: '自测之前学习',
|
||||
[WordPracticeStage.ListenReviewAll]: '听写之前学习',
|
||||
[WordPracticeStage.DictationReviewAll]: '默写之前学习',
|
||||
[WordPracticeStage.Complete]: '完成学习',
|
||||
[WordPracticeStage.Shuffle]: '随机复习',
|
||||
}
|
||||
export const WordPracticeModeNameMap: Record<WordPracticeMode, string> = {
|
||||
[WordPracticeMode.System]: '学习',
|
||||
[WordPracticeMode.Free]: '自由练习',
|
||||
[WordPracticeMode.IdentifyOnly]: '自测',
|
||||
[WordPracticeMode.DictationOnly]: '默写',
|
||||
[WordPracticeMode.ListenOnly]: '听写',
|
||||
[WordPracticeMode.Shuffle]: '随机复习',
|
||||
[WordPracticeMode.Review]: '复习',
|
||||
}
|
||||
export class DictId {
|
||||
static wordCollect = 'wordCollect'
|
||||
static wordWrong = 'wordWrong'
|
||||
static wordKnown = 'wordKnown'
|
||||
static articleCollect = 'articleCollect'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Article, DictId, PracticeArticleWordType, Sentence } from "@/types/types.ts"
|
||||
import type { Article, Sentence } from "@/types/types.ts"
|
||||
import { _nextTick, cloneDeep } from "@/utils"
|
||||
import { usePlayWordAudio } from "@/hooks/sound.ts"
|
||||
import { getSentenceAllText, getSentenceAllTranslateText } from "@/hooks/translate.ts"
|
||||
@@ -7,6 +7,8 @@ import { useSettingStore } from "@/stores/setting.ts"
|
||||
import { useBaseStore } from "@/stores/base.ts"
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts"
|
||||
import { nanoid } from 'nanoid'
|
||||
import {PracticeArticleWordType} from "@/types/enum.ts";
|
||||
import { DictId } from '@/config/env.ts'
|
||||
|
||||
function parseSentence(sentence: string) {
|
||||
// 先统一一些常见的“智能引号” -> 直引号,避免匹配问题
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Article, Dict, DictId, DictType, TaskWords, Word } from '@/types/types.ts'
|
||||
import type { Article, Dict, TaskWords, Word } from '@/types/types.ts'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { getDefaultDict, getDefaultWord } from '@/types/func.ts'
|
||||
import { _getDictDataByUrl, cloneDeep, getRandomN, resourceWrap, shuffle, sleep, splitIntoN } from '@/utils'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { AppEnv, DICT_LIST } from '@/config/env.ts'
|
||||
import { AppEnv, DICT_LIST, DictId } from '@/config/env.ts'
|
||||
import { detail } from '@/apis'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { DictType } from '@/types/enum.ts'
|
||||
|
||||
export function useWordOptions() {
|
||||
const store = useBaseStore()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {onMounted, watchEffect} from "vue"
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {PronunciationApi} from "@/types/types.ts";
|
||||
|
||||
import {SoundFileOptions} from "@/config/env.ts";
|
||||
import { PronunciationApi, SoundFileOptions } from '@/config/env.ts'
|
||||
|
||||
export function useSound(audioSrcList?: string[], audioFileLength?: number) {
|
||||
let audioList: HTMLAudioElement[] = $ref([])
|
||||
@@ -24,7 +23,7 @@ export function useSound(audioSrcList?: string[], audioFileLength?: number) {
|
||||
}
|
||||
|
||||
function play(volume: number = 100) {
|
||||
console.log('play',audioList)
|
||||
console.log('play', audioList)
|
||||
index++
|
||||
if (audioList.length > 1 && audioList.length !== audioLength) {
|
||||
audioList[index % audioList.length].volume = volume / 100
|
||||
@@ -35,7 +34,7 @@ export function useSound(audioSrcList?: string[], audioFileLength?: number) {
|
||||
}
|
||||
}
|
||||
|
||||
return {play, setAudio}
|
||||
return { play, setAudio }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Article, Sentence, TranslateEngine} from "@/types/types.ts";
|
||||
import type {Article, Sentence} from "@/types/types.ts";
|
||||
import Baidu from "@/libs/translate/baidu";
|
||||
import {Translator} from "@/libs/translate/translator/index.ts";
|
||||
import {TranslateEngine} from "@/types/enum.ts";
|
||||
|
||||
export function getSentenceAllTranslateText(article: Article) {
|
||||
return article.sections.map(v => v.map(s => s.translate.trim()).filter(v => v).join(' \n')).filter(v => v).join(' \n\n');
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useBaseStore } from '@/stores/base.ts'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { getDefaultDict } from '@/types/func.ts'
|
||||
import { DictResource, DictType } from '@/types/types.ts'
|
||||
import type { DictResource } from '@/types/types.ts'
|
||||
import {
|
||||
_getDictDataByUrl,
|
||||
_nextTick,
|
||||
@@ -31,6 +31,7 @@ import isBetween from 'dayjs/plugin/isBetween'
|
||||
import isoWeek from 'dayjs/plugin/isoWeek'
|
||||
import { watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DictType } from '@/types/enum.ts'
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
dayjs.extend(isBetween)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Article, DictId } from '@/types/types.ts'
|
||||
import type { Article } from '@/types/types.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { _nextTick, cloneDeep, loadJsLib } from '@/utils'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
@@ -15,7 +15,7 @@ import { getDefaultArticle } from '@/types/func.ts'
|
||||
import BackIcon from '@/components/BackIcon.vue'
|
||||
import MiniDialog from '@/components/dialog/MiniDialog.vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { LIB_JS_URL, Origin } from '@/config/env.ts'
|
||||
import { DictId, LIB_JS_URL, Origin } from '@/config/env.ts'
|
||||
import { syncBookInMyStudyList } from '@/hooks/article.ts'
|
||||
|
||||
const base = useBaseStore()
|
||||
@@ -254,13 +254,13 @@ function updateList(e) {
|
||||
@select-item="selectArticle"
|
||||
>
|
||||
<template v-slot="{ item, index }">
|
||||
<div>
|
||||
<div class="name">
|
||||
<span class="text-sm text-gray-500" v-if="index != undefined"> {{ index + 1 }}. </span>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="translate-name">{{ ` ${item.titleTranslate}` }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="name">
|
||||
<span class="text-sm text-gray-500" v-if="index != undefined"> {{ index + 1 }}. </span>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="translate-name">{{ ` ${item.titleTranslate}` }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</List>
|
||||
<div class="add" v-if="!article.title">正在添加新文章...</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import BackIcon from '@/components/BackIcon.vue'
|
||||
import Empty from '@/components/Empty.vue'
|
||||
import ArticleList from '@/components/list/ArticleList.vue'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import { Article, Dict, DictType } from '@/types/types.ts'
|
||||
import type { Article, Dict } from '@/types/types'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -20,6 +20,7 @@ import { DICT_LIST } from '@/config/env.ts'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import Switch from '@/components/base/Switch.vue'
|
||||
import { useGetDict } from '@/hooks/dict.ts'
|
||||
import { DictType } from '@/types/enum.ts'
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -147,7 +148,7 @@ const list = $computed(() => {
|
||||
}),
|
||||
].concat(runtimeStore.editDict.articles)
|
||||
})
|
||||
console.log('list',list)
|
||||
console.log('list', list)
|
||||
|
||||
let showTranslate = $ref(true)
|
||||
let startPlay = $ref(false)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { resourceWrap, useNav } from "@/utils";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import { DictResource } from "@/types/types.ts";
|
||||
import type { DictResource } from "@/types/types.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
|
||||
@@ -21,23 +21,14 @@ import { usePracticeStore } from '@/stores/practice.ts'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { getDefaultArticle, getDefaultDict, getDefaultWord } from '@/types/func.ts'
|
||||
import {
|
||||
Article,
|
||||
ArticleItem,
|
||||
ArticleWord,
|
||||
Dict,
|
||||
DictType,
|
||||
PracticeArticleWordType,
|
||||
ShortcutKey,
|
||||
Statistics,
|
||||
Word,
|
||||
} from '@/types/types.ts'
|
||||
import type { Article, ArticleItem, ArticleWord, Dict, Statistics, Word } from '@/types/types.ts'
|
||||
import { _getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total } from '@/utils'
|
||||
import { getPracticeArticleCache, setPracticeArticleCache } from '@/utils/cache.ts'
|
||||
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
|
||||
import { computed, onMounted, onUnmounted, provide, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { DictType, PracticeArticleWordType, ShortcutKey } from '@/types/enum.ts'
|
||||
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Article } from '@/types/types.ts'
|
||||
import type { Article } from '@/types/types.ts'
|
||||
import { ref, watch } from 'vue'
|
||||
import { get } from 'idb-keyval'
|
||||
import Audio from '@/components/base/Audio.vue'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Article, Sentence, TranslateEngine } from '@/types/types.ts'
|
||||
import type { Article, Sentence } from '@/types/types.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import EditAbleText from '@/components/EditAbleText.vue'
|
||||
import { getNetworkTranslate, getSentenceAllText, getSentenceAllTranslateText } from '@/hooks/translate.ts'
|
||||
@@ -22,6 +22,7 @@ import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import Textarea from '@/components/base/Textarea.vue'
|
||||
import { LOCAL_FILE_KEY } from '@/config/env.ts'
|
||||
import PopConfirm from '@/components/PopConfirm.vue'
|
||||
import {TranslateEngine} from "@/types/enum.ts";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { Dict, DictId, DictType } from "@/types/types.ts";
|
||||
import { cloneDeep } from "@/utils";
|
||||
import type { Dict } from '@/types/types.ts'
|
||||
import { cloneDeep } from '@/utils'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { onMounted, reactive } from "vue";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { getDefaultDict } from "@/types/func.ts";
|
||||
import { Option, Select } from "@/components/base/select";
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import { addDict } from "@/apis";
|
||||
import { AppEnv } from "@/config/env.ts";
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { getDefaultDict } from '@/types/func.ts'
|
||||
import { Option, Select } from '@/components/base/select'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import Form from '@/components/base/form/Form.vue'
|
||||
import FormItem from '@/components/base/form/FormItem.vue'
|
||||
import { addDict } from '@/apis'
|
||||
import { AppEnv, DictId } from '@/config/env.ts'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { DictType } from '@/types/enum.ts'
|
||||
|
||||
const props = defineProps<{
|
||||
isAdd: boolean,
|
||||
isAdd: boolean
|
||||
isBook: boolean
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
@@ -34,20 +34,20 @@ const DefaultDictForm = {
|
||||
tags: [],
|
||||
translateLanguage: 'zh-CN',
|
||||
language: 'en',
|
||||
type: DictType.article
|
||||
type: DictType.article,
|
||||
}
|
||||
let dictForm: any = $ref(cloneDeep(DefaultDictForm))
|
||||
const dictFormRef = $ref()
|
||||
let loading = $ref(false)
|
||||
const dictRules = reactive({
|
||||
name: [
|
||||
{required: true, message: '请输入名称', trigger: 'blur'},
|
||||
{max: 20, message: '名称不能超过20个字符', trigger: 'blur'},
|
||||
{ required: true, message: '请输入名称', trigger: 'blur' },
|
||||
{ max: 20, message: '名称不能超过20个字符', trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
|
||||
async function onSubmit() {
|
||||
await dictFormRef.validate(async (valid) => {
|
||||
await dictFormRef.validate(async valid => {
|
||||
if (valid) {
|
||||
let data: Dict = getDefaultDict(dictForm)
|
||||
data.type = props.isBook ? DictType.article : DictType.word
|
||||
@@ -78,10 +78,15 @@ async function onSubmit() {
|
||||
} else {
|
||||
let rIndex = source.bookList.findIndex(v => v.id === data.id)
|
||||
//任意修改,都将其变为自定义词典
|
||||
if (!data.custom && ![DictId.wordKnown, DictId.wordWrong, DictId.wordCollect, DictId.articleCollect].includes(data.en_name || data.id)) {
|
||||
if (
|
||||
!data.custom &&
|
||||
![DictId.wordKnown, DictId.wordWrong, DictId.wordCollect, DictId.articleCollect].includes(
|
||||
data.en_name || data.id
|
||||
)
|
||||
) {
|
||||
data.custom = true
|
||||
if (!data.id.includes('_custom')) {
|
||||
data.id +='_custom_' + nanoid(6)
|
||||
data.id += '_custom_' + nanoid(6)
|
||||
}
|
||||
}
|
||||
runtimeStore.editDict = data
|
||||
@@ -106,36 +111,31 @@ onMounted(() => {
|
||||
dictForm = cloneDeep(runtimeStore.editDict)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-120 mt-4">
|
||||
<Form
|
||||
ref="dictFormRef"
|
||||
:rules="dictRules"
|
||||
:model="dictForm"
|
||||
label-width="8rem">
|
||||
<Form ref="dictFormRef" :rules="dictRules" :model="dictForm" label-width="8rem">
|
||||
<FormItem label="名称" prop="name">
|
||||
<BaseInput v-model="dictForm.name"/>
|
||||
<BaseInput v-model="dictForm.name" />
|
||||
</FormItem>
|
||||
<FormItem label="描述">
|
||||
<BaseInput v-model="dictForm.description" textarea/>
|
||||
<BaseInput v-model="dictForm.description" textarea />
|
||||
</FormItem>
|
||||
<FormItem label="原文语言" v-if="false">
|
||||
<Select v-model="dictForm.language" placeholder="请选择选项">
|
||||
<Option label="英语" value="en"/>
|
||||
<Option label="德语" value="de"/>
|
||||
<Option label="日语" value="ja"/>
|
||||
<Option label="代码" value="code"/>
|
||||
<Option label="英语" value="en" />
|
||||
<Option label="德语" value="de" />
|
||||
<Option label="日语" value="ja" />
|
||||
<Option label="代码" value="code" />
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="译文语言" v-if="false">
|
||||
<Select v-model="dictForm.translateLanguage" placeholder="请选择选项">
|
||||
<Option label="中文" value="zh-CN"/>
|
||||
<Option label="英语" value="en"/>
|
||||
<Option label="德语" value="de"/>
|
||||
<Option label="日语" value="ja"/>
|
||||
<Option label="中文" value="zh-CN" />
|
||||
<Option label="英语" value="en" />
|
||||
<Option label="德语" value="de" />
|
||||
<Option label="日语" value="ja" />
|
||||
</Select>
|
||||
</FormItem>
|
||||
<div class="center">
|
||||
@@ -146,7 +146,4 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {Article} from "@/types/types.ts";
|
||||
import type {Article} from "@/types/types.ts";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
import EditArticle from "@/pages/article/components/EditArticle.vue";
|
||||
import {getDefaultArticle} from "@/types/func.ts";
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useBaseStore } from '@/stores/base.ts'
|
||||
import { usePracticeStore } from '@/stores/practice.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { getDefaultArticle, getDefaultWord } from '@/types/func.ts'
|
||||
import { Article, ArticleWord, PracticeArticleWordType, Sentence, ShortcutKey, Word } from '@/types/types.ts'
|
||||
import type { Article, ArticleWord, Sentence, Word } from '@/types/types.ts'
|
||||
import { _dateFormat, _nextTick, isMobile, msToHourMinute, total } from '@/utils'
|
||||
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
|
||||
import ContextMenu from '@imengyu/vue3-context-menu'
|
||||
@@ -20,6 +20,7 @@ import { nanoid } from 'nanoid'
|
||||
import { inject, onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
import { getPracticeArticleCache, setPracticeArticleCache } from '@/utils/cache.ts'
|
||||
import { PracticeArticleWordType, ShortcutKey } from '@/types/enum.ts'
|
||||
|
||||
interface IProps {
|
||||
article: Article
|
||||
@@ -675,13 +676,15 @@ const currentPractice = inject('currentPractice', [])
|
||||
<header class="pt-10 pb-6">
|
||||
<div class="text-center">
|
||||
<span class="text-3xl">{{ store.sbook.lastLearnIndex + 1 }}. </span>
|
||||
<span class="text-3xl">{{ props.article?.title??'' }}</span>
|
||||
<span class="text-3xl">{{ props.article?.title ?? '' }}</span>
|
||||
<span class="ml-6 text-2xl" v-if="settingStore.translate">{{ props.article?.titleTranslate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-2xl" v-if="props.article?.question?.text">
|
||||
<div>Question: {{ props.article?.question?.text }}</div>
|
||||
<div class="text-xl color-translate-second" v-if="settingStore.translate">问题: {{ props.article?.question?.translate }}</div>
|
||||
<div class="text-xl color-translate-second" v-if="settingStore.translate">
|
||||
问题: {{ props.article?.question?.translate }}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="tsx">
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Space from "@/pages/article/components/Space.vue";
|
||||
import { PracticeArticleWordType } from "@/types/types.ts";
|
||||
//引入这个编译就报错
|
||||
// import {ArticleWord} from "@/types/types.ts";
|
||||
|
||||
import {PracticeArticleWordType} from "@/types/enum.ts";
|
||||
import type {ArticleWord} from "@/types/types.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
word: any,
|
||||
word: ArticleWord,
|
||||
isTyping: boolean,
|
||||
}>()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { ShortcutKey } from '@/types/types.ts'
|
||||
import Logo from '@/components/Logo.vue'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -8,6 +7,7 @@ import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import { jump2Feedback } from '@/utils'
|
||||
import { watch } from 'vue'
|
||||
import { ShortcutKey } from '@/types/enum.ts'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
@@ -3,10 +3,18 @@ import { nextTick, ref, watch } from 'vue'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { getShortcutKey, useEventListener } from '@/hooks/event.ts'
|
||||
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, loadJsLib, sleep } from '@/utils'
|
||||
import { DefaultShortcutKeyMap } from '@/types/types.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import { APP_NAME, APP_VERSION, AppEnv, Host, IS_DEV, LIB_JS_URL, LOCAL_FILE_KEY } from '@/config/env.ts'
|
||||
import {
|
||||
APP_NAME,
|
||||
APP_VERSION,
|
||||
AppEnv,
|
||||
DefaultShortcutKeyMap,
|
||||
Host,
|
||||
IS_DEV,
|
||||
LIB_JS_URL,
|
||||
LOCAL_FILE_KEY,
|
||||
} from '@/config/env.ts'
|
||||
import BasePage from '@/components/BasePage.vue'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { set } from 'idb-keyval'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {CodeType} from "@/types/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {sendCode} from "@/apis/user.ts";
|
||||
import {PHONE_CONFIG} from "@/config/auth.ts";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import {CodeType} from "@/types/enum.ts";
|
||||
|
||||
let isSendingCode = $ref(false)
|
||||
let codeCountdown = $ref(0)
|
||||
@@ -63,4 +63,4 @@ async function sendVerificationCode() {
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,6 @@ import BaseButton from "@/components/BaseButton.vue";
|
||||
import {PASSWORD_CONFIG, PHONE_CONFIG} from "@/config/auth.ts";
|
||||
import {changeEmailApi, changePhoneApi, setPassword, updateUserInfoApi, User} from "@/apis/user.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {CodeType} from "@/types/types.ts";
|
||||
import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
import {FormInstance} from "@/components/base/form/types.ts";
|
||||
@@ -18,6 +17,7 @@ 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";
|
||||
import {CodeType} from "@/types/enum.ts";
|
||||
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
@@ -626,4 +626,4 @@ function onFileChange(e) {
|
||||
.item {
|
||||
@apply flex items-center justify-between min-h-14;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script setup lang="tsx">
|
||||
import { onBeforeUnmount, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { APP_NAME } from "@/config/env.ts";
|
||||
import { useUserStore } from "@/stores/user.ts";
|
||||
import { loginApi, LoginParams, registerApi, resetPasswordApi } from "@/apis/user.ts";
|
||||
import { accountRules, codeRules, passwordRules, phoneRules } from "@/utils/validation.ts";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import FormItem from "@/components/base/form/FormItem.vue";
|
||||
import Form from "@/components/base/form/Form.vue";
|
||||
import Notice from "@/pages/user/Notice.vue";
|
||||
import { FormInstance } from "@/components/base/form/types.ts";
|
||||
import { PASSWORD_CONFIG, PHONE_CONFIG } from "@/config/auth.ts";
|
||||
import { CodeType, ImportStatus } from "@/types/types.ts";
|
||||
import Code from "@/pages/user/Code.vue";
|
||||
import { isNewUser, jump2Feedback, sleep, useNav } from "@/utils";
|
||||
import Header from "@/components/Header.vue";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import { useExport } from "@/hooks/export.ts";
|
||||
import { getProgress, upload, uploadImportData } from "@/apis";
|
||||
import { Exception } from "sass";
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { APP_NAME } from '@/config/env.ts'
|
||||
import { useUserStore } from '@/stores/user.ts'
|
||||
import { loginApi, LoginParams, registerApi, resetPasswordApi } from '@/apis/user.ts'
|
||||
import { accountRules, codeRules, passwordRules, phoneRules } from '@/utils/validation.ts'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import FormItem from '@/components/base/form/FormItem.vue'
|
||||
import Form from '@/components/base/form/Form.vue'
|
||||
import Notice from '@/pages/user/Notice.vue'
|
||||
import { FormInstance } from '@/components/base/form/types.ts'
|
||||
import { PASSWORD_CONFIG, PHONE_CONFIG } from '@/config/auth.ts'
|
||||
import Code from '@/pages/user/Code.vue'
|
||||
import { isNewUser, jump2Feedback, sleep, useNav } from '@/utils'
|
||||
import Header from '@/components/Header.vue'
|
||||
import PopConfirm from '@/components/PopConfirm.vue'
|
||||
import { useExport } from '@/hooks/export.ts'
|
||||
import { getProgress, upload, uploadImportData } from '@/apis'
|
||||
import { Exception } from 'sass'
|
||||
import { CodeType, ImportStatus } from '@/types/enum.ts'
|
||||
|
||||
// 状态管理
|
||||
const userStore = useUserStore()
|
||||
@@ -41,28 +41,25 @@ let waitForImportConfirmation = $ref(true)
|
||||
|
||||
const QR_EXPIRE_TIME = 5 * 60 * 1000 // 5分钟过期
|
||||
|
||||
|
||||
let phoneLoginForm = $ref({phone: '', code: ''})
|
||||
let phoneLoginForm = $ref({ phone: '', code: '' })
|
||||
let phoneLoginFormRef = $ref<FormInstance>()
|
||||
let phoneLoginFormRules = {
|
||||
phone: phoneRules,
|
||||
code: codeRules
|
||||
code: codeRules,
|
||||
}
|
||||
|
||||
|
||||
let loginForm2 = $ref({account: '', password: ''})
|
||||
let loginForm2 = $ref({ account: '', password: '' })
|
||||
let loginForm2Ref = $ref<FormInstance>()
|
||||
let loginForm2Rules = {
|
||||
account: accountRules,
|
||||
password: passwordRules,
|
||||
}
|
||||
|
||||
|
||||
const registerForm = $ref({
|
||||
account: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
code: ''
|
||||
code: '',
|
||||
})
|
||||
let registerFormRef = $ref<FormInstance>()
|
||||
// 注册表单规则和引用
|
||||
@@ -71,23 +68,23 @@ let registerFormRules = {
|
||||
code: codeRules,
|
||||
password: passwordRules,
|
||||
confirmPassword: [
|
||||
{required: true, message: '请再次输入密码', trigger: 'blur'},
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value !== registerForm.password) {
|
||||
throw new Error('两次密码输入不一致')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
const forgotForm = $ref({
|
||||
account: '',
|
||||
code: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
confirmPassword: '',
|
||||
})
|
||||
let forgotFormRef = $ref<FormInstance>()
|
||||
// 忘记密码表单规则和引用
|
||||
@@ -96,13 +93,14 @@ let forgotFormRules = {
|
||||
code: codeRules,
|
||||
newPassword: passwordRules,
|
||||
confirmPassword: [
|
||||
{required: true, message: '请再次输入新密码', trigger: 'blur'},
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value !== forgotForm.newPassword) {
|
||||
throw new Error('两次密码输入不一致')
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -122,17 +120,17 @@ function loginSuccess(token: string) {
|
||||
|
||||
// 统一登录处理
|
||||
async function handleLogin() {
|
||||
currentFormRef.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
currentFormRef.validate(async valid => {
|
||||
if (!valid) return
|
||||
try {
|
||||
loading = true
|
||||
let data = {}
|
||||
//手机号登录
|
||||
if (loginType === 'code') {
|
||||
data = {...phoneLoginForm, type: 'code'}
|
||||
data = { ...phoneLoginForm, type: 'code' }
|
||||
} else {
|
||||
//密码登录
|
||||
data = {...loginForm2, type: 'pwd'}
|
||||
data = { ...loginForm2, type: 'pwd' }
|
||||
}
|
||||
let res = await loginApi(data as LoginParams)
|
||||
if (res.success) {
|
||||
@@ -153,7 +151,7 @@ async function handleLogin() {
|
||||
|
||||
// 注册
|
||||
async function handleRegister() {
|
||||
registerFormRef.validate(async (valid) => {
|
||||
registerFormRef.validate(async valid => {
|
||||
if (!valid) return
|
||||
try {
|
||||
loading = true
|
||||
@@ -175,7 +173,7 @@ async function handleRegister() {
|
||||
|
||||
// 忘记密码
|
||||
async function handleForgotPassword() {
|
||||
forgotFormRef.validate(async (valid) => {
|
||||
forgotFormRef.validate(async valid => {
|
||||
if (!valid) return
|
||||
try {
|
||||
loading = true
|
||||
@@ -224,7 +222,8 @@ async function handleWechatLogin() {
|
||||
// wechatQRUrl = response.qrUrl
|
||||
|
||||
// 暂时使用占位二维码
|
||||
wechatQRUrl = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2Y1ZjVmNSIvPgogIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OTk5OSI+55So5o6l566h55CG6L295Lit6K+B77yBPC90ZXh0Pgo8L3N2Zz4K'
|
||||
wechatQRUrl =
|
||||
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2Y1ZjVmNSIvPgogIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OTk5OSI+55So5o6l566h55CG6L295Lit6K+B77yBPC90ZXh0Pgo8L3N2Zz4K'
|
||||
|
||||
// 模拟轮询检查扫码状态
|
||||
qrCheckInterval = setInterval(async () => {
|
||||
@@ -246,7 +245,6 @@ async function handleWechatLogin() {
|
||||
qrCheckInterval = null
|
||||
Toast.info('二维码已过期,请点击刷新')
|
||||
}, QR_EXPIRE_TIME)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Wechat login error:', error)
|
||||
Toast.error('微信登录失败')
|
||||
@@ -288,13 +286,13 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
|
||||
enum ImportStep {
|
||||
CONFIRMATION,//等待确认
|
||||
PROCESSING,//处理中
|
||||
SUCCESS,//成功
|
||||
FAIL,//失败
|
||||
CONFIRMATION, //等待确认
|
||||
PROCESSING, //处理中
|
||||
SUCCESS, //成功
|
||||
FAIL, //失败
|
||||
}
|
||||
|
||||
const {exportData} = useExport()
|
||||
const { exportData } = useExport()
|
||||
let importStep = $ref<ImportStep>(ImportStep.CONFIRMATION)
|
||||
let isImporting = $ref(false)
|
||||
let reason = $ref('')
|
||||
@@ -311,9 +309,9 @@ async function startSync() {
|
||||
let res = await exportData('')
|
||||
reason = '上传数据中'
|
||||
let formData = new FormData()
|
||||
formData.append('file', res, "example.zip")
|
||||
formData.append('file', res, 'example.zip')
|
||||
let result = await uploadImportData(formData, progressEvent => {
|
||||
let percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
let percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
|
||||
reason = `上传进度(${percent}%)`
|
||||
})
|
||||
if (result.success) {
|
||||
@@ -342,7 +340,7 @@ async function startSync() {
|
||||
}
|
||||
}, 2000)
|
||||
} else {
|
||||
throw new Error(`同步失败,${result.msg ? ('原因: ' + result.msg) : ''},请联系管理员`)
|
||||
throw new Error(`同步失败,${result.msg ? '原因: ' + result.msg : ''},请联系管理员`)
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error(error.message || '同步失败')
|
||||
@@ -356,13 +354,9 @@ function logout() {
|
||||
waitForImportConfirmation = false
|
||||
}
|
||||
|
||||
function forgetData() {
|
||||
function forgetData() {}
|
||||
|
||||
}
|
||||
|
||||
function goHome(){
|
||||
|
||||
}
|
||||
function goHome() {}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -379,88 +373,83 @@ function goHome(){
|
||||
<!-- Tab切换 -->
|
||||
<div class="center gap-8 mb-6">
|
||||
<div
|
||||
class="center cp transition-colors"
|
||||
:class="loginType === 'code' ? 'link font-medium' : 'text-gray-600'"
|
||||
@click="loginType = 'code'"
|
||||
class="center cp transition-colors"
|
||||
:class="loginType === 'code' ? 'link font-medium' : 'text-gray-600'"
|
||||
@click="loginType = 'code'"
|
||||
>
|
||||
<div>
|
||||
<span>验证码登录</span>
|
||||
<div
|
||||
v-opacity="loginType === 'code'"
|
||||
class="mt-1 h-0.5 bg-blue-600"
|
||||
></div>
|
||||
<div v-opacity="loginType === 'code'" class="mt-1 h-0.5 bg-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="center cp transition-colors"
|
||||
:class="loginType === 'password' ? 'link font-medium' : 'text-gray-600'"
|
||||
@click="loginType = 'password'"
|
||||
class="center cp transition-colors"
|
||||
:class="loginType === 'password' ? 'link font-medium' : 'text-gray-600'"
|
||||
@click="loginType = 'password'"
|
||||
>
|
||||
<div>
|
||||
<span>密码登录</span>
|
||||
<div
|
||||
v-opacity="loginType === 'password'"
|
||||
class="mt-1 h-0.5 bg-blue-600"
|
||||
></div>
|
||||
<div v-opacity="loginType === 'password'" class="mt-1 h-0.5 bg-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 验证码登录表单 -->
|
||||
<Form
|
||||
v-if="loginType === 'code'"
|
||||
ref="phoneLoginFormRef"
|
||||
:rules="phoneLoginFormRules"
|
||||
:model="phoneLoginForm">
|
||||
v-if="loginType === 'code'"
|
||||
ref="phoneLoginFormRef"
|
||||
:rules="phoneLoginFormRules"
|
||||
:model="phoneLoginForm"
|
||||
>
|
||||
<FormItem prop="phone">
|
||||
<BaseInput v-model="phoneLoginForm.phone"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="tel"
|
||||
size="large"
|
||||
placeholder="请输入手机号"
|
||||
<BaseInput
|
||||
v-model="phoneLoginForm.phone"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="tel"
|
||||
size="large"
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="code">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="phoneLoginForm.code"
|
||||
type="code"
|
||||
size="large"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
placeholder="请输入验证码"
|
||||
v-model="phoneLoginForm.code"
|
||||
type="code"
|
||||
size="large"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
placeholder="请输入验证码"
|
||||
/>
|
||||
<Code
|
||||
:validate-field="() => phoneLoginFormRef.validateField('phone')"
|
||||
:type="CodeType.Login"
|
||||
:val="phoneLoginForm.phone"
|
||||
/>
|
||||
<Code :validate-field="() => phoneLoginFormRef.validateField('phone')"
|
||||
:type="CodeType.Login"
|
||||
:val="phoneLoginForm.phone"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<!-- 密码登录表单 -->
|
||||
<Form
|
||||
v-else
|
||||
ref="loginForm2Ref"
|
||||
:rules="loginForm2Rules"
|
||||
:model="loginForm2">
|
||||
<Form v-else ref="loginForm2Ref" :rules="loginForm2Rules" :model="loginForm2">
|
||||
<FormItem prop="account">
|
||||
<BaseInput v-model="loginForm2.account"
|
||||
type="email"
|
||||
name="username"
|
||||
autocomplete="email"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
<BaseInput
|
||||
v-model="loginForm2.account"
|
||||
type="email"
|
||||
name="username"
|
||||
autocomplete="email"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="password">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="loginForm2.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
v-model="loginForm2.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</div>
|
||||
</FormItem>
|
||||
@@ -470,14 +459,7 @@ function goHome(){
|
||||
<span v-if="loginType === 'code'">,未注册的手机号将自动注册</span>
|
||||
</Notice>
|
||||
|
||||
<BaseButton
|
||||
class="w-full"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录
|
||||
</BaseButton>
|
||||
<BaseButton class="w-full" size="large" :loading="loading" @click="handleLogin"> 登录 </BaseButton>
|
||||
|
||||
<!-- 底部操作链接 - 只在密码登录时显示 -->
|
||||
<div class="mt-4 flex justify-between text-sm" v-opacity="loginType !== 'code'">
|
||||
@@ -488,131 +470,116 @@ function goHome(){
|
||||
|
||||
<!-- 注册模式 -->
|
||||
<div v-else-if="currentMode === 'register'">
|
||||
<Header @click="switchMode('login')" title="注册新账号"/>
|
||||
<Header @click="switchMode('login')" title="注册新账号" />
|
||||
|
||||
<Form
|
||||
ref="registerFormRef"
|
||||
:rules="registerFormRules"
|
||||
:model="registerForm">
|
||||
<Form ref="registerFormRef" :rules="registerFormRules" :model="registerForm">
|
||||
<FormItem prop="account">
|
||||
<BaseInput
|
||||
v-model="registerForm.account"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
v-model="registerForm.account"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="code">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="registerForm.code"
|
||||
type="code"
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
v-model="registerForm.code"
|
||||
type="code"
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<Code
|
||||
:validate-field="() => registerFormRef.validateField('account')"
|
||||
:type="CodeType.Register"
|
||||
:val="registerForm.account"
|
||||
/>
|
||||
<Code :validate-field="() => registerFormRef.validateField('account')"
|
||||
:type="CodeType.Register"
|
||||
:val="registerForm.account"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="password">
|
||||
<BaseInput
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
:placeholder="`请设置密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位)`"
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
size="large"
|
||||
:placeholder="`请设置密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位)`"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="confirmPassword">
|
||||
<BaseInput
|
||||
v-model="registerForm.confirmPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
placeholder="请再次输入密码"
|
||||
v-model="registerForm.confirmPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
placeholder="请再次输入密码"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<Notice/>
|
||||
|
||||
<BaseButton
|
||||
class="w-full"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="handleRegister"
|
||||
>
|
||||
注册
|
||||
</BaseButton>
|
||||
<Notice />
|
||||
|
||||
<BaseButton class="w-full" size="large" :loading="loading" @click="handleRegister"> 注册 </BaseButton>
|
||||
</div>
|
||||
|
||||
<!-- 忘记密码模式 -->
|
||||
<div v-else-if="currentMode === 'forgot'">
|
||||
<Header @click="switchMode('login')" title="重置密码"/>
|
||||
<Header @click="switchMode('login')" title="重置密码" />
|
||||
|
||||
<Form
|
||||
ref="forgotFormRef"
|
||||
:rules="forgotFormRules"
|
||||
:model="forgotForm">
|
||||
<Form ref="forgotFormRef" :rules="forgotFormRules" :model="forgotForm">
|
||||
<FormItem prop="account">
|
||||
<BaseInput
|
||||
v-model="forgotForm.account"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
v-model="forgotForm.account"
|
||||
type="tel"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
size="large"
|
||||
placeholder="请输入手机号/邮箱地址"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="code">
|
||||
<div class="flex gap-2">
|
||||
<BaseInput
|
||||
v-model="forgotForm.code"
|
||||
type="code"
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
v-model="forgotForm.code"
|
||||
type="code"
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
:max-length="PHONE_CONFIG.codeLength"
|
||||
/>
|
||||
<Code
|
||||
:validate-field="() => forgotFormRef.validateField('account')"
|
||||
:type="CodeType.ResetPwd"
|
||||
:val="forgotForm.account"
|
||||
/>
|
||||
<Code :validate-field="() => forgotFormRef.validateField('account')"
|
||||
:type="CodeType.ResetPwd"
|
||||
:val="forgotForm.account"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem prop="newPassword">
|
||||
<BaseInput
|
||||
v-model="forgotForm.newPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
:placeholder="`请输入新密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位)`"
|
||||
v-model="forgotForm.newPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
:placeholder="`请输入新密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength} 位)`"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem prop="confirmPassword">
|
||||
<BaseInput
|
||||
v-model="forgotForm.confirmPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
placeholder="请再次输入新密码"
|
||||
v-model="forgotForm.confirmPassword"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
size="large"
|
||||
placeholder="请再次输入新密码"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<BaseButton
|
||||
class="w-full mt-2"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="handleForgotPassword"
|
||||
>
|
||||
<BaseButton class="w-full mt-2" size="large" :loading="loading" @click="handleForgotPassword">
|
||||
重置密码
|
||||
</BaseButton>
|
||||
</div>
|
||||
@@ -622,43 +589,42 @@ function goHome(){
|
||||
<div v-if="currentMode === 'login'" class="center flex-col bg-gray-100 rounded-xl px-12">
|
||||
<div class="relative w-40 h-40 bg-white rounded-xl overflow-hidden shadow-xl">
|
||||
<img
|
||||
v-if="showWechatQR"
|
||||
:src="wechatQRUrl"
|
||||
alt="微信登录二维码"
|
||||
class="w-full h-full"
|
||||
:class="{ 'opacity-30': qrStatus === 'expired' }"
|
||||
v-if="showWechatQR"
|
||||
:src="wechatQRUrl"
|
||||
alt="微信登录二维码"
|
||||
class="w-full h-full"
|
||||
:class="{ 'opacity-30': qrStatus === 'expired' }"
|
||||
/>
|
||||
<!-- 扫描成功蒙层 -->
|
||||
<div
|
||||
v-if="qrStatus === 'scanned'"
|
||||
class="absolute left-0 top-0 w-full h-full center flex-col gap-space bg-white"
|
||||
v-if="qrStatus === 'scanned'"
|
||||
class="absolute left-0 top-0 w-full h-full center flex-col gap-space bg-white"
|
||||
>
|
||||
<IconFluentCheckmarkCircle20Filled class="color-green text-4xl"/>
|
||||
<IconFluentCheckmarkCircle20Filled class="color-green text-4xl" />
|
||||
<div class="text-base text-gray-700 font-medium">扫描成功</div>
|
||||
<div class="text-xs text-gray-600">微信中轻触允许即可登录</div>
|
||||
</div>
|
||||
<!-- 取消登录蒙层 -->
|
||||
<div
|
||||
v-if="qrStatus === 'cancelled'"
|
||||
class="absolute left-0 top-0 w-full h-full center flex-col gap-space bg-white"
|
||||
v-if="qrStatus === 'cancelled'"
|
||||
class="absolute left-0 top-0 w-full h-full center flex-col gap-space bg-white"
|
||||
>
|
||||
<IconFluentErrorCircle20Regular class="color-red text-4xl"/>
|
||||
<IconFluentErrorCircle20Regular class="color-red text-4xl" />
|
||||
<div class="text-base text-gray-700 font-medium">你已取消此次登录</div>
|
||||
<div class="text-xs text-gray-600">你可<span class="color-link" @click="refreshQRCode">再次登录</span>,或关闭窗口
|
||||
<div class="text-xs text-gray-600">
|
||||
你可<span class="color-link" @click="refreshQRCode">再次登录</span>,或关闭窗口
|
||||
</div>
|
||||
</div>
|
||||
<!-- 过期蒙层 -->
|
||||
<div
|
||||
v-if=" qrStatus === 'expired'"
|
||||
class="absolute top-0 left-0 right-0 bottom-0 bg-opacity-95 center backdrop-blur-sm"
|
||||
v-if="qrStatus === 'expired'"
|
||||
class="absolute top-0 left-0 right-0 bottom-0 bg-opacity-95 center backdrop-blur-sm"
|
||||
>
|
||||
<IconFluentArrowClockwise20Regular
|
||||
@click="refreshQRCode"
|
||||
class="cp text-4xl"/>
|
||||
<IconFluentArrowClockwise20Regular @click="refreshQRCode" class="cp text-4xl" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 center gap-space">
|
||||
<IconIxWechatLogo class="text-xl color-green"/>
|
||||
<IconIxWechatLogo class="text-xl color-green" />
|
||||
<span class="text-sm text-gray-600">微信扫码登录</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -675,12 +641,13 @@ function goHome(){
|
||||
</div>
|
||||
<div class="flex gap-space justify-end">
|
||||
<template v-if="importStep === ImportStep.CONFIRMATION">
|
||||
<PopConfirm :title="[
|
||||
{text:'您的用户数据将以压缩包自动下载到您的电脑中,以便您随时恢复',type:'normal'},
|
||||
{text:'随后网站的用户数据将被删除',type:'redBold'},
|
||||
{text:'是否确认继续?',type:'normal'},
|
||||
]"
|
||||
@confirm="forgetData"
|
||||
<PopConfirm
|
||||
:title="[
|
||||
{ text: '您的用户数据将以压缩包自动下载到您的电脑中,以便您随时恢复', type: 'normal' },
|
||||
{ text: '随后网站的用户数据将被删除', type: 'redBold' },
|
||||
{ text: '是否确认继续?', type: 'normal' },
|
||||
]"
|
||||
@confirm="forgetData"
|
||||
>
|
||||
<BaseButton type="info">不同步</BaseButton>
|
||||
</PopConfirm>
|
||||
@@ -692,22 +659,14 @@ function goHome(){
|
||||
<div>
|
||||
<div class="title text-align-center">正在导入中</div>
|
||||
<ol class="pl-4">
|
||||
<li>
|
||||
您的用户数据已自动下载到您的电脑中,以便随时恢复
|
||||
</li>
|
||||
<li>
|
||||
随后将开始数据同步
|
||||
</li>
|
||||
<li>
|
||||
如果您的数据量很大,这将是一个耗时操作
|
||||
</li>
|
||||
<li class="color-red-5 font-bold">
|
||||
请耐心等待,请勿关闭此页面
|
||||
</li>
|
||||
<li>您的用户数据已自动下载到您的电脑中,以便随时恢复</li>
|
||||
<li>随后将开始数据同步</li>
|
||||
<li>如果您的数据量很大,这将是一个耗时操作</li>
|
||||
<li class="color-red-5 font-bold">请耐心等待,请勿关闭此页面</li>
|
||||
</ol>
|
||||
<div class="flex items-center justify-between gap-2 mt-10">
|
||||
<span>当前进度: {{ reason }}</span>
|
||||
<IconEosIconsLoading class="text-xl"/>
|
||||
<IconEosIconsLoading class="text-xl" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -737,4 +696,5 @@ function goHome(){
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>]
|
||||
</template>
|
||||
]
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup lang="tsx">
|
||||
import { DictId, Sort } from '@/types/types.ts'
|
||||
|
||||
import { detail } from '@/apis'
|
||||
import BackIcon from '@/components/BackIcon.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
@@ -15,7 +13,7 @@ import Form from '@/components/base/form/Form.vue'
|
||||
import FormItem from '@/components/base/form/FormItem.vue'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import DeleteIcon from '@/components/icon/DeleteIcon.vue'
|
||||
import { AppEnv, LIB_JS_URL, TourConfig } from '@/config/env.ts'
|
||||
import { AppEnv, DictId, LIB_JS_URL, TourConfig } from '@/config/env.ts'
|
||||
import { getCurrentStudyWord } from '@/hooks/dict.ts'
|
||||
import EditBook from '@/pages/article/components/EditBook.vue'
|
||||
import PracticeSettingDialog from '@/pages/word/components/PracticeSettingDialog.vue'
|
||||
@@ -31,6 +29,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { wordDelete } from '@/apis/words.ts'
|
||||
import { copyOfficialDict } from '@/apis/dict.ts'
|
||||
import { PRACTICE_WORD_CACHE } from '@/utils/cache.ts'
|
||||
import { Sort } from '@/types/enum.ts'
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const base = useBaseStore()
|
||||
@@ -610,9 +609,14 @@ defineRender(() => {
|
||||
importLoading={importLoading}
|
||||
>
|
||||
{val => (
|
||||
<WordItem showTransPop={false}
|
||||
onClick={() => editWord(val.item)}
|
||||
index={val.index} showCollectIcon={false} showMarkIcon={false} item={val.item}>
|
||||
<WordItem
|
||||
showTransPop={false}
|
||||
onClick={() => editWord(val.item)}
|
||||
index={val.index}
|
||||
showCollectIcon={false}
|
||||
showMarkIcon={false}
|
||||
item={val.item}
|
||||
>
|
||||
{{
|
||||
prefix: () => val.checkbox(val.item),
|
||||
suffix: () => (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { _nextTick, groupBy, isMobile, loadJsLib, resourceWrap, useNav } from "@/utils";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import { DictResource } from "@/types/types.ts";
|
||||
import type { DictResource } from "@/types/types.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
|
||||
@@ -5,17 +5,7 @@ import Statistics from '@/pages/word/components/Statistics.vue'
|
||||
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import {
|
||||
Dict,
|
||||
PracticeData,
|
||||
ShortcutKey,
|
||||
TaskWords,
|
||||
Word,
|
||||
WordPracticeMode,
|
||||
WordPracticeModeStageMap,
|
||||
WordPracticeStage,
|
||||
WordPracticeType,
|
||||
} from '@/types/types.ts'
|
||||
import type { Dict, PracticeData, TaskWords, Word } from '@/types/types.ts'
|
||||
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from '@/hooks/event.ts'
|
||||
import useTheme from '@/hooks/theme.ts'
|
||||
import { getCurrentStudyWord, useWordOptions } from '@/hooks/dict.ts'
|
||||
@@ -35,12 +25,13 @@ import { getDefaultDict, getDefaultWord } from '@/types/func.ts'
|
||||
import ConflictNotice from '@/components/ConflictNotice.vue'
|
||||
import PracticeLayout from '@/components/PracticeLayout.vue'
|
||||
|
||||
import { AppEnv, DICT_LIST, LIB_JS_URL, TourConfig } from '@/config/env.ts'
|
||||
import { AppEnv, DICT_LIST, LIB_JS_URL, TourConfig, WordPracticeModeStageMap } from '@/config/env.ts'
|
||||
import { ToastInstance } from '@/components/base/toast/type.ts'
|
||||
import { watchOnce } from '@vueuse/core'
|
||||
import { setUserDictProp } from '@/apis'
|
||||
import GroupList from '@/pages/word/components/GroupList.vue'
|
||||
import { getPracticeWordCache, setPracticeWordCache } from '@/utils/cache.ts'
|
||||
import { ShortcutKey, WordPracticeMode, WordPracticeStage, WordPracticeType } from '@/types/enum.ts'
|
||||
|
||||
const { isWordCollect, toggleWordCollect, isWordSimple, toggleWordSimple } = useWordOptions()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -735,7 +726,7 @@ useEvents([
|
||||
<div class="practice-word mb-50">
|
||||
<div
|
||||
class="fixed z-1 top-4 w-full"
|
||||
style="left: calc(50vw + var(--aside-width) / 2 - var(--toolbar-width) / 2);width:var(--toolbar-width)"
|
||||
style="left: calc(50vw + var(--aside-width) / 2 - var(--toolbar-width) / 2); width: var(--toolbar-width)"
|
||||
v-if="settingStore.showNearWord"
|
||||
>
|
||||
<div class="center gap-2 cursor-pointer float-left" @click="prev" v-if="prevWord">
|
||||
|
||||
@@ -5,7 +5,7 @@ import BaseButton from '@/components/BaseButton.vue'
|
||||
import VolumeIcon from '@/components/icon/VolumeIcon.vue'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useBaseStore} from '@/stores/base.ts'
|
||||
import {Dict, Word} from '@/types/types.ts'
|
||||
import type {Dict, Word} from '@/types/types.ts'
|
||||
import {_getDictDataByUrl, shuffle} from '@/utils'
|
||||
import {useRuntimeStore} from '@/stores/runtime.ts'
|
||||
import {usePlayBeep, usePlayCorrect, usePlayWordAudio} from '@/hooks/sound.ts'
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
useNav,
|
||||
} from '@/utils'
|
||||
import BasePage from '@/components/BasePage.vue'
|
||||
import { DictResource, WordPracticeMode, WordPracticeModeNameMap } from '@/types/types.ts'
|
||||
import type { DictResource } from '@/types/types.ts'
|
||||
import { watch } from 'vue'
|
||||
import { getCurrentStudyWord } from '@/hooks/dict.ts'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
@@ -28,13 +28,14 @@ import PracticeSettingDialog from '@/pages/word/components/PracticeSettingDialog
|
||||
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, Origin, TourConfig } from '@/config/env.ts'
|
||||
import { AppEnv, DICT_LIST, Host, LIB_JS_URL, Origin, TourConfig, WordPracticeModeNameMap } 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 { deleteDict } from '@/apis/dict.ts'
|
||||
import OptionButton from '@/components/base/OptionButton.vue'
|
||||
import { getPracticeWordCache, setPracticeWordCache } from '@/utils/cache.ts'
|
||||
import { WordPracticeMode } from '@/types/enum.ts'
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -2,24 +2,16 @@
|
||||
import { inject, Ref } from 'vue'
|
||||
import { usePracticeStore } from '@/stores/practice.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import {
|
||||
PracticeData,
|
||||
ShortcutKey,
|
||||
TaskWords,
|
||||
WordPracticeMode,
|
||||
WordPracticeModeNameMap,
|
||||
WordPracticeModeStageMap,
|
||||
WordPracticeStage,
|
||||
WordPracticeStageNameMap,
|
||||
} from '@/types/types.ts'
|
||||
import type { PracticeData, TaskWords } from '@/types/types.ts'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import Tooltip from '@/components/base/Tooltip.vue'
|
||||
import Progress from '@/components/base/Progress.vue'
|
||||
import SettingDialog from '@/components/setting/SettingDialog.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import VolumeSettingMiniDialog from '@/pages/word/components/VolumeSettingMiniDialog.vue'
|
||||
import StageProgress from '@/components/StageProgress.vue'
|
||||
import { ShortcutKey, WordPracticeMode, WordPracticeStage } from '@/types/enum.ts'
|
||||
import { WordPracticeModeNameMap, WordPracticeModeStageMap, WordPracticeStageNameMap } from '@/config/env.ts'
|
||||
|
||||
const statStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
@@ -97,9 +89,24 @@ const stages = $computed(() => {
|
||||
)
|
||||
) {
|
||||
const stages = [
|
||||
{ name: `新词:${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`, ratio: 33, percentage: 0, active: false },
|
||||
{ name: `上次学习:${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`, ratio: 33, percentage: 0, active: false },
|
||||
{ name: `之前学习:${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`, ratio: 33, percentage: 0, active: false },
|
||||
{
|
||||
name: `新词:${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`,
|
||||
ratio: 33,
|
||||
percentage: 0,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
name: `上次学习:${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`,
|
||||
ratio: 33,
|
||||
percentage: 0,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
name: `之前学习:${WordPracticeModeNameMap[settingStore.wordPracticeMode]}`,
|
||||
ratio: 33,
|
||||
percentage: 0,
|
||||
active: false,
|
||||
},
|
||||
]
|
||||
|
||||
// 设置已完成阶段的百分比和比例
|
||||
@@ -241,7 +248,7 @@ const stages = $computed(() => {
|
||||
<div class="flex gap-2 justify-center items-center" id="toolbar-icons">
|
||||
<SettingDialog type="word" />
|
||||
|
||||
<VolumeSettingMiniDialog/>
|
||||
<VolumeSettingMiniDialog />
|
||||
|
||||
<BaseIcon
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.Free"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import BaseTable from "@/components/BaseTable.vue";
|
||||
import WordItem from "@/components/WordItem.vue";
|
||||
import { defineAsyncComponent } from "vue";
|
||||
import { TaskWords } from "@/types/types.ts";
|
||||
import type { TaskWords } from "@/types/types.ts";
|
||||
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { ShortcutKey, Statistics, TaskWords, WordPracticeMode } from '@/types/types.ts'
|
||||
import type { Statistics, TaskWords } from '@/types/types.ts'
|
||||
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { usePracticeStore } from '@/stores/practice.ts'
|
||||
@@ -15,6 +15,7 @@ import ChannelIcons from '@/components/ChannelIcons/ChannelIcons.vue'
|
||||
import { AppEnv } from '@/config/env.ts'
|
||||
import { addStat } from '@/apis'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { ShortcutKey, WordPracticeMode } from '@/types/enum.ts'
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
dayjs.extend(isBetween)
|
||||
@@ -78,13 +79,9 @@ watch(model, async newVal => {
|
||||
if (settingStore.wordPracticeMode !== WordPracticeMode.Shuffle) {
|
||||
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex + statStore.newWordNumber
|
||||
// 检查已忽略的单词数量,是否全部完成
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][
|
||||
settingStore.ignoreSimpleWord ? 0 : 1
|
||||
]
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
|
||||
// 忽略单词数
|
||||
const ignoreCount = ignoreList.filter(word =>
|
||||
store.sdict.words.some(w => w.word.toLowerCase() === word)
|
||||
).length
|
||||
const ignoreCount = ignoreList.filter(word => store.sdict.words.some(w => w.word.toLowerCase() === word)).length
|
||||
// 如果lastLearnIndex已经超过可学单词数,则判定完成
|
||||
if (store.sdict.lastLearnIndex + ignoreCount >= store.sdict.length) {
|
||||
dictIsEnd = true
|
||||
@@ -156,13 +153,7 @@ calcWeekList() // 新增:计算本周学习记录
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="model"
|
||||
:close-on-click-bg="false"
|
||||
:header="false"
|
||||
:keyboard="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<Dialog v-model="model" :close-on-click-bg="false" :header="false" :keyboard="false" :show-close="false">
|
||||
<div class="p-8 pr-3 bg-[var(--bg-card-primary)] rounded-2xl space-y-6">
|
||||
<!-- Header Section -->
|
||||
<div class="text-center relative">
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ShortcutKey, Word, WordPracticeStage, WordPracticeType } from '@/types/types.ts'
|
||||
import type { Word } from '@/types/types.ts'
|
||||
import VolumeIcon from '@/components/icon/VolumeIcon.vue'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import {
|
||||
usePlayBeep,
|
||||
usePlayCorrect,
|
||||
usePlayKeyboardAudio,
|
||||
usePlayWordAudio,
|
||||
} from '@/hooks/sound.ts'
|
||||
import { usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio } from '@/hooks/sound.ts'
|
||||
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
|
||||
import { onMounted, onUnmounted, watch } from 'vue'
|
||||
import SentenceHightLightWord from '@/pages/word/components/SentenceHightLightWord.vue'
|
||||
@@ -18,6 +13,7 @@ import BaseButton from '@/components/BaseButton.vue'
|
||||
import Space from '@/pages/article/components/Space.vue'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import Tooltip from '@/components/base/Tooltip.vue'
|
||||
import { ShortcutKey, WordPracticeStage, WordPracticeType } from '@/types/enum.ts'
|
||||
|
||||
interface IProps {
|
||||
word: Word
|
||||
@@ -308,18 +304,12 @@ async function onTyping(e: KeyboardEvent) {
|
||||
wordCompletedTime = Date.now() // 记录单词完成的时间戳
|
||||
playCorrect()
|
||||
if (
|
||||
[WordPracticeType.Listen, WordPracticeType.Identify].includes(
|
||||
settingStore.wordPracticeType
|
||||
) &&
|
||||
[WordPracticeType.Listen, WordPracticeType.Identify].includes(settingStore.wordPracticeType) &&
|
||||
!showWordResult
|
||||
) {
|
||||
showWordResult = true
|
||||
}
|
||||
if (
|
||||
[WordPracticeType.FollowWrite, WordPracticeType.Spell].includes(
|
||||
settingStore.wordPracticeType
|
||||
)
|
||||
) {
|
||||
if ([WordPracticeType.FollowWrite, WordPracticeType.Spell].includes(settingStore.wordPracticeType)) {
|
||||
if (settingStore.autoNextWord) {
|
||||
if (settingStore.repeatCount == 100) {
|
||||
if (settingStore.repeatCustomCount <= wordRepeatCount + 1) {
|
||||
@@ -476,11 +466,9 @@ useEvents([
|
||||
class="phonetic"
|
||||
:class="
|
||||
(settingStore.dictation ||
|
||||
[
|
||||
WordPracticeType.Spell,
|
||||
WordPracticeType.Listen,
|
||||
WordPracticeType.Dictation,
|
||||
].includes(settingStore.wordPracticeType)) &&
|
||||
[WordPracticeType.Spell, WordPracticeType.Listen, WordPracticeType.Dictation].includes(
|
||||
settingStore.wordPracticeType
|
||||
)) &&
|
||||
!showFullWord &&
|
||||
!showWordResult &&
|
||||
'word-shadow'
|
||||
@@ -499,9 +487,7 @@ useEvents([
|
||||
|
||||
<Tooltip
|
||||
:title="
|
||||
settingStore.dictation
|
||||
? `可以按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`
|
||||
: ''
|
||||
settingStore.dictation ? `可以按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案` : ''
|
||||
"
|
||||
>
|
||||
<div
|
||||
@@ -526,12 +512,7 @@ useEvents([
|
||||
>
|
||||
<template v-for="i in input">
|
||||
<span class="l" v-if="i !== ' '">{{ i }}</span>
|
||||
<Space
|
||||
class="l"
|
||||
v-else
|
||||
:is-wrong="showWordResult ? !right : false"
|
||||
:is-wait="!showWordResult"
|
||||
/>
|
||||
<Space class="l" v-else :is-wrong="showWordResult ? !right : false" :is-wait="!showWordResult" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -605,10 +586,7 @@ useEvents([
|
||||
:word="word.word"
|
||||
:dictation="!(!settingStore.dictation || showFullWord || showWordResult)"
|
||||
/>
|
||||
<div
|
||||
class="text-base anim"
|
||||
v-opacity="settingStore.translate || showFullWord || showWordResult"
|
||||
>
|
||||
<div class="text-base anim" v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
{{ item.cn }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -627,10 +605,7 @@ useEvents([
|
||||
:word="word.word"
|
||||
:dictation="!(!settingStore.dictation || showFullWord || showWordResult)"
|
||||
/>
|
||||
<div
|
||||
class="cn anim"
|
||||
v-opacity="settingStore.translate || showFullWord || showWordResult"
|
||||
>
|
||||
<div class="cn anim" v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
{{ item.cn }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -647,16 +622,10 @@ useEvents([
|
||||
<div class="flex" v-for="item in word.synos">
|
||||
<div class="pos line-height-1.4rem!">{{ item.pos }}</div>
|
||||
<div>
|
||||
<div
|
||||
class="cn anim"
|
||||
v-opacity="settingStore.translate || showFullWord || showWordResult"
|
||||
>
|
||||
<div class="cn anim" v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
{{ item.cn }}
|
||||
</div>
|
||||
<div
|
||||
class="anim"
|
||||
v-opacity="!settingStore.dictation || showFullWord || showWordResult"
|
||||
>
|
||||
<div class="anim" v-opacity="!settingStore.dictation || showFullWord || showWordResult">
|
||||
<span class="en" v-for="(i, j) in item.ws">
|
||||
{{ i }} {{ j !== item.ws.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
@@ -670,9 +639,7 @@ useEvents([
|
||||
|
||||
<div
|
||||
class="anim"
|
||||
v-opacity="
|
||||
(settingStore.translate && !settingStore.dictation) || showFullWord || showWordResult
|
||||
"
|
||||
v-opacity="(settingStore.translate && !settingStore.dictation) || showFullWord || showWordResult"
|
||||
>
|
||||
<template v-if="word?.etymology?.length">
|
||||
<div class="line-white my-3"></div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { Dict, DictId, Word } from '../types/types.ts'
|
||||
import { Dict, Word } from '../types/types.ts'
|
||||
import { _getStudyProgress, checkAndUpgradeSaveDict, shakeCommonDict } from '@/utils'
|
||||
import { shallowReactive } from 'vue'
|
||||
import { getDefaultDict } from '@/types/func.ts'
|
||||
import { get, set } from 'idb-keyval'
|
||||
import { AppEnv, SAVE_DICT_KEY } from '@/config/env.ts'
|
||||
import { AppEnv, DictId, SAVE_DICT_KEY } from '@/config/env.ts'
|
||||
import { add2MyDict, dictListVersion, myDictList } from '@/apis'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { WordPracticeModeStageMap, WordPracticeStage, WordPracticeStageNameMap } from '@/types/types.ts'
|
||||
import { useSettingStore } from './setting'
|
||||
import {WordPracticeStage} from "@/types/enum.ts";
|
||||
import { WordPracticeModeStageMap, WordPracticeStageNameMap } from '@/config/env.ts'
|
||||
|
||||
export interface PracticeState {
|
||||
stage: WordPracticeStage
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {defineStore} from "pinia"
|
||||
import {Dict} from "@/types/types.ts";
|
||||
import type {Dict} from "@/types/types.ts";
|
||||
import {getDefaultDict} from "@/types/func.ts";
|
||||
|
||||
export interface RuntimeState {
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
import { defineStore } from "pinia"
|
||||
import { checkAndUpgradeSaveSetting, cloneDeep } from "@/utils";
|
||||
import { DefaultShortcutKeyMap, WordPracticeMode, WordPracticeType } from "@/types/types.ts";
|
||||
import { get } from "idb-keyval";
|
||||
import { AppEnv, SAVE_SETTING_KEY } from "@/config/env.ts";
|
||||
import { getSetting } from "@/apis";
|
||||
import { AppEnv, DefaultShortcutKeyMap, SAVE_SETTING_KEY } from '@/config/env.ts'
|
||||
import { getSetting } from '@/apis'
|
||||
import { WordPracticeMode, WordPracticeType } from '@/types/enum.ts'
|
||||
|
||||
export interface SettingState {
|
||||
soundType: string,
|
||||
soundType: string
|
||||
|
||||
wordSound: boolean,
|
||||
wordSoundVolume: number,
|
||||
wordSoundSpeed: number,
|
||||
wordReviewRatio:number //单词复习比例
|
||||
wordSound: boolean
|
||||
wordSoundVolume: number
|
||||
wordSoundSpeed: number
|
||||
wordReviewRatio: number //单词复习比例
|
||||
|
||||
articleSound: boolean,
|
||||
articleAutoPlayNext: boolean,
|
||||
articleSoundVolume: number,
|
||||
articleSoundSpeed: number,
|
||||
articleSound: boolean
|
||||
articleAutoPlayNext: boolean
|
||||
articleSoundVolume: number
|
||||
articleSoundSpeed: number
|
||||
|
||||
keyboardSound: boolean,
|
||||
keyboardSoundVolume: number,
|
||||
keyboardSoundFile: string,
|
||||
keyboardSound: boolean
|
||||
keyboardSoundVolume: number
|
||||
keyboardSoundFile: string
|
||||
|
||||
effectSound: boolean,
|
||||
effectSoundVolume: number,
|
||||
effectSound: boolean
|
||||
effectSoundVolume: number
|
||||
|
||||
repeatCount: number, //重复次数
|
||||
repeatCustomCount?: number, //自定义重复次数
|
||||
dictation: boolean,//显示默写
|
||||
translate: boolean, //显示翻译
|
||||
repeatCount: number //重复次数
|
||||
repeatCustomCount?: number //自定义重复次数
|
||||
dictation: boolean //显示默写
|
||||
translate: boolean //显示翻译
|
||||
showNearWord: boolean //显示上/下一个词
|
||||
ignoreCase: boolean //忽略大小写
|
||||
allowWordTip: boolean //默写时时否允许查看提示
|
||||
waitTimeForChangeWord: number // 切下一个词的等待时间
|
||||
fontSize: {
|
||||
articleForeignFontSize: number,
|
||||
articleTranslateFontSize: number,
|
||||
wordForeignFontSize: number,
|
||||
wordTranslateFontSize: number,
|
||||
},
|
||||
showToolbar: boolean, //收起/展开工具栏
|
||||
showPanel: boolean, // 收起/展开面板
|
||||
sideExpand: boolean, //收起/展开左侧侧边栏
|
||||
theme: string,
|
||||
shortcutKeyMap: Record<string, string>,
|
||||
articleForeignFontSize: number
|
||||
articleTranslateFontSize: number
|
||||
wordForeignFontSize: number
|
||||
wordTranslateFontSize: number
|
||||
}
|
||||
showToolbar: boolean //收起/展开工具栏
|
||||
showPanel: boolean // 收起/展开面板
|
||||
sideExpand: boolean //收起/展开左侧侧边栏
|
||||
theme: string
|
||||
shortcutKeyMap: Record<string, string>
|
||||
first: boolean
|
||||
firstTime: number
|
||||
load: boolean
|
||||
|
||||
103
src/types/enum.ts
Normal file
103
src/types/enum.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
export enum DictType {
|
||||
collect = 'collect',
|
||||
simple = 'simple',
|
||||
wrong = 'wrong',
|
||||
known = 'known',
|
||||
word = 'word',
|
||||
article = 'article',
|
||||
}
|
||||
export enum Sort {
|
||||
normal = 0,
|
||||
random = 1,
|
||||
reverse = 2,
|
||||
reverseAll = 3,
|
||||
randomAll = 4,
|
||||
}
|
||||
|
||||
export enum ShortcutKey {
|
||||
ShowWord = 'ShowWord',
|
||||
EditArticle = 'EditArticle',
|
||||
Next = 'Next',
|
||||
Previous = 'Previous',
|
||||
ToggleSimple = 'ToggleSimple',
|
||||
ToggleCollect = 'ToggleCollect',
|
||||
NextChapter = 'NextChapter',
|
||||
PreviousChapter = 'PreviousChapter',
|
||||
RepeatChapter = 'RepeatChapter',
|
||||
DictationChapter = 'DictationChapter',
|
||||
PlayWordPronunciation = 'PlayWordPronunciation',
|
||||
ToggleShowTranslate = 'ToggleShowTranslate',
|
||||
ToggleDictation = 'ToggleDictation',
|
||||
ToggleTheme = 'ToggleTheme',
|
||||
ToggleConciseMode = 'ToggleConciseMode',
|
||||
TogglePanel = 'TogglePanel',
|
||||
RandomWrite = 'RandomWrite',
|
||||
KnowWord = 'KnowWord',
|
||||
UnknownWord = 'UnknownWord',
|
||||
}
|
||||
|
||||
export enum TranslateEngine {
|
||||
Baidu = 0,
|
||||
}
|
||||
|
||||
export enum PracticeArticleWordType {
|
||||
Symbol,
|
||||
Number,
|
||||
Word,
|
||||
}
|
||||
|
||||
//练习模式
|
||||
export enum WordPracticeMode {
|
||||
System = 0,
|
||||
Free = 1,
|
||||
IdentifyOnly = 2, // 独立自测模式
|
||||
DictationOnly = 3, // 独立默写模式
|
||||
ListenOnly = 4, // 独立听写模式
|
||||
Shuffle = 5, // 随机复习模式
|
||||
Review = 6, // 复习模式
|
||||
}
|
||||
|
||||
//练习类型
|
||||
export enum WordPracticeType {
|
||||
FollowWrite, //跟写
|
||||
Spell,
|
||||
Identify,
|
||||
Listen,
|
||||
Dictation,
|
||||
}
|
||||
|
||||
export enum CodeType {
|
||||
Login = 0,
|
||||
Register = 1,
|
||||
ResetPwd = 2,
|
||||
ChangeEmail = 3,
|
||||
ChangePhoneNew = 4,
|
||||
ChangePhoneOld = 5,
|
||||
}
|
||||
|
||||
export enum ImportStatus {
|
||||
Idle = 0,
|
||||
Success = 1,
|
||||
Fail = 2,
|
||||
}
|
||||
|
||||
//练习阶段
|
||||
export enum WordPracticeStage {
|
||||
FollowWriteNewWord = 0,
|
||||
IdentifyNewWord = 1,
|
||||
ListenNewWord = 2,
|
||||
DictationNewWord = 3,
|
||||
|
||||
FollowWriteReview = 4,
|
||||
IdentifyReview = 5,
|
||||
ListenReview = 6,
|
||||
DictationReview = 7,
|
||||
|
||||
FollowWriteReviewAll = 8,
|
||||
IdentifyReviewAll = 9,
|
||||
ListenReviewAll = 10,
|
||||
DictationReviewAll = 11,
|
||||
|
||||
Shuffle = 12,
|
||||
Complete = 13,
|
||||
}
|
||||
@@ -1,25 +1,26 @@
|
||||
import { Article, ArticleWord, Dict, DictType, PracticeArticleWordType, Word } from "@/types/types.ts";
|
||||
import type { Article, ArticleWord, Dict, Word } from '@/types/types.ts'
|
||||
import { shallowReactive } from "vue";
|
||||
import { cloneDeep } from "@/utils";
|
||||
import { nanoid } from "nanoid";
|
||||
import { DictType, PracticeArticleWordType } from '@/types/enum.ts'
|
||||
|
||||
export function getDefaultWord(val: Partial<Word> = {}): Word {
|
||||
return {
|
||||
custom: false,
|
||||
id: nanoid(6),
|
||||
"word": "",
|
||||
"phonetic0": "",
|
||||
"phonetic1": "",
|
||||
"trans": [],
|
||||
"sentences": [],
|
||||
"phrases": [],
|
||||
"synos": [],
|
||||
"relWords": {
|
||||
"root": "",
|
||||
"rels": []
|
||||
word: '',
|
||||
phonetic0: '',
|
||||
phonetic1: '',
|
||||
trans: [],
|
||||
sentences: [],
|
||||
phrases: [],
|
||||
synos: [],
|
||||
relWords: {
|
||||
root: '',
|
||||
rels: [],
|
||||
},
|
||||
"etymology": [],
|
||||
...val
|
||||
etymology: [],
|
||||
...val,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DictType, PracticeArticleWordType } from '@/types/enum.ts'
|
||||
|
||||
export type Word = {
|
||||
id?: string
|
||||
custom?: boolean
|
||||
@@ -37,20 +39,9 @@ export type Word = {
|
||||
}[]
|
||||
}
|
||||
|
||||
export const PronunciationApi = 'https://dict.youdao.com/dictvoice?audio='
|
||||
|
||||
export type TranslateLanguageType = 'en' | 'zh-CN' | 'ja' | 'de' | 'common' | ''
|
||||
export type LanguageType = 'en' | 'ja' | 'de' | 'code'
|
||||
|
||||
export enum DictType {
|
||||
collect = 'collect',
|
||||
simple = 'simple',
|
||||
wrong = 'wrong',
|
||||
known = 'known',
|
||||
word = 'word',
|
||||
article = 'article',
|
||||
}
|
||||
|
||||
export interface ArticleWord extends Word {
|
||||
nextSpace: boolean
|
||||
symbolPosition: 'start' | 'end' | ''
|
||||
@@ -107,62 +98,6 @@ export interface Statistics {
|
||||
title: string //文章标题
|
||||
}
|
||||
|
||||
export enum Sort {
|
||||
normal = 0,
|
||||
random = 1,
|
||||
reverse = 2,
|
||||
reverseAll = 3,
|
||||
randomAll = 4,
|
||||
}
|
||||
|
||||
export enum ShortcutKey {
|
||||
ShowWord = 'ShowWord',
|
||||
EditArticle = 'EditArticle',
|
||||
Next = 'Next',
|
||||
Previous = 'Previous',
|
||||
ToggleSimple = 'ToggleSimple',
|
||||
ToggleCollect = 'ToggleCollect',
|
||||
NextChapter = 'NextChapter',
|
||||
PreviousChapter = 'PreviousChapter',
|
||||
RepeatChapter = 'RepeatChapter',
|
||||
DictationChapter = 'DictationChapter',
|
||||
PlayWordPronunciation = 'PlayWordPronunciation',
|
||||
ToggleShowTranslate = 'ToggleShowTranslate',
|
||||
ToggleDictation = 'ToggleDictation',
|
||||
ToggleTheme = 'ToggleTheme',
|
||||
ToggleConciseMode = 'ToggleConciseMode',
|
||||
TogglePanel = 'TogglePanel',
|
||||
RandomWrite = 'RandomWrite',
|
||||
KnowWord = 'KnowWord',
|
||||
UnknownWord = 'UnknownWord',
|
||||
}
|
||||
|
||||
export const DefaultShortcutKeyMap = {
|
||||
[ShortcutKey.EditArticle]: 'Ctrl+E',
|
||||
[ShortcutKey.ShowWord]: 'Escape',
|
||||
[ShortcutKey.Previous]: 'Alt+⬅',
|
||||
[ShortcutKey.Next]: 'Tab',
|
||||
[ShortcutKey.ToggleSimple]: '`',
|
||||
[ShortcutKey.ToggleCollect]: 'Enter',
|
||||
[ShortcutKey.PreviousChapter]: 'Ctrl+⬅',
|
||||
[ShortcutKey.NextChapter]: 'Ctrl+➡',
|
||||
[ShortcutKey.RepeatChapter]: 'Ctrl+Enter',
|
||||
[ShortcutKey.DictationChapter]: 'Alt+Enter',
|
||||
[ShortcutKey.PlayWordPronunciation]: 'Ctrl+P',
|
||||
[ShortcutKey.ToggleShowTranslate]: 'Ctrl+Z',
|
||||
[ShortcutKey.ToggleDictation]: 'Ctrl+I',
|
||||
[ShortcutKey.ToggleTheme]: 'Ctrl+Q',
|
||||
[ShortcutKey.ToggleConciseMode]: 'Ctrl+M',
|
||||
[ShortcutKey.TogglePanel]: 'Ctrl+L',
|
||||
[ShortcutKey.RandomWrite]: 'Ctrl+R',
|
||||
[ShortcutKey.KnowWord]: '1',
|
||||
[ShortcutKey.UnknownWord]: '2',
|
||||
}
|
||||
|
||||
export enum TranslateEngine {
|
||||
Baidu = 0,
|
||||
}
|
||||
|
||||
export type DictResource = {
|
||||
id: string
|
||||
name: string
|
||||
@@ -202,11 +137,6 @@ export interface ArticleItem {
|
||||
index: number
|
||||
}
|
||||
|
||||
export const SlideType = {
|
||||
HORIZONTAL: 0,
|
||||
VERTICAL: 1,
|
||||
}
|
||||
|
||||
export interface PracticeData {
|
||||
index: number
|
||||
words: Word[]
|
||||
@@ -220,143 +150,3 @@ export interface TaskWords {
|
||||
write: Word[]
|
||||
shuffle: Word[]
|
||||
}
|
||||
|
||||
export class DictId {
|
||||
static wordCollect = 'wordCollect'
|
||||
static wordWrong = 'wordWrong'
|
||||
static wordKnown = 'wordKnown'
|
||||
static articleCollect = 'articleCollect'
|
||||
}
|
||||
|
||||
export enum PracticeArticleWordType {
|
||||
Symbol,
|
||||
Number,
|
||||
Word,
|
||||
}
|
||||
|
||||
//练习模式
|
||||
export enum WordPracticeMode {
|
||||
System = 0,
|
||||
Free = 1,
|
||||
IdentifyOnly = 2, // 独立自测模式
|
||||
DictationOnly = 3, // 独立默写模式
|
||||
ListenOnly = 4, // 独立听写模式
|
||||
Shuffle = 5, // 随机复习模式
|
||||
Review = 6, // 复习模式
|
||||
}
|
||||
|
||||
//练习类型
|
||||
export enum WordPracticeType {
|
||||
FollowWrite, //跟写
|
||||
Spell,
|
||||
Identify,
|
||||
Listen,
|
||||
Dictation,
|
||||
}
|
||||
|
||||
export enum CodeType {
|
||||
Login = 0,
|
||||
Register = 1,
|
||||
ResetPwd = 2,
|
||||
ChangeEmail = 3,
|
||||
ChangePhoneNew = 4,
|
||||
ChangePhoneOld = 5,
|
||||
}
|
||||
|
||||
export enum ImportStatus {
|
||||
Idle = 0,
|
||||
Success = 1,
|
||||
Fail = 2,
|
||||
}
|
||||
|
||||
//练习阶段
|
||||
export enum WordPracticeStage {
|
||||
FollowWriteNewWord = 0,
|
||||
IdentifyNewWord = 1,
|
||||
ListenNewWord = 2,
|
||||
DictationNewWord = 3,
|
||||
|
||||
FollowWriteReview = 4,
|
||||
IdentifyReview = 5,
|
||||
ListenReview = 6,
|
||||
DictationReview = 7,
|
||||
|
||||
FollowWriteReviewAll = 8,
|
||||
IdentifyReviewAll = 9,
|
||||
ListenReviewAll = 10,
|
||||
DictationReviewAll = 11,
|
||||
|
||||
Shuffle = 12,
|
||||
Complete = 13,
|
||||
}
|
||||
|
||||
export const WordPracticeModeStageMap: Record<WordPracticeMode, WordPracticeStage[]> = {
|
||||
[WordPracticeMode.Free]: [WordPracticeStage.FollowWriteNewWord, WordPracticeStage.Complete],
|
||||
[WordPracticeMode.IdentifyOnly]: [
|
||||
WordPracticeStage.IdentifyNewWord,
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.DictationOnly]: [
|
||||
WordPracticeStage.DictationNewWord,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.ListenOnly]: [
|
||||
WordPracticeStage.ListenNewWord,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.System]: [
|
||||
WordPracticeStage.FollowWriteNewWord,
|
||||
WordPracticeStage.ListenNewWord,
|
||||
WordPracticeStage.DictationNewWord,
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.Shuffle]: [WordPracticeStage.Shuffle, WordPracticeStage.Complete],
|
||||
[WordPracticeMode.Review]: [
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
}
|
||||
|
||||
export const WordPracticeStageNameMap: Record<WordPracticeStage, string> = {
|
||||
[WordPracticeStage.FollowWriteNewWord]: '跟写新词',
|
||||
[WordPracticeStage.IdentifyNewWord]: '自测新词',
|
||||
[WordPracticeStage.ListenNewWord]: '听写新词',
|
||||
[WordPracticeStage.DictationNewWord]: '默写新词',
|
||||
[WordPracticeStage.FollowWriteReview]: '跟写上次学习',
|
||||
[WordPracticeStage.IdentifyReview]: '自测上次学习',
|
||||
[WordPracticeStage.ListenReview]: '听写上次学习',
|
||||
[WordPracticeStage.DictationReview]: '默写上次学习',
|
||||
[WordPracticeStage.FollowWriteReviewAll]: '跟写之前学习',
|
||||
[WordPracticeStage.IdentifyReviewAll]: '自测之前学习',
|
||||
[WordPracticeStage.ListenReviewAll]: '听写之前学习',
|
||||
[WordPracticeStage.DictationReviewAll]: '默写之前学习',
|
||||
[WordPracticeStage.Complete]: '完成学习',
|
||||
[WordPracticeStage.Shuffle]: '随机复习',
|
||||
}
|
||||
|
||||
export const WordPracticeModeNameMap: Record<WordPracticeMode, string> = {
|
||||
[WordPracticeMode.System]: '学习',
|
||||
[WordPracticeMode.Free]: '自由练习',
|
||||
[WordPracticeMode.IdentifyOnly]: '自测',
|
||||
[WordPracticeMode.DictationOnly]: '默写',
|
||||
[WordPracticeMode.ListenOnly]: '听写',
|
||||
[WordPracticeMode.Shuffle]: '随机复习',
|
||||
[WordPracticeMode.Review]: '复习',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PracticeData, TaskWords } from '@/types/types.ts'
|
||||
import type { PracticeData, TaskWords } from '@/types/types.ts'
|
||||
import { PracticeState } from '@/stores/practice.ts'
|
||||
import { IS_DEV } from '@/config/env'
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { BaseState, getDefaultBaseState, useBaseStore } from '@/stores/base.ts'
|
||||
import { getDefaultSettingState, SettingState } from '@/stores/setting.ts'
|
||||
import { Dict, DictId, DictResource, DictType } from '@/types/types.ts'
|
||||
import type { Dict, DictResource } from '@/types/types.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRuntimeStore } from '@/stores/runtime.ts'
|
||||
import dayjs from 'dayjs'
|
||||
import { AppEnv, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from '@/config/env.ts'
|
||||
import { AppEnv, DictId, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from '@/config/env.ts'
|
||||
import { nextTick } from 'vue'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import { getDefaultDict, getDefaultWord } from '@/types/func.ts'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
import {DictType} from "@/types/enum.ts";
|
||||
|
||||
dayjs.extend(duration)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user