feat:拆分页面
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -7,7 +7,7 @@ export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BackIcon: typeof import('./src/components/BackIcon.vue')['default']
|
||||
BackIcon: typeof import('./src/components/icon/BackIcon.vue')['default']
|
||||
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
|
||||
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
|
||||
Close: typeof import('./src/components/icon/Close.vue')['default']
|
||||
|
||||
@@ -5,7 +5,6 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import * as localforage from "localforage";
|
||||
import SettingDialog from "@/pages/pc/components/dialog/SettingDialog.vue";
|
||||
import CollectNotice from "@/pages/pc/components/CollectNotice.vue";
|
||||
import {SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/utils/const.ts";
|
||||
import {isMobile, shakeCommonDict} from "@/utils";
|
||||
@@ -76,7 +75,6 @@ watch(() => route.path, (to, from) => {
|
||||
</transition>
|
||||
</router-view>
|
||||
<CollectNotice/>
|
||||
<SettingDialog/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -64,10 +64,6 @@
|
||||
|
||||
}
|
||||
|
||||
.side-expand {
|
||||
//--panel-margin-left: 10px !important;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--color-main-bg: rgba(14, 18, 23, 1);
|
||||
--color-second-bg: rgb(30, 31, 34);
|
||||
@@ -467,7 +463,7 @@ footer {
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply rounded-xl bg-white p-4 mb-5 box-border;
|
||||
@apply rounded-xl bg-white p-4 mb-5 box-border relative;
|
||||
}
|
||||
|
||||
.center {
|
||||
@@ -492,3 +488,7 @@ footer {
|
||||
width: 100%;
|
||||
border-bottom: 1px dashed #ababab;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
646
src/pages/pc/Setting.vue
Normal file
646
src/pages/pc/Setting.vue
Normal file
@@ -0,0 +1,646 @@
|
||||
<script setup lang="ts">
|
||||
import {Icon} from '@iconify/vue';
|
||||
import {ref, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
|
||||
import {getShortcutKey, useDisableEventListener, useEventListener} from "@/hooks/event.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {DefaultShortcutKeyMap, ShortcutKey} from "@/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions} from "@/utils/const.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {saveAs} from "file-saver";
|
||||
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict} from "@/utils";
|
||||
import {GITHUB} from "@/config/ENV.ts";
|
||||
import dayjs from "dayjs";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
}>()
|
||||
|
||||
const tabIndex = $ref(0)
|
||||
const settingStore = useSettingStore()
|
||||
const store = useBaseStore()
|
||||
//@ts-ignore
|
||||
const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
|
||||
|
||||
useDisableEventListener(() => undefined)
|
||||
useWatchAllSound()
|
||||
|
||||
let editShortcutKey = $ref('')
|
||||
|
||||
const disabledDefaultKeyboardEvent = $computed(() => {
|
||||
return editShortcutKey && tabIndex === 2
|
||||
})
|
||||
|
||||
watch(() => disabledDefaultKeyboardEvent, v => {
|
||||
emit('toggleDisabledDialogEscKey', !!v)
|
||||
})
|
||||
|
||||
useEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (!disabledDefaultKeyboardEvent) return
|
||||
e.preventDefault()
|
||||
|
||||
let shortcutKey = getShortcutKey(e)
|
||||
// console.log('e', e, e.keyCode, e.ctrlKey, e.altKey, e.shiftKey)
|
||||
// console.log('key', shortcutKey)
|
||||
|
||||
// if (shortcutKey[shortcutKey.length-1] === '+') {
|
||||
// settingStore.shortcutKeyMap[editShortcutKey] = DefaultShortcutKeyMap[editShortcutKey]
|
||||
// return ElMessage.warning('设备失败!')
|
||||
// }
|
||||
|
||||
if (editShortcutKey) {
|
||||
if (shortcutKey === 'Delete') {
|
||||
settingStore.shortcutKeyMap[editShortcutKey] = ''
|
||||
} else {
|
||||
for (const [k, v] of Object.entries(settingStore.shortcutKeyMap)) {
|
||||
if (v === shortcutKey && k !== editShortcutKey) {
|
||||
settingStore.shortcutKeyMap[editShortcutKey] = DefaultShortcutKeyMap[editShortcutKey]
|
||||
return ElMessage.warning('快捷键重复!')
|
||||
}
|
||||
}
|
||||
settingStore.shortcutKeyMap[editShortcutKey] = shortcutKey
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function resetShortcutKeyMap() {
|
||||
editShortcutKey = ''
|
||||
settingStore.shortcutKeyMap = cloneDeep(DefaultShortcutKeyMap)
|
||||
ElMessage.success('恢复成功')
|
||||
}
|
||||
|
||||
function exportData() {
|
||||
let data = {
|
||||
version: EXPORT_DATA_KEY.version,
|
||||
val: {
|
||||
setting: {
|
||||
version: SAVE_SETTING_KEY.version,
|
||||
val: settingStore.$state
|
||||
},
|
||||
dict: {
|
||||
version: SAVE_DICT_KEY.version,
|
||||
val: shakeCommonDict(store.$state)
|
||||
}
|
||||
}
|
||||
}
|
||||
let blob = new Blob([JSON.stringify(data)], {type: "text/plain;charset=utf-8"});
|
||||
saveAs(blob, `${APP_NAME}-User-Data-${dayjs().format('YYYY-MM-DD HH-mm-ss')}.json`);
|
||||
ElMessage.success('导出成功!')
|
||||
}
|
||||
|
||||
function importData(e) {
|
||||
let file = e.target.files[0]
|
||||
if (!file) return
|
||||
// no()
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (v) {
|
||||
let str: any = v.target.result;
|
||||
if (str) {
|
||||
let obj = {
|
||||
version: -1,
|
||||
val: {
|
||||
setting: {},
|
||||
dict: {},
|
||||
}
|
||||
}
|
||||
try {
|
||||
obj = JSON.parse(str)
|
||||
} catch (err) {
|
||||
ElMessage.error('导入失败!')
|
||||
}
|
||||
if (obj.version === EXPORT_DATA_KEY.version) {
|
||||
|
||||
} else {
|
||||
//TODO
|
||||
}
|
||||
let data = obj.val
|
||||
let settingState = checkAndUpgradeSaveSetting(data.setting)
|
||||
settingStore.setState(settingState)
|
||||
let dictState = checkAndUpgradeSaveDict(data.dict)
|
||||
store.init(dictState)
|
||||
ElMessage.success('导入成功!')
|
||||
}
|
||||
}
|
||||
reader.readAsText(file);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="page-title text-align-center">设置</div>
|
||||
<div class="setting">
|
||||
<div class="left">
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
|
||||
<Icon icon="bx:headphone" width="20" color="#0C8CE9"/>
|
||||
<span>音效设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
|
||||
<Icon icon="material-symbols:keyboard-outline" width="20" color="#0C8CE9"/>
|
||||
<span>快捷键设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
|
||||
<Icon icon="icon-park-outline:setting-config" width="20" color="#0C8CE9"/>
|
||||
<span>其他设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">
|
||||
<Icon icon="mdi:database-cog-outline" width="20" color="#0C8CE9"/>
|
||||
<span>数据管理</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 4 && 'active'" @click="tabIndex = 4">
|
||||
<Icon icon="mingcute:service-fill" width="20" color="#0C8CE9"/>
|
||||
<span>反馈</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 5 && 'active'" @click="tabIndex = 5">
|
||||
<Icon icon="mdi:about-circle-outline" width="20" color="#0C8CE9"/>
|
||||
<span>关于</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="git-log">
|
||||
Build {{ gitLastCommitHash }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div v-if="tabIndex === 0">
|
||||
<div class="row">
|
||||
<label class="main-title">所有音效</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.allSound"
|
||||
@change="useChangeAllSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">单词/句子自动发音</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.wordSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">单词/句子发音口音</label>
|
||||
<div class="wrapper">
|
||||
<el-select v-model="settingStore.wordSoundType"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option label="美音" value="us"/>
|
||||
<el-option label="英音" value="uk"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">音量</label>
|
||||
<div class="wrapper">
|
||||
<el-slider v-model="settingStore.wordSoundVolume"/>
|
||||
<span>{{ settingStore.wordSoundVolume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">倍速</label>
|
||||
<div class="wrapper">
|
||||
<el-slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
|
||||
<span>{{ settingStore.wordSoundSpeed }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">按键音</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.keyboardSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="item-title">按键音效</label>
|
||||
<div class="wrapper">
|
||||
<el-select v-model="settingStore.keyboardSoundFile"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in SoundFileOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div class="el-option-row">
|
||||
<span>{{ item.label }}</span>
|
||||
<VolumeIcon
|
||||
:time="100"
|
||||
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">音量</label>
|
||||
<div class="wrapper">
|
||||
<el-slider v-model="settingStore.keyboardSoundVolume"/>
|
||||
<span>{{ settingStore.keyboardSoundVolume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">效果音(输入错误、完成时的音效)</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.effectSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">音量</label>
|
||||
<div class="wrapper">
|
||||
<el-slider v-model="settingStore.effectSoundVolume"/>
|
||||
<span>{{ settingStore.effectSoundVolume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 1">
|
||||
<div class="row">
|
||||
<label class="item-title">显示上一个/下一个单词</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.showNearWord"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
开启后,练习中会在上方显示上一个/下一个单词
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">忽略大小写</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.ignoreCase"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
开启后,输入时不区分大小写,如输入“hello”和“Hello”都会被认为是正确的
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">允许默写模式下显示提示</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.allowWordTip"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
开启后,可以通过鼠标 hover 单词或者按 {{ settingStore.shortcutKeyMap[ShortcutKey.ShowWord] }} 显示正确答案
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">字体设置(仅可调整单词练习)</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">外语字体</label>
|
||||
<div class="wrapper">
|
||||
<el-slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordForeignFontSize"/>
|
||||
<span>{{ settingStore.fontSize.wordForeignFontSize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">中文字体</label>
|
||||
<div class="wrapper">
|
||||
<el-slider
|
||||
:min="10"
|
||||
:max="100"
|
||||
v-model="settingStore.fontSize.wordTranslateFontSize"/>
|
||||
<span>{{ settingStore.fontSize.wordTranslateFontSize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">其他设置</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">切换下一个单词时间</label>
|
||||
<div class="wrapper">
|
||||
<el-input-number v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="6"
|
||||
:max="100"
|
||||
type="number"
|
||||
/>
|
||||
<span>毫秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body" v-if="tabIndex === 2">
|
||||
<div class="row">
|
||||
<label class="main-title">功能</label>
|
||||
<div class="wrapper">快捷键(点击可修改)</div>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="row" v-for="item of Object.entries(settingStore.shortcutKeyMap)">
|
||||
<label class="item-title">{{ $t(item[0]) }}</label>
|
||||
<div class="wrapper" @click="editShortcutKey = item[0]">
|
||||
<div class="set-key" v-if="editShortcutKey === item[0]">
|
||||
<input :value="item[1]?item[1]:'未设置快捷键'" readonly type="text" @blur="editShortcutKey = ''">
|
||||
<span @click.stop="editShortcutKey = ''">直接按键盘进行设置</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="item[1]">{{ item[1] }}</div>
|
||||
<span v-else>未设置快捷键</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row footer">
|
||||
<label class="item-title"></label>
|
||||
<div class="wrapper">
|
||||
<BaseButton @click="resetShortcutKeyMap">恢复默认</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 3">
|
||||
<div class="row">
|
||||
<div class="main-title">数据导出</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">
|
||||
目前用户的所有数据(自定义设置、自定义词典、练习进度等)
|
||||
<b>仅保存在本地</b>
|
||||
。如果您需要在不同的设备、浏览器或者其他非官方部署上使用 {{ APP_NAME }}, 您需要手动进行数据同步和保存。
|
||||
</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<BaseButton @click="exportData">数据导出</BaseButton>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="main-title">数据导入</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="sub-title">
|
||||
请注意,导入数据将
|
||||
<b style="color: red"> 完全覆盖 </b>
|
||||
当前数据。请谨慎操作。
|
||||
</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="import hvr-grow">
|
||||
<BaseButton>数据导入</BaseButton>
|
||||
<input type="file"
|
||||
accept="application/json"
|
||||
@change="importData">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 4" class="feedback-modal">
|
||||
<div>
|
||||
给我发Email:<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
</div>
|
||||
<p>or</p>
|
||||
<div class="github">
|
||||
<span>在<a :href="GITHUB" target="_blank">Github</a>上给我提一个
|
||||
<a :href="`${GITHUB}/issues`" target="_blank">Issue</a>
|
||||
</span>
|
||||
<div class="options">
|
||||
<BaseButton>
|
||||
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=%E5%8D%95%E8%AF%8D%E9%94%99%E8%AF%AF---word-error.md&title=%E5%8D%95%E8%AF%8D%E9%94%99%E8%AF%AF+%7C+Word+error`"
|
||||
target="_blank">词典错误?</a>
|
||||
</BaseButton>
|
||||
<BaseButton>
|
||||
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=问题报告---bug-report-.md&title=问题报告+%7C+Bug+report+`"
|
||||
target="_blank">反馈BUG?</a>
|
||||
</BaseButton>
|
||||
<BaseButton>
|
||||
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=功能请求---feature-request.md&title=功能请求+%7C+Feature+request`"
|
||||
target="_blank">功能请求?</a>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tabIndex === 5" class="about">
|
||||
<h1>Type Words</h1>
|
||||
<p>
|
||||
本项目完全开源!好用请大家多多点Star!
|
||||
</p>
|
||||
<p>
|
||||
GitHub地址:<a href="https://github.com/zyronon/typing-word">https://github.com/zyronon/typing-word</a>
|
||||
</p>
|
||||
<p>
|
||||
反馈:<a
|
||||
href="https://github.com/zyronon/typing-word/issues">https://github.com/zyronon/typing-word/issues</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.setting {
|
||||
display: flex;
|
||||
color: var(--color-font-1);
|
||||
|
||||
.left {
|
||||
height: 93vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-right: 2px solid gainsboro;
|
||||
|
||||
.tabs {
|
||||
padding: .6rem 1.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem;
|
||||
|
||||
.tab {
|
||||
cursor: pointer;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
|
||||
&.active {
|
||||
background: var(--color-item-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.git-log {
|
||||
font-size: .6rem;
|
||||
color: gray;
|
||||
margin-bottom: .3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: var(--space);
|
||||
padding: 0 2.6rem;
|
||||
|
||||
.row {
|
||||
min-height: 2.6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: calc(var(--space) * 5);
|
||||
|
||||
.wrapper {
|
||||
height: 2rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space);
|
||||
|
||||
span {
|
||||
text-align: right;
|
||||
//width: 30rem;
|
||||
font-size: .7rem;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.set-key {
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
width: 9rem;
|
||||
box-sizing: border-box;
|
||||
margin-right: .6rem;
|
||||
height: 1.8rem;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
border: 1px solid gray;
|
||||
border-radius: .2rem;
|
||||
padding: 0 .3rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
flex: 1;
|
||||
padding-right: .6rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-bottom: .6rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.line {
|
||||
border-bottom: 1px solid #c4c3c3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-option-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.icon-wrapper {
|
||||
transform: translateX(10rem);
|
||||
}
|
||||
}
|
||||
|
||||
.import {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.feedback-modal {
|
||||
//height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--space);
|
||||
//justify-content: center;
|
||||
color: var(--color-font-1);
|
||||
|
||||
p {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
.github {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space);
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.about {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import BackIcon from "@/components/BackIcon.vue";
|
||||
import BackIcon from "@/pages/pc/components/BackIcon.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<div class="w-[70vw] 2xl:w-[50vw] my-5">
|
||||
<div class="w-[70vw] 2xl:w-[50vw] page">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.page{
|
||||
min-height: calc(100vh - 1.2rem);
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,7 +54,7 @@ watch(() => settingStore.load, (n) => {
|
||||
<div class="collect" v-if="showNotice">
|
||||
<div class="href-wrapper">
|
||||
<div class="round">
|
||||
<div class="href">typing-word.ttentau.top</div>
|
||||
<div class="href">2study.top</div>
|
||||
<Icon
|
||||
width="22"
|
||||
icon="mdi:star-outline"/>
|
||||
@@ -115,7 +115,7 @@ watch(() => settingStore.load, (n) => {
|
||||
box-shadow: var(--shadow);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.mobile{
|
||||
&.mobile {
|
||||
width: 95%;
|
||||
padding: 0.6rem;
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {DictResource} from "@/types.ts";
|
||||
import {dictionaryResources} from "@/assets/dictionary.ts";
|
||||
import {groupBy} from "lodash-es";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import DictGroup from "@/pages/pc/components/list/DictGroup.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Input from "@/pages/pc/components/Input.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {computed} from "vue";
|
||||
import DictList from "@/pages/pc/components/list/DictList.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
add: [],
|
||||
selectDict: [val: { dict: any, index: number }]
|
||||
}>()
|
||||
const store = useBaseStore()
|
||||
|
||||
|
||||
function groupByDictTags(dictList: DictResource[]) {
|
||||
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
|
||||
dict.tags.forEach((tag) => {
|
||||
if (result[tag]) {
|
||||
result[tag].push(dict)
|
||||
} else {
|
||||
result[tag] = [dict]
|
||||
}
|
||||
})
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
const groupedByCategoryAndTag = $computed(() => {
|
||||
const groupByCategory = groupBy(dictionaryResources, 'category')
|
||||
let data = []
|
||||
for (const [key, value] of Object.entries(groupByCategory)) {
|
||||
data.push([key, groupByDictTags(value)])
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
let showSearchInput = $ref(false)
|
||||
let searchKey = $ref('')
|
||||
|
||||
const searchList = computed(() => {
|
||||
if (searchKey) {
|
||||
let s = searchKey.toLowerCase()
|
||||
return dictionaryResources.filter((item) => {
|
||||
return item.name.toLowerCase().includes(s)
|
||||
|| item.category.toLowerCase().includes(s)
|
||||
|| item.tags.join('').replace('所有', '').toLowerCase().includes(s)
|
||||
|| item?.url?.toLowerCase?.().includes?.(s)
|
||||
})
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
defineExpose({startSearch: () => showSearchInput = true})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="flex gap-4" v-if="showSearchInput">
|
||||
<Input placeholder="请输入词典名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus/>
|
||||
<BaseButton @click="showSearchInput = false, searchKey = ''">取消</BaseButton>
|
||||
</div>
|
||||
<div class="title flex justify-between" v-else>
|
||||
<span>词典列表</span>
|
||||
<BaseIcon @click="showSearchInput = true" icon="fluent:search-24-regular"/>
|
||||
</div>
|
||||
<div class="mt-4" v-if="searchKey">
|
||||
<DictList
|
||||
v-if="searchList.length "
|
||||
@selectDict="e => emit('selectDict',e)"
|
||||
:list="searchList"
|
||||
:select-id="'-1'"/>
|
||||
<Empty v-else text="没有相关词典"/>
|
||||
</div>
|
||||
<div class="w-full" v-else>
|
||||
<DictGroup
|
||||
v-for="item in groupedByCategoryAndTag"
|
||||
:select-id="store.currentStudyWordDict.id"
|
||||
@selectDict="e => emit('selectDict',e)"
|
||||
:groupByTag="item[1]"
|
||||
:category="item[0]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -1,154 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import Dialog from "@/pages/pc/components/dialog/Dialog.vue"
|
||||
import Setting from "@/pages/pc/components/Setting.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let disabledDialogEscKey = $ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="runtimeStore.showSettingModal"
|
||||
:keyboard="disabledDialogEscKey"
|
||||
title="设置">
|
||||
<Setting @toggle-disabled-dialog-esc-key="e => disabledDialogEscKey = !e"/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
.setting-modal {
|
||||
width: 40vw;
|
||||
height: 70vh;
|
||||
display: flex;
|
||||
color: var(--color-font-1);
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.tabs {
|
||||
padding: .6rem 1.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
//align-items: center;
|
||||
//justify-content: center;
|
||||
gap: .6rem;
|
||||
|
||||
.tab {
|
||||
cursor: pointer;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
|
||||
&.active {
|
||||
background: var(--color-item-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.git-log {
|
||||
font-size: .6rem;
|
||||
color: gray;
|
||||
margin-bottom: .3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--color-header-bg);
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 0 var(--space);
|
||||
|
||||
.row {
|
||||
height: 2.6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: calc(var(--space) * 5);
|
||||
|
||||
.wrapper {
|
||||
height: 2rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space);
|
||||
|
||||
span {
|
||||
text-align: right;
|
||||
//width: 30rem;
|
||||
font-size: .7rem;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.set-key {
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
width: 9rem;
|
||||
box-sizing: border-box;
|
||||
margin-right: .6rem;
|
||||
height: 1.8rem;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
border: 1px solid gray;
|
||||
border-radius: .2rem;
|
||||
padding: 0 .3rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
flex: 1;
|
||||
padding-right: .6rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-bottom: .6rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.line {
|
||||
border-bottom: 1px solid #c4c3c3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -42,7 +42,7 @@ const {toggleTheme} = useTheme()
|
||||
</div>
|
||||
<div class="row"
|
||||
:title="`设置(${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
|
||||
@click="runtimeStore.showSettingModal = true"
|
||||
@click="router.push('/setting')"
|
||||
>
|
||||
<Icon icon="uil:setting"/>
|
||||
<span v-if="settingStore.sideExpand">设置</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="tsx">
|
||||
import {getDefaultDict, getDefaultWord} from "@/types";
|
||||
import {DictId, getDefaultDict, getDefaultWord} from "@/types";
|
||||
import type {Word} from "@/types";
|
||||
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
@@ -12,7 +12,7 @@ import BaseTable from "@/pages/pc/components/BaseTable.vue";
|
||||
import WordItem from "@/pages/pc/components/WordItem.vue";
|
||||
import type {FormInstance, FormRules} from "element-plus";
|
||||
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
|
||||
import BackIcon from "@/components/BackIcon.vue";
|
||||
import BackIcon from "@/pages/pc/components/BackIcon.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
@@ -278,7 +278,10 @@ onMounted(() => {
|
||||
if (!runtimeStore.editDict.id) {
|
||||
router.push("/word")
|
||||
} else {
|
||||
if (!runtimeStore.editDict.words.length && !runtimeStore.editDict.custom) {
|
||||
if (!runtimeStore.editDict.words.length
|
||||
&& !runtimeStore.editDict.custom
|
||||
&& ![DictId.wordCollect, DictId.wordWrong, DictId.wordKnown].includes(runtimeStore.editDict.id)
|
||||
) {
|
||||
loading = true
|
||||
_getDictDataByUrl(runtimeStore.editDict).then(r => {
|
||||
loading = false
|
||||
@@ -295,8 +298,13 @@ function formClose() {
|
||||
}
|
||||
|
||||
async function addMyStudyList() {
|
||||
base.changeDict(runtimeStore.editDict)
|
||||
if (route.query?.from) {
|
||||
router.back()
|
||||
}
|
||||
router.back()
|
||||
requestIdleCallback(()=>{
|
||||
base.changeDict(runtimeStore.editDict)
|
||||
})
|
||||
}
|
||||
|
||||
defineRender(() => {
|
||||
@@ -306,7 +314,7 @@ defineRender(() => {
|
||||
showBookDetail.value ? <div className="card mb-0 h-[95vh] flex flex-col">
|
||||
<div class="flex justify-between items-center relative">
|
||||
<BackIcon class="z-2" onClick={() => router.back()}/>
|
||||
<div class="absolute text-2xl text-align-center w-full">{runtimeStore.editDict.name}</div>
|
||||
<div class="absolute page-title text-align-center w-full">{runtimeStore.editDict.name}</div>
|
||||
<div class="flex gap-2">
|
||||
<BaseButton type="info" onClick={() => isEdit = true}>编辑</BaseButton>
|
||||
<BaseButton onClick={addMyStudyList}>学习</BaseButton>
|
||||
@@ -450,7 +458,7 @@ defineRender(() => {
|
||||
<div class="card mb-0 h-[95vh]">
|
||||
<div class="flex justify-between items-center relative">
|
||||
<BackIcon class="z-2" onClick={() => isAdd ? router.back() : (isEdit = false)}/>
|
||||
<div class="absolute text-2xl text-align-center w-full">
|
||||
<div class="absolute page-title text-align-center w-full">
|
||||
{runtimeStore.editDict.id ? '修改' : '创建'}词典
|
||||
</div>
|
||||
</div>
|
||||
|
||||
114
src/pages/pc/word/DictList.vue
Normal file
114
src/pages/pc/word/DictList.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
import "vue-activity-calendar/style.css";
|
||||
import {useNav} from "@/utils";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import {DictResource, getDefaultDict} from "@/types.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import Input from "@/pages/pc/components/Input.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import DictList from "@/pages/pc/components/list/DictList.vue";
|
||||
import BackIcon from "@/pages/pc/components/BackIcon.vue";
|
||||
import DictGroup from "@/pages/pc/components/list/DictGroup.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useRouter} from "vue-router";
|
||||
import {groupBy} from "lodash-es";
|
||||
import {dictionaryResources} from "@/assets/dictionary.ts";
|
||||
import {computed} from "vue";
|
||||
|
||||
const {nav} = useNav()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const store = useBaseStore()
|
||||
const router = useRouter()
|
||||
|
||||
function selectDict(e) {
|
||||
console.log(e.dict)
|
||||
getDictDetail(e.dict)
|
||||
}
|
||||
|
||||
async function getDictDetail(val: DictResource) {
|
||||
runtimeStore.editDict = getDefaultDict(val)
|
||||
nav('dict-detail', {from: 'list'})
|
||||
}
|
||||
|
||||
|
||||
function groupByDictTags(dictList: DictResource[]) {
|
||||
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
|
||||
dict.tags.forEach((tag) => {
|
||||
if (result[tag]) {
|
||||
result[tag].push(dict)
|
||||
} else {
|
||||
result[tag] = [dict]
|
||||
}
|
||||
})
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
const groupedByCategoryAndTag = $computed(() => {
|
||||
const groupByCategory = groupBy(dictionaryResources, 'category')
|
||||
let data = []
|
||||
for (const [key, value] of Object.entries(groupByCategory)) {
|
||||
data.push([key, groupByDictTags(value)])
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
let showSearchInput = $ref(false)
|
||||
let searchKey = $ref('')
|
||||
|
||||
const searchList = computed<any[]>(() => {
|
||||
if (searchKey) {
|
||||
let s = searchKey.toLowerCase()
|
||||
return dictionaryResources.filter((item) => {
|
||||
return item.name.toLowerCase().includes(s)
|
||||
|| item.category.toLowerCase().includes(s)
|
||||
|| item.tags.join('').replace('所有', '').toLowerCase().includes(s)
|
||||
|| item?.url?.toLowerCase?.().includes?.(s)
|
||||
})
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="card">
|
||||
<div class="flex items-center relative gap-2">
|
||||
<BackIcon class="z-2" @Click='router.back()'/>
|
||||
<div class="flex flex-1 gap-4" v-if="showSearchInput">
|
||||
<Input placeholder="请输入词典名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus/>
|
||||
<BaseButton @click="showSearchInput = false, searchKey = ''">取消</BaseButton>
|
||||
</div>
|
||||
<div class="py-1 flex flex-1 justify-end" v-else>
|
||||
<span class="page-title absolute w-full center">词典列表</span>
|
||||
<BaseIcon @click="showSearchInput = true"
|
||||
class="z-1"
|
||||
icon="fluent:search-24-regular"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4" v-if="searchKey">
|
||||
<DictList
|
||||
v-if="searchList.length "
|
||||
@selectDict="selectDict"
|
||||
:list="searchList"
|
||||
:select-id="'-1'"/>
|
||||
<Empty v-else text="没有相关词典"/>
|
||||
</div>
|
||||
<div class="w-full" v-else>
|
||||
<DictGroup
|
||||
v-for="item in groupedByCategoryAndTag"
|
||||
:select-id="store.currentStudyWordDict.id"
|
||||
@selectDict="selectDict"
|
||||
:groupByTag="item[1]"
|
||||
:category="item[0]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -6,12 +6,11 @@ import "vue-activity-calendar/style.css";
|
||||
import {useRouter} from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
|
||||
import {_getAccomplishDate, _getAccomplishDays, _getDictDataByUrl, useNav, _dateFormat} from "@/utils";
|
||||
import {_dateFormat, _getAccomplishDate, _getAccomplishDays, _getDictDataByUrl, useNav} from "@/utils";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import {DictResource, getDefaultDict} from "@/types.ts";
|
||||
import {onMounted, watch} from "vue";
|
||||
import {getCurrentStudyWord} from "@/hooks/dict.ts";
|
||||
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import Book from "@/pages/pc/components/Book.vue";
|
||||
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
|
||||
@@ -28,13 +27,8 @@ let currentStudy = $ref({
|
||||
write: []
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
watch(() => store.load, () => {
|
||||
init()
|
||||
})
|
||||
onMounted(init)
|
||||
watch(() => store.load, init)
|
||||
|
||||
async function init() {
|
||||
if (store.word.studyIndex >= 3) {
|
||||
@@ -46,7 +40,6 @@ async function init() {
|
||||
if (!currentStudy.new.length && store.sdict.words.length) {
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function startStudy() {
|
||||
@@ -64,7 +57,6 @@ function startStudy() {
|
||||
nav('study-word', {}, currentStudy)
|
||||
} else {
|
||||
ElMessage.warning('请先选择一本词典')
|
||||
dictListRef.startSearch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +66,6 @@ function setPerDayStudyNumber() {
|
||||
tempPerDayStudyNumber = store.sdict.perDayStudyNumber
|
||||
} else {
|
||||
ElMessage.warning('请先选择一本词典')
|
||||
dictListRef.startSearch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,17 +77,11 @@ function changePerDayStudyNumber() {
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
function selectDict(e) {
|
||||
console.log(e.dict)
|
||||
getDictDetail(e.dict)
|
||||
}
|
||||
|
||||
async function getDictDetail(val: DictResource) {
|
||||
async function goDictDetail(val: DictResource) {
|
||||
runtimeStore.editDict = getDefaultDict(val)
|
||||
nav('dict-detail', {})
|
||||
}
|
||||
|
||||
let dictListRef = $ref<any>()
|
||||
let isMultiple = $ref(false)
|
||||
let selectIds = $ref([])
|
||||
|
||||
@@ -113,6 +98,7 @@ function handleBatchDel() {
|
||||
store.word.bookList.splice(r, 1)
|
||||
}
|
||||
})
|
||||
selectIds = []
|
||||
ElMessage.success("删除成功!")
|
||||
}
|
||||
|
||||
@@ -190,7 +176,7 @@ const progressTextRight = $computed(() => {
|
||||
<span class="text-xl font-bold">{{ store.sdict.name || '请选择书籍开始学习' }}</span>
|
||||
<BaseIcon title="切换词典" :icon="store.sdict.name ? 'gg:arrows-exchange' : 'fluent:add-20-filled'"
|
||||
class="ml-4"
|
||||
@click="dictListRef.startSearch()"/>
|
||||
@click="router.push('/dict-list')"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
@@ -224,14 +210,14 @@ const progressTextRight = $computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end justify-around">
|
||||
<div class="flex flex-col items-end justify-around ">
|
||||
<div class="flex gap-1 items-center">
|
||||
每日目标
|
||||
<div style="color:#ac6ed1;" @click="setPerDayStudyNumber"
|
||||
class="bg-slate-200 px-2 h-10 flex center text-2xl rounded cursor-pointer">
|
||||
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
|
||||
</div>
|
||||
个单词
|
||||
个单词 <span class="color-blue cursor-pointer" @click="setPerDayStudyNumber">更改</span>
|
||||
</div>
|
||||
<div class="rounded-xl bg-slate-800 flex items-center gap-2 py-3 px-5 text-white cursor-pointer"
|
||||
:class="store.sdict.name || 'opacity-70 cursor-not-allowed'" @click="startStudy">
|
||||
@@ -258,12 +244,11 @@ const progressTextRight = $computed(() => {
|
||||
<div class="grid grid-cols-6 gap-4 mt-4">
|
||||
<Book :is-add="false" quantifier="个词" :item="item" :checked="selectIds.includes(item.id)"
|
||||
@check="() => toggleSelect(item)" :show-checkbox="isMultiple && j >= 3"
|
||||
v-for="(item, j) in store.word.bookList" @click="getDictDetail(item)"/>
|
||||
<Book :is-add="true" @click="dictListRef.startSearch()"/>
|
||||
v-for="(item, j) in store.word.bookList" @click="goDictDetail(item)"/>
|
||||
<Book :is-add="true" @click="router.push('/dict-list')"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DictListPanel ref="dictListRef" @selectDict="selectDict"/>
|
||||
|
||||
<div class="card">
|
||||
<div class="title">
|
||||
|
||||
@@ -12,6 +12,8 @@ import StudyWord from "@/pages/pc/word/StudyWord.vue";
|
||||
import EditArticlePage from "@/pages/pc/article/EditArticlePage.vue";
|
||||
import BookDetail from "@/pages/pc/article/BookDetail.vue";
|
||||
import BatchEditArticlePage from "@/pages/pc/article/BatchEditArticlePage.vue";
|
||||
import DictList from "@/pages/pc/word/DictList.vue";
|
||||
import Setting from "@/pages/pc/Setting.vue";
|
||||
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
@@ -20,6 +22,7 @@ export const routes: RouteRecordRaw[] = [
|
||||
children: [
|
||||
{path: 'home', component: HomeIndex},
|
||||
{path: 'word', component: WordHomePage},
|
||||
{path: 'dict-list', component: DictList},
|
||||
{path: 'study-word', component: StudyWord},
|
||||
{path: 'dict-detail', component: DictDetail},
|
||||
{path: 'article', component: ArticleHomePage},
|
||||
@@ -27,6 +30,7 @@ export const routes: RouteRecordRaw[] = [
|
||||
{path: 'edit-article', component: EditArticlePage},
|
||||
{path: 'batch-edit-article', component: BatchEditArticlePage},
|
||||
{path: 'book-detail', component: BookDetail},
|
||||
{path: 'setting', component: Setting},
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {Article, Dict, DictType, getDefaultDict, Sort, Word} from "../types.ts"
|
||||
import {Article, Dict, DictId, DictType, getDefaultDict, Sort, Word} from "../types.ts"
|
||||
import {cloneDeep, merge, reverse, shuffle} from "lodash-es";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import * as localforage from "localforage";
|
||||
@@ -32,11 +32,9 @@ export const DefaultBaseState = (): BaseState => ({
|
||||
load: false,
|
||||
word: {
|
||||
bookList: [
|
||||
getDefaultDict({
|
||||
id: 'word-collect', name: '收藏', words: []
|
||||
}),
|
||||
getDefaultDict({id: 'word-wrong', name: '错词'}),
|
||||
getDefaultDict({id: 'word-known', name: '已掌握'}),
|
||||
getDefaultDict({id: DictId.wordCollect, name: '收藏', words: []}),
|
||||
getDefaultDict({id: DictId.wordWrong, name: '错词'}),
|
||||
getDefaultDict({id: DictId.wordKnown, name: '已掌握'}),
|
||||
// getDefaultDict({
|
||||
// id: 'nce-new-2',
|
||||
// name: '新概念英语(新版)-2',
|
||||
@@ -54,7 +52,7 @@ export const DefaultBaseState = (): BaseState => ({
|
||||
},
|
||||
article: {
|
||||
bookList: [
|
||||
getDefaultDict({id: 'article-collect', name: '收藏'})
|
||||
getDefaultDict({id: DictId.articleCollect, name: '收藏'})
|
||||
],
|
||||
studyIndex: -1,
|
||||
}
|
||||
|
||||
30
src/types.ts
30
src/types.ts
@@ -74,7 +74,6 @@ export enum DictType {
|
||||
simple = 'simple',
|
||||
wrong = 'wrong',
|
||||
known = 'known',
|
||||
collectWord = 'collect-word',
|
||||
word = 'word',
|
||||
article = 'article',
|
||||
}
|
||||
@@ -240,10 +239,21 @@ export type DictResource = {
|
||||
category: string
|
||||
tags: string[]
|
||||
translateLanguage: TranslateLanguageType
|
||||
//todo 可以考虑删除了
|
||||
type: DictType
|
||||
language: LanguageType
|
||||
}
|
||||
|
||||
export interface Dict extends DictResource {
|
||||
lastLearnIndex: number,
|
||||
perDayStudyNumber: number,
|
||||
words: Word[],
|
||||
articles: Article[],
|
||||
statistics: Statistics[],
|
||||
custom: boolean,//是否是自定义词典
|
||||
complete: boolean,//是否学习完成,学完了设为true,然后lastLearnIndex重置
|
||||
}
|
||||
|
||||
export function getDefaultDict(val: Partial<Dict> = {}): Dict {
|
||||
return {
|
||||
id: '',
|
||||
@@ -262,20 +272,11 @@ export function getDefaultDict(val: Partial<Dict> = {}): Dict {
|
||||
articles: [],
|
||||
statistics: [],
|
||||
custom: false,
|
||||
complete: false,
|
||||
...val
|
||||
}
|
||||
}
|
||||
|
||||
export interface Dict extends DictResource {
|
||||
lastLearnIndex: number,
|
||||
perDayStudyNumber: number,
|
||||
words: Word[],
|
||||
articles: Article[],
|
||||
statistics: Statistics[],
|
||||
custom: boolean,//是否是自定义词典
|
||||
complete: boolean,//是否学习完成,学完了设为true,然后lastLearnIndex重置
|
||||
}
|
||||
|
||||
export interface ArticleItem {
|
||||
item: Article,
|
||||
index: number
|
||||
@@ -291,3 +292,10 @@ export interface StudyData {
|
||||
words: any[],
|
||||
wrongWords: any[],
|
||||
}
|
||||
|
||||
export class DictId {
|
||||
static wordCollect = 'wordCollect'
|
||||
static wordWrong = 'wordWrong'
|
||||
static wordKnown = 'wordKnown'
|
||||
static articleCollect = 'articleCollect'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user