Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/assets/css/style.scss
#	src/pages/pc/components/Logo.vue
#	src/pages/pc/components/dialog/DictDiglog.vue
#	src/pages/pc/components/list/DictItem.vue
#	src/pages/pc/components/toolbar/index.vue
#	src/pages/pc/index.vue
#	src/pages/pc/practice/practice-article/TypingArticle.vue
#	src/pages/pc/word/WordHome.vue
#	src/router.ts
#	vite.config.ts
This commit is contained in:
zyronon
2024-05-21 01:19:20 +08:00
62 changed files with 6471 additions and 5756 deletions

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Typing Word</title>
<script>
+(function () {
function s() {
function rem() {
let html = document.documentElement;
let max = parseInt('2048rem');
@@ -23,7 +23,7 @@
rem();
window.addEventListener('resize', rem);
})();
}
</script>
<script>

View File

@@ -19,7 +19,7 @@
"dependencies": {
"@opentranslate/baidu": "^1.4.2",
"@opentranslate/translator": "^1.4.2",
"axios": "^1.6.8",
"axios": "^1.5.0",
"compromise": "^14.10.0",
"copy-to-clipboard": "^3.3.3",
"element-plus": "^2.3.9",
@@ -32,14 +32,14 @@
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"nanoid": "^5.0.3",
"pinia": "^2.1.6",
"sentence-splitter": "^4.2.1",
"tesseract.js": "^4.1.1",
"vant": "^4.8.1",
"vue": "^3.3.4",
"vue-activity-calendar": "^1.2.2",
"vue-i18n": "9",
"pinia": "^2.1.7",
"vue": "3.4.21",
"vue-router": "4.3.0",
"vue-router": "4",
"vue-virtual-scroller": "2.0.0-beta.8"
},
"devDependencies": {
@@ -47,26 +47,26 @@
"@types/file-saver": "^2.0.5",
"@types/lodash-es": "^4.17.9",
"@types/uuid": "^9.0.4",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.19",
"@unocss/postcss": "^0.60.2",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/compiler-sfc": "^3.3.4",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"esm": "^3.2.25",
"gulp": "^4.0.2",
"husky": "^8.0.3",
"postcss": "^8.4.38",
"push-dir": "^0.4.1",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.64.2",
"tailwindcss": "^3.4.3",
"tslib": "^2.6.2",
"typescript": "5.3.3",
"typescript": "^5.2.0",
"unocss": "^0.60.2",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"unplugin-vue-macros": "^2.9.1",
"unplugin-vue-define-options": "^1.4.1",
"vite": "^5.2.0",
"vue-tsc": "^2.0.6",
"vue-tsc": "^1.8.5",
"xlsx": "^0.18.5"
},
"config": {

9847
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

8
postcss.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
// postcss.config.mjs
import UnoCSS from '@unocss/postcss'
export default {
plugins: [
UnoCSS(),
],
}

View File

@@ -3,14 +3,11 @@
@import "variable.scss";
@import "anim";
@import 'element-plus/theme-chalk/dark/css-vars';
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-background: #E6E8EB;
//--color-main-bg: #E6E8EB;
--color-main-bg: rgb(238,240,244);
--color-main-bg: rgb(238, 240, 244);
--color-second-bg: rgb(240, 242, 244);
--color-third-bg: rgb(213, 215, 217);
@@ -35,14 +32,14 @@
--practice-wrapper-translateX: 1px;
--article-width: 50vw;
--toolbar-width: 700rem;
--toolbar-height: 54rem;
--panel-width: 400rem;
--space: 20rem;
--radius: 8rem;
--toolbar-width: 45rem;
--toolbar-height: 3.2rem;
--panel-width: 24rem;
--space: 1.2rem;
--radius: .5rem;
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
--panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--toolbar-width) / 2 + 24rem);
--article-panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--article-width) / 2 + 24rem);
--panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--toolbar-width) / 2 + 2rem);
--article-panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--article-width) / 2 + 2rem);
--anim-time: 0.5s;
--color-input-bg: white;
@@ -50,6 +47,8 @@
--color-textarea-bg: white;
--aside-width: 12rem;
--font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--word-font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
}
@@ -141,7 +140,7 @@ html.dark {
}
html, body {
font-size: 1px;
//font-size: 1px;
padding: 0;
margin: 0;
width: 100vw;
@@ -197,13 +196,13 @@ a {
.base-textarea {
flex: 1;
font-family: var(--font-family);
font-size: 18rem;
font-size: 1.1rem;
outline: none;
border: 1px solid transparent;
border-radius: 6rem;
padding: 8rem 10rem;
border-radius: .4rem;
padding: .5rem .6rem;
transition: all .3s;
min-height: 20rem;
min-height: 1.2rem;
width: 100%;
box-sizing: border-box;
background: var(--color-textarea-bg);
@@ -219,24 +218,24 @@ a {
}
::-webkit-scrollbar {
width: 8rem;
height: 10rem;
width: .5rem;
height: .6rem;
}
::-webkit-scrollbar-track {
background: transparent;
border-radius: 2rem;
border-radius: .1rem;
}
::-webkit-scrollbar-thumb {
background: var(--color-scrollbar);
border-radius: 10rem;
border-radius: .6rem;
}
/* 火狐美化滚动条 */
* {
scrollbar-color: var(--color-scrollbar) #f3f4f9;
scrollbar-color: var(--color-scrollbar) #f3f4f9;
/* 滑块颜色 滚动条背景颜色 */
scrollbar-width: thin;
/* 滚动条宽度有三种thin、auto、none */
@@ -290,20 +289,20 @@ footer {
box-sizing: border-box;
.list-header {
min-height: 50rem;
padding: 10rem var(--space);
min-height: 3rem;
padding: .6rem var(--space);
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16rem;
font-size: 1rem;
color: var(--color-font-3);
.left {
flex: 1;
display: flex;
align-items: center;
gap: 10rem;
gap: .6rem;
}
.title {
@@ -345,7 +344,7 @@ footer {
}
.list-item-wrapper {
padding-bottom: 15rem;
padding-bottom: 1rem;
}
.common-list-item {
@@ -354,23 +353,23 @@ footer {
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
font-size: 18rem;
border-radius: 8rem;
font-size: 1.1rem;
border-radius: .5rem;
display: flex;
justify-content: space-between;
transition: all .3s;
padding: 10rem;
gap: 10rem;
padding: .6rem;
gap: .6rem;
border: 1px solid var(--color-item-border);
.left {
display: flex;
gap: 10rem;
gap: .6rem;
.title-wrapper {
display: flex;
flex-direction: column;
gap: 3rem;
gap: .2rem;
word-break: break-word;
}
}
@@ -378,7 +377,7 @@ footer {
.right {
display: flex;
flex-direction: column;
gap: 5rem;
gap: .3rem;
transition: all .3s;
}
@@ -430,22 +429,22 @@ footer {
.item-title {
display: flex;
align-items: center;
gap: 8rem;
gap: .5rem;
color: var(--color-font-1);
.word {
font-size: 20rem;
font-size: 1.2rem;
display: flex;
}
.phonetic {
font-size: 14rem;
font-size: .9rem;
color: gray;
}
}
.item-sub-title {
font-size: 16rem;
font-size: 1rem;
color: gray;
}
@@ -458,15 +457,14 @@ footer {
}
.common-title {
min-height: 40rem;
font-size: 18rem;
min-height: 2.8rem;
font-size: 1.1rem;
color: var(--color-font-1);
display: flex;
justify-content: center;
align-items: center;
}
.slide {
flex: 1;
width: 100%;
@@ -487,3 +485,7 @@ footer {
position: relative;
}
}
.container2 {
@apply w-5/10 pt-5;
}

View File

@@ -21,7 +21,7 @@ defineEmits(['click'])
</script>
<template>
<Tooltip :disabled="!keyboard" :title="`快捷键: ${keyboard}`">
<Tooltip :disabled="!keyboard" :title="`${keyboard}`">
<div class="base-button"
v-bind="$attrs"
@click="e => (!disabled && !loading) && $emit('click',e)"
@@ -52,8 +52,8 @@ defineEmits(['click'])
.base-button {
cursor: pointer;
border-radius: 6rem;
padding: 0 15rem;
border-radius: .4rem;
padding: 0 1rem;
display: flex;
align-items: center;
justify-content: center;
@@ -61,7 +61,7 @@ defineEmits(['click'])
//background: #999;
//background: rgb(60, 63, 65);
//background: var(--color-second-bg);
height: 36rem;
height: 2.5rem;
line-height: 1;
position: relative;
@@ -76,25 +76,25 @@ defineEmits(['click'])
}
&.small {
height: 30rem;
height: 2.4rem;
& > span {
font-size: 13rem;
font-size: .8rem;
}
}
&.large {
height: 50rem;
font-size: 18rem;
padding: 0 22rem;
height: 3rem;
font-size: 1.1rem;
padding: 0 1.4rem;
& > span {
font-size: 18rem;
font-size: 1.1rem;
}
}
& > span {
font-size: 16rem;
font-size: 1rem;
color: white;
:deep(a) {
@@ -126,11 +126,11 @@ defineEmits(['click'])
}
.key-notice {
margin-left: 10rem;
margin-left: .6rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 12rem;
font-size: .8rem;
color: white;
//gap: 2rem;

View File

@@ -27,16 +27,16 @@ defineEmits<{
justify-content: center;
align-items: center;
flex-direction: column;
font-size: 12rem;
gap: 20rem;
font-size: .7rem;
gap: 1.3rem;
span {
font-family: var(--font-family);
}
img {
margin-top: -50rem;
width: 120rem;
margin-top: -3rem;
width: 9rem;
}
}
</style>

View File

@@ -25,6 +25,6 @@ defineProps<{
display: flex;
align-items: center;
justify-content: center;
font-size: 18rem;
font-size: 1.1rem;
}
</style>

View File

@@ -75,7 +75,7 @@ defineExpose({play})
display: flex;
align-items: center;
justify-content: center;
$w: 22rem;
$w: 1.6rem;
:deep(svg) {
width: $w;

View File

@@ -9,6 +9,7 @@ import {createI18n} from 'vue-i18n'
import router from "@/router.ts";
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import 'virtual:uno.css';
const i18n = createI18n({
locale: 'zh-CN',

View File

@@ -207,7 +207,7 @@ watch(() => props.word, () => {
<span class="letter" v-else>{{ displayWord }}</span>
</div>
<Tooltip
:title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
>
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</Tooltip>

View File

@@ -0,0 +1,92 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
import {Icon} from '@iconify/vue'
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<div class="word flex justify-center ">
<div class="w-5/10 pt-5">
<div class="flex gap-6">
<div class="card w-1/2 flex flex-col">
<div class="title">
我的词典
</div>
<div class="grid flex-1 flex gap-5 mt-4">
<div class="p-4 flex-1 rounded-md bg-slate-200 relative" v-for="i in 3">
<span>收藏</span>
<div class="absolute bottom-4 right-4">333个词</div>
</div>
</div>
</div>
<div class="w-1/2">
<div class="card ">
<div class="flex justify-between items-center">
<div class="bg-slate-200 p-3 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold">{{ base.currentDict.name }}</span>
<Icon icon="gg:arrows-exchange" class="text-2xl ml-2"/>
<Icon icon="uil:setting" class="text-2xl ml-2"/>
</div>
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
@click="router.push('/practice')">
开始学习
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" percentage="80" :show-text="false"></el-progress>
</div>
</div>
</div>
<div class="card">
<div class="flex justify-between">
<div class="title">
其他学习词典
</div>
<BaseIcon icon="ic:round-add" @click="router.push('/dict')"/>
</div>
<div class="grid grid-cols-2 gap-6 mt-5 ">
<div class=" p-4 rounded-md justify-between items-center bg-slate-200 " v-for="i in 3">
<div class="flex justify-between w-full">
<span>{{ base.currentDict.name }}</span>
<div class="text-2xl ml-2 flex gap-4">
<Icon icon="hugeicons:delete-02"/>
<Icon icon="nonicons:go-16"/>
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" percentage="80" color="white" :show-text="false"></el-progress>
</div>
</div>
<div class="flex justify-center mt-2 text-2xl">
<Icon icon="mingcute:down-line"/>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.center {
@apply flex justify-center items-center;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -0,0 +1,164 @@
<script setup lang="ts">
import Toolbar from "@/pages/pc/components/toolbar/index.vue"
import {onMounted, onUnmounted, watch} from "vue";
import {usePracticeStore} from "@/stores/practice.ts";
import Footer from "@/pages/pc/practice/Footer.vue";
import {useBaseStore} from "@/stores/base.ts";
import Statistics from "@/pages/pc/practice/Statistics.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import PracticeArticle from "@/pages/pc/practice/practice-article/index.vue";
import {ShortcutKey} from "@/types.ts";
import DictModal from "@/pages/pc/components/dialog/DictDiglog.vue";
import {useStartKeyboardEventListener} from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
const practiceStore = usePracticeStore()
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const {toggleTheme} = useTheme()
const practiceRef: any = $ref()
watch(practiceStore, () => {
if (practiceStore.inputWordNumber < 1) {
return practiceStore.correctRate = -1
}
if (practiceStore.wrongWordNumber > practiceStore.inputWordNumber) {
return practiceStore.correctRate = 0
}
practiceStore.correctRate = 100 - Math.trunc(((practiceStore.wrongWordNumber) / (practiceStore.inputWordNumber)) * 100)
})
function test() {
MessageBox.confirm(
'2您选择了“本地翻译”但译文内容却为空白是否修改为“不需要翻译”并保存?',
'1提示',
() => {
console.log('ok')
},
() => {
console.log('cencal')
})
}
function write() {
// console.log('write')
settingStore.dictation = true
repeat()
}
//TODO 需要判断是否已忽略
function repeat() {
// console.log('repeat')
emitter.emit(EventKey.resetWord)
practiceRef.getCurrentPractice()
}
function prev() {
// console.log('next')
if (store.currentDict.chapterIndex === 0) {
ElMessage.warning('已经在第一章了~')
} else {
store.currentDict.chapterIndex--
repeat()
}
}
function toggleShowTranslate() {
settingStore.translate = !settingStore.translate
}
function toggleDictation() {
settingStore.dictation = !settingStore.dictation
}
function openSetting() {
runtimeStore.showSettingModal = true
}
function openDictDetail() {
emitter.emit(EventKey.openDictModal, 'detail')
}
function toggleConciseMode() {
settingStore.showToolbar = !settingStore.showToolbar
settingStore.showPanel = settingStore.showToolbar
}
function togglePanel() {
settingStore.showPanel = !settingStore.showPanel
}
function jumpSpecifiedChapter(val: number) {
store.currentDict.chapterIndex = val
repeat()
}
onMounted(() => {
emitter.on(EventKey.write, write)
emitter.on(EventKey.repeat, repeat)
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.on(ShortcutKey.PreviousChapter, prev)
emitter.on(ShortcutKey.RepeatChapter, repeat)
emitter.on(ShortcutKey.DictationChapter, write)
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
emitter.on(ShortcutKey.OpenSetting, openSetting)
emitter.on(ShortcutKey.OpenDictDetail, openDictDetail)
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
emitter.on(ShortcutKey.TogglePanel, togglePanel)
})
onUnmounted(() => {
emitter.off(EventKey.write, write)
emitter.off(EventKey.repeat, repeat)
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.off(ShortcutKey.PreviousChapter, prev)
emitter.off(ShortcutKey.RepeatChapter, repeat)
emitter.off(ShortcutKey.DictationChapter, write)
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
emitter.off(ShortcutKey.OpenSetting, openSetting)
emitter.off(ShortcutKey.OpenDictDetail, openDictDetail)
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
emitter.off(ShortcutKey.TogglePanel, togglePanel)
})
useStartKeyboardEventListener()
</script>
<template>
<div class="practice-wrapper">
<Toolbar/>
<PracticeArticle ref="practiceRef"/>
<Footer/>
</div>
<DictModal/>
<Statistics/>
</template>
<style scoped lang="scss">
.practice-wrapper {
font-size: 0.9rem;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
//padding-right: var(--practice-wrapper-padding-right);
transform: translateX(var(--practice-wrapper-translateX));
}
</style>

View File

@@ -100,15 +100,15 @@ watch(() => settingStore.load, (n) => {
right: var(--space);
top: var(--space);
z-index: 2;
font-size: 20rem;
font-size: 1.2rem;
display: flex;
flex-direction: column;
align-items: center;
background: var(--color-second-bg);
padding: 30rem;
border-radius: 12rem;
width: 500rem;
gap: 40rem;
padding: 1.8rem;
border-radius: 0.7rem;
width: 30rem;
gap: 2.4rem;
color: var(--color-font-1);
line-height: 1.5;
border: 1px solid var(--color-item-border);
@@ -117,11 +117,11 @@ watch(() => settingStore.load, (n) => {
&.mobile{
width: 95%;
padding: 10rem;
padding: 0.6rem;
}
.notice {
margin-top: 30rem;
margin-top: 2.4rem;
}
.active {
@@ -136,23 +136,23 @@ watch(() => settingStore.load, (n) => {
.href-wrapper {
display: flex;
font-size: 16rem;
font-size: 1rem;
align-items: center;
gap: 10rem;
gap: 0.6rem;
.round {
color: var(--color-font-1);
border-radius: 50rem;
padding: 10rem 10rem;
padding-left: 20rem;
gap: 30rem;
border-radius: 3rem;
padding: 0.6rem 0.6rem;
padding-left: 1.2rem;
gap: 2rem;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-main-bg);
.href {
font-size: 14rem;
font-size: 0.9rem;
}
}
@@ -167,11 +167,11 @@ watch(() => settingStore.load, (n) => {
}
.collect-keyboard {
margin-top: 20rem;
font-size: 16rem;
margin-top: 1.2rem;
font-size: 1rem;
span {
margin-left: 10rem;
margin-left: 0.6rem;
}
}
}
@@ -181,12 +181,12 @@ watch(() => settingStore.load, (n) => {
right: var(--space);
top: var(--space);
position: absolute;
font-size: 14rem;
font-size: 0.9rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: var(--color-font-1);
gap: 10rem;
gap: 0.6rem;
}
}

View File

@@ -96,8 +96,8 @@ function del(e) {
:list="groupByTranslateLanguage['common']"/>
</template>
<template v-else>
<div class="translate">
<span>翻译</span>
<div class="translate ">
<span>释义</span>
<el-radio-group v-model="currentTranslateLanguage">
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ $t(i) }}</el-radio-button>
</el-radio-group>
@@ -119,10 +119,10 @@ function del(e) {
@import "@/assets/css/style";
.dict-list-panel {
width: 50%;
width: 100%;
height: 100%;
$header-height: 60rem;
padding: var(--space);
$header-height: 5rem;
//padding: var(--space);
padding-top: 0;
box-sizing: border-box;
@@ -134,18 +134,18 @@ function del(e) {
.tabs {
display: flex;
gap: 20rem;
gap: 2rem;
.tab {
color: var(--color-font-1);
cursor: pointer;
padding: 10rem;
padding-bottom: 5rem;
padding: 1rem;
padding-bottom: 0.5rem;
transition: all .5s;
border-bottom: 2px solid transparent;
display: flex;
align-items: center;
gap: 6rem;
gap: 0.6rem;
&.active {
$main: rgb(64, 158, 255);
@@ -153,7 +153,7 @@ function del(e) {
}
img {
height: 30rem;
height: 2rem;
}
}
}
@@ -167,16 +167,16 @@ function del(e) {
flex: 1;
overflow: auto;
height: 100%;
padding-right: 10rem;
padding-right: 1rem;
.translate {
display: flex;
align-items: center;
color: var(--color-font-1);
margin-bottom: 30rem;
margin-bottom: 1rem;
& > span {
font-size: 22rem;
font-size: 1.2rem;
}
}
}

View File

@@ -43,7 +43,7 @@ function toggle() {
autosize
autofocus
type="textarea"
:input-style="`color: var(--color-font-1);font-size: 16rem;`"
:input-style="`color: var(--color-font-1);font-size: 1rem;`"
/>
<div class="options">
<BaseButton @click="toggle">取消</BaseButton>
@@ -53,7 +53,7 @@ function toggle() {
<div
v-else
class="text"
:style="`font-size: 16rem;`"
:style="`font-size: 1rem;`"
@click="toggle">
{{ value }}
</div>
@@ -61,12 +61,12 @@ function toggle() {
<style scoped lang="scss">
.edit-text {
margin-top: 10rem;
margin-top: .6rem;
color: var(--color-font-1);
.options {
margin-top: 10rem;
gap: 10rem;
margin-top: .6rem;
gap: .6rem;
display: flex;
justify-content: flex-end;
}
@@ -74,6 +74,6 @@ function toggle() {
.text {
color: var(--color-font-1);
min-height: 18rem;
min-height: 1.1rem;
}
</style>

View File

@@ -6,16 +6,16 @@
<style scoped lang="scss">
$w: 22rem;
$w: 1.4rem;
.icon-wrapper {
cursor: pointer;
//padding: 2rem;
width: 26rem;
height: 26rem;
width: 2rem;
height: 2rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 3rem;
border-radius: .3rem;
background: transparent;
transition: all .3s;
color: var(--color-main-active);

View File

@@ -44,9 +44,9 @@ useDisableEventListener(() => focus)
.base-input {
border: 1px solid var(--color-second-bg);
border-radius: 6rem;
border-radius: .4rem;
overflow: hidden;
padding: 3rem 5rem;
padding: .2rem .3rem;
transition: all .3s;
display: flex;
align-items: center;
@@ -68,9 +68,9 @@ useDisableEventListener(() => focus)
input {
font-family: var(--font-family);
font-size: 18rem;
font-size: 1.1rem;
outline: none;
min-height: 20rem;
min-height: 1.2rem;
flex: 1;
box-sizing: border-box;
outline: none;

View File

@@ -18,13 +18,13 @@ function goHome(){
<style scoped lang="scss">
.logo {
//position: fixed;
//left: var(--space);
//top: var(--space);
left: var(--space);
top: var(--space);
z-index: 1;
img {
cursor: pointer;
height: 35rem;
height: 2rem;
}
}
</style>

View File

@@ -480,20 +480,20 @@ function importData(e) {
align-items: center;
.tabs {
padding: 10rem 20rem;
padding: .6rem 1.6rem;
display: flex;
flex-direction: column;
//align-items: center;
//justify-content: center;
gap: 10rem;
gap: .6rem;
.tab {
cursor: pointer;
padding: 10rem 15rem;
border-radius: 8rem;
padding: .6rem .9rem;
border-radius: .5rem;
display: flex;
align-items: center;
gap: 10rem;
gap: .6rem;
&.active {
background: var(--color-item-bg);
@@ -502,9 +502,9 @@ function importData(e) {
}
.git-log {
font-size: 10rem;
font-size: .6rem;
color: gray;
margin-bottom: 5rem;
margin-bottom: .3rem;
}
}
@@ -513,17 +513,17 @@ function importData(e) {
flex: 1;
height: 100%;
overflow: auto;
padding: 10rem var(--space);
padding: 0 var(--space);
.row {
min-height: 40rem;
height: 2.6rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: calc(var(--space) * 5);
.wrapper {
height: 30rem;
height: 2rem;
flex: 1;
display: flex;
justify-content: flex-end;
@@ -532,7 +532,7 @@ function importData(e) {
span {
text-align: right;
//width: 30rem;
font-size: 12rem;
font-size: .7rem;
color: gray;
}
@@ -540,31 +540,34 @@ function importData(e) {
align-items: center;
input {
width: 150rem;
width: 9rem;
box-sizing: border-box;
margin-right: 10rem;
height: 28rem;
margin-right: .6rem;
height: 1.8rem;
outline: none;
font-size: 16rem;
font-size: 1rem;
border: 1px solid gray;
border-radius: 3rem;
padding: 0 5rem;
border-radius: .2rem;
padding: 0 .3rem;
background: var(--color-second-bg);
color: var(--color-font-1);
}
}
}
.main-title {
font-size: 22rem;
font-size: 1.1rem;
font-weight: bold;
}
.item-title {
font-size: 16rem;
font-size: 1rem;
}
.sub-title {
font-size: 14rem;
font-size: .9rem;
}
}
@@ -577,17 +580,17 @@ function importData(e) {
.scroll {
flex: 1;
padding-right: 10rem;
padding-right: .6rem;
overflow: auto;
}
.footer {
margin-bottom: 20rem;
margin-bottom: 1.3rem;
}
.desc {
margin-bottom: 10rem;
font-size: 12rem;
margin-bottom: .6rem;
font-size: .8rem;
}
.line {
@@ -629,7 +632,7 @@ function importData(e) {
color: var(--color-font-1);
p {
font-size: 30rem;
font-size: 2.4rem;
}
.github {
@@ -640,7 +643,7 @@ function importData(e) {
.options {
display: flex;
flex-direction: column;
gap: 10rem;
gap: .6rem;
}
}
}

View File

@@ -59,10 +59,10 @@ export default {
.tip {
position: fixed;
font-size: 14rem;
font-size: 0.8rem;
z-index: 9999;
border-radius: 4rem;
padding: 10rem;
border-radius: .2rem;
padding: .8rem;
color: var(--color-font-1);
background: var(--color-tooltip-bg);
//box-shadow: 1px 1px 6px #bbbbbb;

View File

@@ -389,7 +389,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
display: flex;
gap: var(--space);
padding: var(--space);
padding-top: 10rem;
padding-top: .6rem;
}
.row {
@@ -411,7 +411,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
}
.title {
font-size: 22rem;
font-size: 1.4rem;
text-align: center;
}
@@ -420,11 +420,11 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
//margin-bottom: 10rem;
.label {
height: 45rem;
height: 3rem;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16rem;
font-size: 1rem;
}
}
@@ -441,31 +441,31 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
}
.article-translate {
margin-top: 10rem;
margin-bottom: 20rem;
margin-top: .6rem;
margin-bottom: 1.2rem;
flex: 1;
overflow: auto;
border-radius: 8rem;
border-radius: .5rem;
.section {
background: var(--color-textarea-bg);
margin-bottom: 20rem;
margin-bottom: 1.2rem;
padding: var(--space);
border-radius: 8rem;
border-radius: .5rem;
&:last-child {
margin-bottom: 0;
}
.sentence {
margin-bottom: 20rem;
margin-bottom: 1.2rem;
&:last-child {
margin-bottom: 0;
}
.text {
font-size: 18rem;
font-size: 1.1rem;
}
}
}
@@ -484,14 +484,14 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
.warning {
display: flex;
align-items: center;
font-size: 20rem;
font-size: 1.2rem;
color: red;
}
.success {
display: flex;
align-items: center;
font-size: 20rem;
font-size: 1.2rem;
color: #67C23A;
}

View File

@@ -148,7 +148,7 @@ useWindowClick(() => showExport = false)
:header="false"
>
<div class="add-article">
<div class="slide">
<div class="aslide">
<header>
<div class="dict-name">{{ runtimeStore.editDict.name }}</div>
</header>
@@ -224,18 +224,18 @@ useWindowClick(() => showExport = false)
.close {
position: absolute;
right: 20rem;
top: 20rem;
right: 1.2rem;
top: 1.2rem;
}
.slide {
.aslide {
width: 14vw;
height: 100%;
padding: 0 10rem;
padding: 0 .6rem;
display: flex;
flex-direction: column;
$height: 60rem;
$height: 4rem;
header {
height: $height;
@@ -245,25 +245,25 @@ useWindowClick(() => showExport = false)
//opacity: 0;
.dict-name {
font-size: 30rem;
font-size: 2rem;
color: var(--color-font-1);
}
}
.name {
font-size: 18rem;
font-size: 1.1rem;
}
.translate-name {
font-size: 16rem;
font-size: 1rem;
}
.add {
width: 260rem;
width: 16rem;
box-sizing: border-box;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
border-radius: .5rem;
margin-bottom: .6rem;
padding: .6rem;
display: flex;
justify-content: space-between;
transition: all .3s;
@@ -274,7 +274,7 @@ useWindowClick(() => showExport = false)
.footer {
height: $height;
display: flex;
gap: 10rem;
gap: .6rem;
align-items: center;
justify-content: flex-end;

View File

@@ -70,25 +70,25 @@ onUnmounted(() => {
.article-content {
flex: 1;
overflow: hidden;
font-size: 20rem;
font-size: 1.2rem;
display: flex;
flex-direction: column;
.title {
text-align: center;
margin-bottom: var(--space);
font-size: 24rem;
font-size: 1.4rem;
}
.text {
text-indent: 1.5em;
line-height: 35rem;
line-height: 2rem;
overflow: auto;
padding-right: 10rem;
padding-bottom: 50rem;
padding-right: .6rem;
padding-bottom: 3rem;
.sentence {
margin-bottom: 30rem;
margin-bottom: 3rem;
}
}
}

View File

@@ -189,18 +189,15 @@ async function cancel() {
@import "@/assets/css/variable";
$modal-mask-bg: rgba(#000, .45);
$radius: 8rem;
$radius: .5rem;
$time: 0.3s;
$header-height: 60rem;
$header-height: 4rem;
@keyframes bounce-in {
0% {
opacity: 0;
transform: scale(0);
}
50% {
transform: scale(1.15);
}
100% {
opacity: 1;
transform: scale(1);
@@ -288,8 +285,8 @@ $header-height: 60rem;
.close {
position: absolute;
right: 20rem;
top: 20rem;
right: 1.2rem;
top: 1.2rem;
z-index: 999;
}
@@ -297,14 +294,14 @@ $header-height: 60rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rem 24rem 16rem;
padding: 1.3rem 1.3rem 1rem;
border-radius: $radius $radius 0 0;
.title {
color: var(--color-font-1);
font-weight: bold;
font-size: 24rem;
line-height: 33rem;
font-size: 1.3rem;
line-height: 1.8rem;
}
}
@@ -312,21 +309,21 @@ $header-height: 60rem;
box-sizing: border-box;
color: rgba(255, 255, 255, 0.8);
font-weight: 400;
font-size: 18rem;
line-height: 27rem;
font-size: 1.1rem;
line-height: 1.7rem;
width: 100%;
flex: 1;
overflow: hidden;
display: flex;
&.padding {
padding: 4rem 24rem 24rem;
padding: .2rem 1.6rem 1.6rem;
}
.content {
width: 350rem;
width: 25rem;
color: var(--color-font-1);
padding: 4rem 24rem 24rem;
padding: .2rem 1.6rem 1.6rem;
}
}
@@ -334,11 +331,11 @@ $header-height: 60rem;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rem 24rem;
padding: 1rem 1.6rem;
color: #fff;
font-size: 18rem;
font-size: 1.1rem;
background: rgba(0, 0, 0, .2);
border-radius: 0 0 24rem 24rem;
border-radius: 0 0 1.6rem 1.6rem;
.left {
display: flex;
@@ -347,7 +344,7 @@ $header-height: 60rem;
.text {
color: white;
font-size: 16rem;
font-size: 1rem;
cursor: pointer;
}

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {onMounted} from "vue"
import {DefaultDict, Dict, DictResource, DictType, Sort, Word} from "@/types.ts"
import {chunk, cloneDeep, reverse, shuffle} from "lodash-es";
import {chunk} from "lodash-es";
import {$computed, $ref} from "vue/macros";
import BaseButton from "@/components/BaseButton.vue";
import {Icon} from '@iconify/vue';
import "vue-activity-calendar/style.css";
@@ -11,106 +11,24 @@ import {isArticle} from "@/hooks/article.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import Slide from "@/pages/pc/components/Slide.vue";
import Empty from "@/components/Empty.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import EditBatchArticleModal from "@/pages/pc/components/article/EditBatchArticleModal.vue";
import {nanoid} from "nanoid";
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
import {useRouter} from "vue-router";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import BaseList from "@/pages/pc/components/list/BaseList.vue";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {getDictFile} from "@/utils";
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let router = useRouter()
let step = $ref(1)
let loading = $ref(false)
let show = $ref(false)
let chapterList2 = $ref([])
let chapterWordNumber = $ref(0)
let toggleLoading = $ref(false)
const activeId = $computed(() => {
if (dictIsArticle) {
return runtimeStore.editDict.articles?.[runtimeStore.editDict.chapterIndex].id ?? ''
}
return ''
})
async function selectDict(val: { dict: DictResource | Dict, index: number }) {
let item = val.dict
// console.log('item', item)
step = 1
loading = true
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
} else {
runtimeStore.editDict = cloneDeep({
...cloneDeep(DefaultDict),
...item,
})
runtimeStore.editDict.id = nanoid(6)
//设置默认章节单词数
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
}
if ([DictType.collect, DictType.simple, DictType.wrong].includes(runtimeStore.editDict.type)) {
} else {
//如果不是自定义词典并且有url地址才去下载
if (!runtimeStore.editDict.isCustom && runtimeStore.editDict.url) {
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
if (runtimeStore.editDict.type === DictType.word) {
if (!runtimeStore.editDict.originWords.length) {
let v = await getDictFile(url)
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort, false)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.words.length
}
}
if (runtimeStore.editDict.type === DictType.article) {
if (!runtimeStore.editDict.articles.length) {
let v = await getDictFile(url)
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.articles = cloneDeep(v)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.articles.length
}
}
}
}
chapterWordNumber = runtimeStore.editDict.chapterWordNumber
chapterList2 = runtimeStore.editDict.chapterWords.map((v, i) => ({id: i}))
loading = false
}
function close() {
show = false
}
//TODO 切大词典太卡了
function changeDict() {
close()
store.changeDict(runtimeStore.editDict)
setTimeout(() => {
runtimeStore.editDict = cloneDeep(DefaultDict)
})
ElMessage.success('切换成功')
}
const dictIsArticle = $computed(() => {
return isArticle(runtimeStore.editDict.type)
})
@@ -127,41 +45,9 @@ function resetChapterList(v: number) {
const temp = () => {
runtimeStore.editDict.chapterWordNumber = v
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
chapterList2 = runtimeStore.editDict.chapterWords.map((v, i) => ({id: i}))
}
if (runtimeStore.editDict.isCustom) {
MessageBox.confirm(
'检测到您已对这本词典自定义修改,修改“每章单词数”将会导致所有章节被重新分配,原有章节内容将被清除且不可恢复,是否继续?',
'提示',
() => temp(),
() => {
chapterWordNumber = runtimeStore.editDict.chapterWordNumber
}
)
} else {
temp()
}
}
function changeSort(v: Sort, notice: boolean = true) {
const temp = () => {
runtimeStore.editDict.sort = v
if (v === Sort.normal) {
runtimeStore.editDict.words = cloneDeep(runtimeStore.editDict.originWords)
} else if (v === Sort.random) {
runtimeStore.editDict.words = shuffle(cloneDeep(runtimeStore.editDict.originWords))
} else {
runtimeStore.editDict.words = reverse(cloneDeep(runtimeStore.editDict.originWords))
}
resetChapterList(runtimeStore.editDict.chapterWordNumber)
notice && ElMessage.success('已重新排序')
}
if (runtimeStore.editDict.isCustom) {
MessageBox.confirm(
'检测到您已对这本词典自定义修改,修改“单词排序”将会导致所有章节被重新分配,原有章节内容将被清除且不可恢复,是否继续?',
'提示',
() => temp()
)
} else {
temp()
}
@@ -175,36 +61,11 @@ function option(type: string) {
}
onMounted(() => {
emitter.on(EventKey.openDictModal, (type: 'detail' | 'list' | 'my') => {
if (type === "detail") {
selectDict({dict: store.currentDict, index: 0})
}
if (type === "list") {
// currentLanguage = 'en'
step = 0
}
if (type === "my") {
// currentLanguage = 'my'
step = 0
}
emitter.on(EventKey.openDictModal, (typ) => {
show = true
})
})
function showWordListModal(val: { item: Word, index: number }) {
emitter.emit(EventKey.openWordListModal, {
title: `${val.index + 1}`,
translateLanguage: runtimeStore.editDict.translateLanguage,
list: runtimeStore.editDict.chapterWords[val.index]
})
}
function handleChangeArticleChapterIndex(val: any) {
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
runtimeStore.editDict.chapterIndex = rIndex
}
}
</script>
@@ -214,215 +75,67 @@ function handleChangeArticleChapterIndex(val: any) {
v-model="show"
:show-close="false">
<div id="DictDialog">
<Slide :slide-count="2" :step="step">
<DictListPanel
@add="option('addDict')"
@select-dict="selectDict"
/>
<div class="dict-detail-page">
<header>
<div class="left" @click.stop="step = 0">
<Icon icon="octicon:arrow-left-24" class="go" width="20"/>
<div class="title">
词典详情
</div>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail">
<div class="page-content">
<div class="left-column">
<BaseIcon
v-if="![DictType.collect,DictType.wrong,DictType.simple].includes(runtimeStore.editDict.type)"
class="edit-icon"
title="编辑词典"
icon="tabler:edit"
@click='option("editDict")'
/>
<div class="name">{{ runtimeStore.editDict.name }}</div>
<div class="desc">{{ runtimeStore.editDict.description }}</div>
<div class="text flex align-center gap10">
<div v-if="dictIsArticle">总文章{{ runtimeStore.editDict.articles.length }}
</div>
<div v-else>总词汇
<span class="count" @click="showAllWordModal">{{
runtimeStore.editDict.originWords.length
}}</span>
</div>
<BaseIcon icon="mi:add"
@click='option("addWordOrArticle")'
:title="`添加${dictIsArticle?'文章':'单词'}`"
/>
</div>
<template v-if="false">
<div class="text">开始日期-</div>
<div class="text">花费时间-</div>
<div class="text">累积错误-</div>
<div class="text">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
</template>
</div>
<div class="center-column">
<div class="common-title">学习设置</div>
<div class="setting">
<template v-if="!dictIsArticle">
<div class="row">
<div class="label">每章单词数</div>
<el-slider
class="my-slider"
:min="10"
:step="10"
:max="runtimeStore.editDict.words.length < 10 ? 10 : runtimeStore.editDict.words.length"
show-input
:show-input-controls="false"
size="small"
v-model="chapterWordNumber"
@change="resetChapterList"
/>
</div>
<div class="notice">
<span class="text">最小:10</span>
<span class="text">最大:{{ runtimeStore.editDict.words.length }}</span>
</div>
<div class="row">
<div class="label">单词顺序</div>
<div class="option">
<el-radio-group :model-value="runtimeStore.editDict.sort"
@change="changeSort"
>
<el-radio :label="Sort.normal" size="large">默认</el-radio>
<el-radio :label="Sort.random" size="large">随机</el-radio>
<el-radio :label="Sort.reverse" size="large">反转</el-radio>
</el-radio-group>
</div>
</div>
</template>
<div class="row">
<div class="label">学习模式</div>
<div class="option">
<el-radio-group v-model="settingStore.dictation">
<el-radio :label="false" size="large">再认</el-radio>
<el-radio :label="true" size="large">拼写</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">{{ dictIsArticle ? '句子' : '单词' }}发音</div>
<div class="option">
<el-radio-group v-model="settingStore.wordSoundType">
<el-radio label="us" size="large">美音</el-radio>
<el-radio label="uk" size="large">英音</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">{{ dictIsArticle ? '句子' : '单词' }}自动发音</div>
<div class="option">
<el-switch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<div class="label">是否显示翻译</div>
<div class="option">
<el-switch v-model="settingStore.translate"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<div class="label">忽略大小写</div>
<div class="option">
<el-switch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
</div>
</div>
<div class="right-column">
<div class="common-title">
<span>{{ dictIsArticle ? '文章' : '章节' }}列表</span>
<BaseIcon
icon="fluent:notepad-edit-20-regular"
@click='option("detail")'
style="position: absolute;right: 20rem;"
:title="`管理${dictIsArticle?'文章':'章节'}`"
/>
</div>
<div class="list-content">
<template v-if="dictIsArticle">
<ArticleList
v-if="runtimeStore.editDict.articles.length"
:isActive="false"
v-loading="loading"
:show-border="true"
@title="(val:any) => emitter.emit(EventKey.openArticleContentModal,val.item)"
@click="handleChangeArticleChapterIndex"
:active-id="activeId"
:list="runtimeStore.editDict.articles">
<template v-slot:prefix="{item,index}">
<input type="radio" :checked="activeId === item.id">
</template>
</ArticleList>
<Empty v-else/>
</template>
<template v-else>
<BaseList
ref="chapterListRef"
v-if="chapterList2.length"
:list="chapterList2"
:show-border="true"
@click="(val:any) => runtimeStore.editDict.chapterIndex = val.index"
:active-index="runtimeStore.editDict.chapterIndex"
>
<template v-slot:prefix="{ item, index }">
<input type="radio" :checked="runtimeStore.editDict.chapterIndex === item.id">
</template>
<template v-slot="{ item, index }">
<div class="item-title" @click.stop="showWordListModal({item,index})">
<span>{{ item.id + 1 }}</span>&nbsp;&nbsp;&nbsp;
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</template>
</BaseList>
<Empty v-else/>
</template>
</div>
<div class="footer">
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton :loading="toggleLoading" @click="changeDict">切换</BaseButton>
</div>
</div>
</div>
</div>
<header>
<div class="text-2xl">
{{ store.currentDict.name }}
</div>
</Slide>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail">
<div class="desc">{{ store.currentDict.description }}</div>
<div class="text flex align-center">
<div v-if="dictIsArticle">总文章{{ store.currentDict.articles.length }}
</div>
<div v-else>总词汇
<span class="count" @click="showAllWordModal">{{
store.currentDict.originWords.length
}}</span>
</div>
<BaseIcon icon="mi:add"
@click='option("addWordOrArticle")'
:title="`添加${dictIsArticle?'文章':'单词'}`"
/>
</div>
<div class="text">开始日期-</div>
<div class="text">花费时间-</div>
<div class="text">累积错误-</div>
<div class="text">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
<div class="row">
<div class="label">每日目标</div>
<el-slider
class="my-slider"
:min="10"
:step="10"
:max="store.currentDict.words.length < 10 ? 10 : 500"
size="small"
v-model="chapterWordNumber"
@change="resetChapterList"
/>
</div>
<div class="notice">
<span class="text">最小:10</span>
<span class="text">最大:{{ store.currentDict.words.length < 10 ? 10 : 500 }}</span>
</div>
</div>
<div class="footer">
<BaseButton @click="close">关闭</BaseButton>
</div>
</div>
</Dialog>
<WordListDialog/>
<EditBatchArticleModal/>
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
$header-height: 60rem;
$header-height: 4rem;
#DictDialog {
//position: fixed;
@@ -431,13 +144,7 @@ $header-height: 60rem;
//transform: translate(-50%, -50%);
background: var(--color-second-bg);
z-index: 99999;
width: 1030rem;
height: 75vh;
}
.dict-detail-page {
width: 50%;
height: 100%;
width: 30rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -455,7 +162,7 @@ $header-height: 60rem;
.left {
display: flex;
gap: 10rem;
gap: .6rem;
align-items: center;
}
}
@@ -464,110 +171,48 @@ $header-height: 60rem;
padding-left: var(--space);
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
flex-direction: column;
background: var(--color-second-bg);
color: var(--color-font-1);
gap: .2rem;
position: relative;
font-size: .9rem;
padding-right: var(--space);
.page-content {
flex: 1;
overflow: hidden;
display: flex;
position: relative;
.column {
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
}
.left-column {
flex: 5;
gap: 10rem;
position: relative;
font-size: 14rem;
padding-right: var(--space);
@extend .column;
.name {
font-size: 24rem;
width: 95%;
}
.desc {
font-size: 16rem;
margin-bottom: 20rem;
}
.count {
cursor: pointer;
border-bottom: 2px solid var(--color-item-active);
}
:deep(.edit-icon) {
position: absolute;
top: 0;
right: 0;
}
}
.center-column {
overflow: auto;
flex: 7;
@extend .column;
.setting {
.row {
display: flex;
align-items: center;
justify-content: space-between;
height: 34rem;
word-break: keep-all;
gap: 10rem;
.el-radio {
margin-right: 10rem;
}
}
.my-slider {
:deep(.el-slider__input) {
width: 55px;
}
}
.notice {
display: flex;
justify-content: space-between;
transform: translate3d(0, -5rem, 0);
padding-left: 100rem;
font-size: 13rem;
}
}
}
.right-column {
flex: 7;
@extend .column;
.list-content {
flex: 1;
overflow: hidden;
display: flex;
}
}
.name {
font-size: 1.6rem;
width: 95%;
}
.footer {
box-sizing: content-box;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: var(--space);
padding-right: var(--space);
margin: var(--space) 0;
.desc {
font-size: 1rem;
margin-bottom: 1.2rem;
}
.count {
cursor: pointer;
border-bottom: 2px solid var(--color-item-active);
}
:deep(.edit-icon) {
position: absolute;
top: 0;
right: 0;
}
}
.footer {
box-sizing: content-box;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: var(--space);
padding-right: var(--space);
margin: var(--space) 0;
}
}
</style>

View File

@@ -19,19 +19,19 @@ withDefaults(defineProps<IProps>(), {
</Transition>
</template>
<style lang="scss">
<style lang="scss">
@import "@/assets/css/style";
.mini-row-title {
min-height: 35rem;
min-height: 2rem;
text-align: center;
font-size: 16rem;
font-size: 1rem;
font-weight: bold;
color: var(--color-font-1);
}
.mini-row {
min-height: 35rem;
min-height: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
@@ -43,12 +43,12 @@ withDefaults(defineProps<IProps>(), {
.mini-modal {
position: absolute;
z-index: 9;
width: 180rem;
width: 12rem;
background: var(--color-second-bg);
border-radius: 8rem;
border-radius: .5rem;
box-shadow: 0 0 8px 2px var(--color-item-border);
padding: 10rem var(--space);
top: 40rem;
padding: .6rem var(--space);
top: 2.4rem;
left: 50%;
transform: translate3d(-50%, 0, 0);
//margin-top: 10rem;

View File

@@ -32,20 +32,20 @@ let disabledDialogEscKey = $ref(true)
align-items: center;
.tabs {
padding: 10rem 20rem;
padding: .6rem 1.6rem;
display: flex;
flex-direction: column;
//align-items: center;
//justify-content: center;
gap: 10rem;
gap: .6rem;
.tab {
cursor: pointer;
padding: 10rem 15rem;
border-radius: 8rem;
padding: .6rem .9rem;
border-radius: .5rem;
display: flex;
align-items: center;
gap: 10rem;
gap: .6rem;
&.active {
background: var(--color-item-bg);
@@ -54,9 +54,9 @@ let disabledDialogEscKey = $ref(true)
}
.git-log {
font-size: 10rem;
font-size: .6rem;
color: gray;
margin-bottom: 5rem;
margin-bottom: .3rem;
}
}
@@ -68,14 +68,14 @@ let disabledDialogEscKey = $ref(true)
padding: 0 var(--space);
.row {
height: 40rem;
height: 2.6rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: calc(var(--space) * 5);
.wrapper {
height: 30rem;
height: 2rem;
flex: 1;
display: flex;
justify-content: flex-end;
@@ -84,7 +84,7 @@ let disabledDialogEscKey = $ref(true)
span {
text-align: right;
//width: 30rem;
font-size: 12rem;
font-size: .7rem;
color: gray;
}
@@ -92,15 +92,15 @@ let disabledDialogEscKey = $ref(true)
align-items: center;
input {
width: 150rem;
width: 9rem;
box-sizing: border-box;
margin-right: 10rem;
height: 28rem;
margin-right: .6rem;
height: 1.8rem;
outline: none;
font-size: 16rem;
font-size: 1rem;
border: 1px solid gray;
border-radius: 3rem;
padding: 0 5rem;
border-radius: .2rem;
padding: 0 .3rem;
background: var(--color-second-bg);
color: var(--color-font-1);
}
@@ -110,16 +110,16 @@ let disabledDialogEscKey = $ref(true)
}
.main-title {
font-size: 18rem;
font-size: 1.1rem;
font-weight: bold;
}
.item-title {
font-size: 16rem;
font-size: 1rem;
}
.sub-title {
font-size: 14rem;
font-size: .9rem;
}
}
@@ -132,17 +132,17 @@ let disabledDialogEscKey = $ref(true)
.scroll {
flex: 1;
padding-right: 10rem;
padding-right: .6rem;
overflow: auto;
}
.footer {
margin-bottom: 20rem;
margin-bottom: 1.3rem;
}
.desc {
margin-bottom: 10rem;
font-size: 12rem;
margin-bottom: .6rem;
font-size: .8rem;
}
.line {

View File

@@ -77,7 +77,7 @@ defineExpose({scrollToBottom, scrollToItem})
.list {
display: flex;
flex-direction: column;
gap: 15rem;
gap: 1rem;
flex: 1;
overflow: hidden;
@@ -88,7 +88,7 @@ defineExpose({scrollToBottom, scrollToItem})
}
.translate {
font-size: 16rem;
font-size: 1rem;
}
}
</style>

View File

@@ -26,13 +26,16 @@ watch(() => props.groupByTag, () => {
<template>
<div class="dict-group">
<div class="category">{{ category }}</div>
<div class="tags">
<div class="tag" :class="i === currentTag &&'active'"
@click="currentTag = i"
v-for="i in Object.keys(groupByTag)">{{ i }}
<div class="flex items-center border">
<div class="category">{{ category }}</div>
<div class="tags">
<div class="tag" :class="i === currentTag &&'active'"
@click="currentTag = i"
v-for="i in Object.keys(groupByTag)">{{ i }}
</div>
</div>
</div>
<DictList
@selectDict="e => emit('selectDict',e)"
:list="list"
@@ -43,26 +46,28 @@ watch(() => props.groupByTag, () => {
<style scoped lang="scss">
.dict-group {
color: var(--color-font-1);
margin-bottom: 40rem;
margin-bottom: 1.5rem;
//border-bottom: 1px dashed gray;
.category {
font-size: 24rem;
padding-bottom: 10rem;
border-bottom: 1px dashed gray;
font-size: 1.2rem;
//padding-bottom: 1rem;
}
.border{
border-top: 1px dashed gray;
}
}
.tags {
display: flex;
flex-wrap: wrap;
margin: 10rem 0;
margin: 1rem 0;
.tag {
color: var(--color-font-1);
cursor: pointer;
padding: 5rem 10rem;
border-radius: 20rem;
padding: 0.4rem 1rem;
border-radius: 2rem;
&.active {
color: var(--color-font-active-1);

View File

@@ -2,6 +2,7 @@
import {Dict, DictType} from "@/types.ts";
import {Icon} from "@iconify/vue";
import {$computed} from "vue/macros";
import BaseIcon from "@/components/BaseIcon.vue";
const props = defineProps<{
@@ -34,7 +35,7 @@ let length = $computed(() => {
<template>
<div
class="dict-item anim"
class="dict-item anim rounded-md p-4"
:class="active && 'active'"
>
<template v-if="dict.id">
@@ -61,22 +62,21 @@ let length = $computed(() => {
.dict-item {
cursor: pointer;
box-sizing: border-box;
padding: 10rem;
width: 125rem;
height: 165rem;
border-radius: 10rem;
//width: 9rem;
//height: 12rem;
position: relative;
background: var(--color-third-bg);
//background: var(--color-third-bg);
background: white;
border: 1px solid var(--color-item-border);
color: var(--color-font-1);
font-size: 14rem;
font-size: 1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
.name {
font-size: 16rem;
font-size: 1rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; //作为弹性伸缩盒子模型显示。
@@ -94,6 +94,7 @@ let length = $computed(() => {
}
.num {
margin-top: 2rem;
text-align: right;
color: var(--color-font-2);
//font-weight: bold;
@@ -101,14 +102,14 @@ let length = $computed(() => {
.go {
position: absolute;
right: 10rem;
bottom: 15rem;
right: 1rem;
bottom: 1rem;
}
.del {
position: absolute;
top: 10rem;
right: 10rem;
top: 1rem;
right: 1rem;
opacity: 0;
transition: opacity .3s;
}
@@ -137,17 +138,17 @@ let length = $computed(() => {
position: absolute;
bottom: 0;
left: 0;
height: 55rem;
width: 55rem;
height: 4rem;
width: 4rem;
color: white;
//background-color: skyblue;
background-color: var(--color-main-active);
clip-path: polygon(0 10%, 0% 100%, 100% 100%);
font-size: 12rem;
font-size: 0.8rem;
display: flex;
justify-content: flex-start;
align-items: flex-end;
padding: 4rem;
padding: 0.2rem;
box-sizing: border-box;
}
}

View File

@@ -18,7 +18,7 @@ const emit = defineEmits<{
</script>
<template>
<div class="dict-list">
<div class="dict-list1 grid grid-cols-4 gap-4">
<DictItem v-for="(dict,index) in list"
:active="selectId === dict.id"
@click="emit('selectDict',{dict,index})"
@@ -32,7 +32,7 @@ const emit = defineEmits<{
.dict-list {
display: flex;
flex-wrap: wrap;
gap: 15rem;
gap: 1rem;
}
</style>

View File

@@ -161,10 +161,10 @@ defineExpose({scrollBottom})
transition: all .3s;
flex: 1;
overflow: overlay;
padding-right: 5rem;
padding-right: .3rem;
.search {
margin: 10rem 0;
margin: .6rem 0;
}
.list {
@@ -172,9 +172,9 @@ defineExpose({scrollBottom})
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
border-radius: .5rem;
margin-bottom: .6rem;
padding: .6rem;
display: flex;
justify-content: space-between;
transition: all .3s;

View File

@@ -1,108 +0,0 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import {useWindowClick} from "@/hooks/event.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {nextTick, watch} from "vue";
const store = useBaseStore()
let timer = 0
let show = $ref(false)
useWindowClick(() => show = false)
function toggle(val) {
clearTimeout(timer)
if (val) {
emitter.emit(EventKey.closeOther)
show = val
} else {
timer = setTimeout(() => {
show = val
}, 100)
}
}
function clickJumpSpecifiedChapter(index: number) {
emitter.emit(EventKey.jumpSpecifiedChapter, index)
}
const listRef: HTMLElement = $ref(null as any)
watch(() => show, n => {
if (n){
nextTick(()=>{
listRef?.children[store.currentDict.chapterIndex]?.scrollIntoView({block: 'center'})
})
}
})
</script>
<template>
<div class="ChapterName" @click.stop="null">
<div class="info hvr-grow"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
>
{{ store.chapterName }}
</div>
<MiniDialog
v-model="show"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
style="width: 230rem;"
>
<div class="chapter-list" ref="listRef">
<div class="chapter-list-item"
:class="store.currentDict.chapterIndex === index && 'active'"
v-for="(item,index) in store.currentDict.chapterWords"
@click="clickJumpSpecifiedChapter(index)">
<input type="radio" :checked="store.currentDict.chapterIndex === index">
<div class="title">{{ index + 1 }}&nbsp;&nbsp;&nbsp;{{ item.length }}</div>
</div>
</div>
</MiniDialog>
</div>
</template>
<style scoped lang="scss">
.ChapterName {
position: relative;
}
.chapter-list {
max-height: 250rem;
overflow: auto;
padding-right: 5rem;
.chapter-list-item {
margin-bottom: 7rem;
height: 36rem;
display: flex;
align-items: center;
gap: 10rem;
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
font-size: 18rem;
border-radius: 8rem;
transition: all .3s;
padding: 10rem;
border: 1px solid var(--color-item-border);
&:hover {
background: var(--color-item-hover);
}
&.active {
background: var(--color-item-active);
}
}
}
.el-radio-group {
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>

View File

@@ -46,7 +46,7 @@ onMounted(() => {
v-model="show"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
style="width: 230rem;"
style="width: 15rem;"
>
<div class="mini-row-title">
单词循环设置

View File

@@ -42,7 +42,7 @@ function save() {
<template>
<div class="setting" @click.stop="null">
<Tooltip
:title="`开关释义显示(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
>
<IconWrapper>
<Icon v-if="settingStore.translate" icon="mdi:translate"
@@ -114,9 +114,9 @@ function save() {
}
.footer {
margin-top: 10rem;
margin-top: .6rem;
display: flex;
justify-content: flex-end;
gap: 10rem;
gap: .6rem;
}
</style>

View File

@@ -58,7 +58,7 @@ function toggle2() {
</IconWrapper>
</Tooltip>
<MiniDialog
width="250rem"
width="12rem"
v-model="show">
<div class="mini-row-title">
音效设置
@@ -75,7 +75,7 @@ function toggle2() {
</div>
</div>
<div class="mini-row">
<label class="item-title">单词/句子自动发音</label>
<label class="item-title">自动发音</label>
<div class="wrapper">
<el-switch v-model="settingStore.wordSound"
inline-prompt
@@ -85,7 +85,7 @@ function toggle2() {
</div>
</div>
<div class="mini-row">
<label class="item-title">单词/句子发音口音</label>
<label class="item-title">口音</label>
<div class="wrapper">
<el-select v-model="settingStore.wordSoundType"
placeholder="请选择"

View File

@@ -13,11 +13,11 @@ import TranslateSetting from "@/pages/pc/components/toolbar/TranslateSetting.vue
import {useSettingStore} from "@/stores/setting.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {DictType, ShortcutKey} from "@/types.ts";
import ChapterName from "@/pages/pc/components/toolbar/ChapterName.vue";
import {$ref} from "vue/macros";
import {ShortcutKey} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import {useNav} from "@/utils";
const {toggleTheme} = useTheme()
const store = useBaseStore()
@@ -31,7 +31,7 @@ const moreOptionsRef = $ref<HTMLDivElement>(null)
watch([() => settingStore.showToolbar, () => headerRef], n => {
if (n[1]) {
if (n[0]) {
n[1].style.marginTop = '10rem'
n[1].style.marginTop = '.8rem'
} else {
let rect = n[1].getBoundingClientRect()
n[1].style.marginTop = `-${rect.height}px`
@@ -56,6 +56,7 @@ watch(() => store.load, n => {
}
})
const {nav} = useNav()
</script>
<template>
@@ -63,12 +64,14 @@ watch(() => store.load, n => {
<div class="content">
<div class="dict-name">
<Tooltip
:title="`词典详情(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info hvr-grow" @click="emitter.emit(EventKey.openDictModal,'detail')">
:title="`词典详情(${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info" @click="emitter.emit(EventKey.openDictModal,'detail')">
{{ store.currentDict.name }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
</div>
</Tooltip>
<ChapterName v-if="store.currentDict.type === DictType.word"/>
<BaseIcon title="切换词典"
@click="nav('/dict')"
icon="gg:arrows-exchange"/>
<div class="info-text" v-if="practiceStore.repeatNumber">
复习错词
</div>
@@ -84,7 +87,7 @@ watch(() => store.load, n => {
</Tooltip>
<Tooltip
:title="`开关默写模式(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
>
<IconWrapper>
<Icon icon="majesticons:eye-off-line"
@@ -102,16 +105,8 @@ watch(() => store.load, n => {
<RepeatSetting/>
<!-- <Add/>-->
<BaseIcon
@click="emitter.emit(EventKey.openDictModal,'my')"
title="添加"
icon="ic:outline-cloud-upload"/>
<Tooltip
:title="`切换主题(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
:title="`切换主题(${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
>
<IconWrapper>
<Icon icon="ep:moon" v-if="settingStore.theme === 'dark'"
@@ -119,12 +114,9 @@ watch(() => store.load, n => {
<Icon icon="tabler:sun" v-else @click="toggleTheme"/>
</IconWrapper>
</Tooltip>
</div>
<div class="with-bg anim">
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`单词本(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
:title="`单词本(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
icon="tdesign:menu-unfold"/>
</div>
</div>
@@ -143,14 +135,14 @@ watch(() => store.load, n => {
<style lang="scss">
.info {
border-radius: 6rem;
border-radius: .5rem;
color: var(--color-font-1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all .3s;
padding: 6rem 8rem;
padding: .5rem .6rem;
&:hover {
background: var(--color-main-active);
@@ -182,15 +174,15 @@ watch(() => store.load, n => {
header {
width: var(--toolbar-width);
margin-top: 10rem;
margin-top: 1rem;
background: var(--color-second-bg);
border-radius: 8rem;
margin-bottom: 30rem;
border-radius: .8rem;
margin-bottom: 3rem;
position: relative;
z-index: 2;
padding: 4rem var(--space);
padding: .4rem var(--space);
box-sizing: border-box;
gap: 10rem;
gap: 1rem;
border: 1px solid var(--color-item-border);
transition: all var(--anim-time);
box-shadow: var(--shadow);
@@ -204,12 +196,13 @@ header {
.dict-name {
display: flex;
max-width: 45%;
font-size: 17rem;
font-size: 1rem;
position: relative;
gap: .5rem;
}
.hide {
transform: translateX(calc(100% - 36rem));
transform: translateX(calc(100% - 2rem));
}
.options {
@@ -217,12 +210,9 @@ header {
align-items: center;
overflow: hidden;
.icon-wrapper {
margin-left: 10rem;
}
:deep(.icon-wrapper) {
margin-left: 10rem;
margin-left: .2rem;
}
.more {
@@ -230,13 +220,6 @@ header {
align-items: center;
transition: all .3s;
}
.with-bg {
display: flex;
align-items: center;
position: relative;
background: var(--color-second-bg);
}
}
}
@@ -247,7 +230,7 @@ header {
cursor: pointer;
transition: all .5s;
transform: translate3d(-50%, 100%, 0) rotate(180deg);
padding: 5rem;
padding: .5rem;
&.down {
transform: translate3d(-50%, 100%, 0) rotate(0);

View File

@@ -145,7 +145,7 @@ onMounted(() => {
@import "@/assets/css/variable";
#DictDialog {
font-size: 14rem;
font-size: .9rem;
position: absolute;
left: 50%;
top: 50%;

View File

@@ -407,8 +407,8 @@ defineExpose({getDictDetail, add, editDict})
align-items: center;
color: var(--color-font-1);
padding: 0 var(--space);
gap: 20rem;
margin-bottom: 20rem;
gap: 1.2rem;
margin-bottom: 1.2rem;
.back {
height: 100%;
@@ -419,15 +419,15 @@ defineExpose({getDictDetail, add, editDict})
.left {
display: flex;
gap: 10rem;
gap: .6rem;
flex-direction: column;
color: var(--color-font-2);
.top {
color: var(--color-font-1);
display: flex;
gap: 10rem;
font-size: 20rem;
gap: .6rem;
font-size: 1.2rem;
align-items: center;
}
@@ -447,7 +447,7 @@ defineExpose({getDictDetail, add, editDict})
.box {
background: white;
border-radius: 10rem;
border-radius: .6rem;
background: var(--color-second-bg);
color: var(--color-font-1);
padding-bottom: var(--space);
@@ -463,7 +463,7 @@ defineExpose({getDictDetail, add, editDict})
}
.chapter-list {
width: 400rem;
width: 25rem;
height: 100%;
@extend .box;
@@ -478,19 +478,19 @@ defineExpose({getDictDetail, add, editDict})
position: absolute;
right: 0;
display: flex;
gap: 10rem;
gap: .6rem;
}
}
.select {
height: 45rem;
height: 3rem;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
gap: 5rem;
gap: .3rem;
align-items: center;
}
}
@@ -509,7 +509,7 @@ defineExpose({getDictDetail, add, editDict})
flex: 1;
padding: var(--space);
overflow: hidden;
font-size: 20rem;
font-size: 1.2rem;
.title {
display: flex;
@@ -517,18 +517,18 @@ defineExpose({getDictDetail, add, editDict})
align-items: center;
position: relative;
margin-bottom: var(--space);
font-size: 24rem;
font-size: 1.4rem;
}
.text {
text-indent: 1.5em;
line-height: 35rem;
line-height: 2.6rem;
overflow: auto;
padding-right: 10rem;
padding-bottom: 50rem;
padding-right: .6rem;
padding-bottom: 3rem;
.sentence {
margin-bottom: 30rem;
margin-bottom: 2.4rem;
}
}
}

View File

@@ -183,19 +183,19 @@ useWindowClick(() => show = false)
position: absolute;
right: 0;
display: flex;
gap: 10rem;
gap: .6rem;
}
}
.select {
height: 45rem;
height: 3rem;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
gap: 5rem;
gap: .3rem;
align-items: center;
}
}
@@ -210,7 +210,7 @@ useWindowClick(() => show = false)
.column {
flex: 1;
background: white;
border-radius: 10rem;
border-radius: .6rem;
background: var(--color-second-bg);
color: var(--color-font-1);
padding-bottom: var(--space);

View File

@@ -180,7 +180,7 @@ onMounted(() => {
justify-content: center;
.wrapper {
width: 500rem;
width: 80rem;
}
.el-select {

View File

@@ -779,8 +779,8 @@ defineExpose({getDictDetail, add: addWord, editDict})
align-items: center;
color: var(--color-font-1);
padding: 0 var(--space);
gap: 20rem;
margin-bottom: 20rem;
gap: 1.2rem;
margin-bottom: 1.2rem;
.back {
height: 100%;
@@ -791,15 +791,15 @@ defineExpose({getDictDetail, add: addWord, editDict})
.left {
display: flex;
gap: 10rem;
gap: .6rem;
flex-direction: column;
color: var(--color-font-2);
.top {
color: var(--color-font-1);
display: flex;
gap: 10rem;
font-size: 20rem;
gap: .6rem;
font-size: 1.2rem;
align-items: center;
}
@@ -842,14 +842,14 @@ defineExpose({getDictDetail, add: addWord, editDict})
}
.select {
height: 45rem;
height: 3rem;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
gap: 5rem;
gap: .3rem;
align-items: center;
}
}
@@ -864,7 +864,7 @@ defineExpose({getDictDetail, add: addWord, editDict})
.column {
flex: 1;
background: white;
border-radius: 10rem;
border-radius: .6rem;
background: var(--color-second-bg);
color: var(--color-font-1);
padding-bottom: var(--space);
@@ -873,7 +873,7 @@ defineExpose({getDictDetail, add: addWord, editDict})
}
.left-column {
max-width: 250rem;
max-width: 16rem;
width: 16vw;
@extend .column;
}
@@ -882,7 +882,7 @@ defineExpose({getDictDetail, add: addWord, editDict})
display: flex;
align-items: center;
justify-content: center;
gap: 10rem;
gap: .6rem;
}
.right-column {
@@ -898,39 +898,39 @@ defineExpose({getDictDetail, add: addWord, editDict})
}
.allocation-chapter {
width: 500rem;
width: 30rem;
padding: var(--space);
padding-top: 0;
color: var(--color-font-1);
.desc {
margin-top: 10rem;
margin-bottom: 35rem;
margin-top: .6rem;
margin-bottom: 2rem;
text-align: center;
}
.row {
display: flex;
align-items: center;
gap: 20rem;
margin-bottom: 15rem;
gap: 1.2rem;
margin-bottom: 1rem;
word-break: keep-all;
.label {
width: 90rem;
width: 5.6rem;
}
.text {
font-size: 12rem;
font-size: .7rem;
}
}
.notice {
display: flex;
justify-content: space-between;
transform: translate3d(0, -15rem, 0);
padding-left: 110rem;
font-size: 13rem;
transform: translate3d(0, -1rem, 0);
padding-left: 7rem;
font-size: .8rem;
}
}

View File

@@ -38,7 +38,7 @@ onMounted(() => {
header {
background: var(--color-second-bg);
height: 60rem;
height: 4rem;
width: 100%;
display: flex;
justify-content: center;
@@ -47,12 +47,12 @@ onMounted(() => {
.nav-list {
display: flex;
gap: 10rem;
gap: .6rem;
nav {
padding: 7rem 20rem;
padding: .4rem 1.2rem;
cursor: pointer;
font-size: 16rem;
font-size: 1rem;
transition: all .3s;
&:hover {

View File

@@ -0,0 +1,185 @@
<script setup lang="ts">
import {DictResource,} from "@/types.ts";
import {$computed, $ref} from "vue/macros";
import {dictionaryResources} from "@/assets/dictionary.ts";
import {groupBy} from "lodash-es";
import {useBaseStore} from "@/stores/base.ts";
import DictList from "@/pages/pc/components/list/DictList.vue";
import DictGroup from "@/pages/pc/components/list/DictGroup.vue";
import bookFlag from "@/assets/img/flags/book.png";
import enFlag from "@/assets/img/flags/en.png";
import jaFlag from "@/assets/img/flags/ja.png";
import deFlag from "@/assets/img/flags/de.png";
import codeFlag from "@/assets/img/flags/code.png";
import myFlag from "@/assets/img/flags/my.png";
import {Icon} from "@iconify/vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {useRouter} from "vue-router";
const emit = defineEmits<{
add: [],
selectDict: [val: { dict: any, index: number }]
}>()
const store = useBaseStore()
let currentLanguage = $ref('en')
let currentTranslateLanguage = $ref('common')
let groupByLanguage = groupBy(dictionaryResources, 'language')
let translateLanguageList = $ref([])
function groupByDictTags(dictList: DictResource[]) {
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
dict.tags.forEach((tag) => {
if (Object.prototype.hasOwnProperty.call(result, tag)) {
result[tag].push(dict)
} else {
result[tag] = [dict]
}
})
return result
}, {})
}
const groupByTranslateLanguage = $computed(() => {
let data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
// console.log('groupByTranslateLanguage', data)
translateLanguageList = Object.keys(data)
currentTranslateLanguage = translateLanguageList[0]
return data
})
const groupedByCategoryAndTag = $computed(() => {
const currentTranslateLanguageDictList = groupByTranslateLanguage[currentTranslateLanguage]
const groupByCategory = groupBy(currentTranslateLanguageDictList, 'category')
let data = []
for (const [key, value] of Object.entries(groupByCategory)) {
data.push([key, groupByDictTags(value)])
}
// console.log('groupedByCategoryAndTag', data)
return data
})
function del(e) {
store.myDictList.splice(e.index, 1)
}
const languageCategoryOptions = [
{id: 'en', name: '英语', flag: enFlag},
{id: 'ja', name: '日语', flag: jaFlag},
{id: 'de', name: '德语', flag: deFlag},
{id: 'code', name: 'Code', flag: codeFlag},
]
const router = useRouter()
</script>
<template>
<div class="dict-list-panel">
<header class="flex justify-center pb-3">
<div class="container2 flex justify-between items-center">
<div class="flex items-center gap-5">
<BaseIcon icon="ion:chevron-back" @click="router.back"/>
<div class="tabs">
<div class="tab"
:class="currentLanguage === item.id && 'active'"
@click="currentLanguage = item.id"
v-for="item in languageCategoryOptions">
<img :src='item.flag' alt=""/>
<span>{{ item.name }}</span>
</div>
</div>
</div>
<BaseIcon icon="lucide:search"/>
</div>
</header>
<div class="page-content">
<div class="dict-list-wrapper">
<div class="translate ">
<span>释义</span>
<el-radio-group v-model="currentTranslateLanguage">
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ $t(i) }}</el-radio-button>
</el-radio-group>
</div>
<DictGroup
v-for="item in groupedByCategoryAndTag"
:select-id="store.currentDict.id"
@selectDict="e => emit('selectDict',e)"
:groupByTag="item[1]"
:category="item[0]"
/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.dict-list-panel {
width: 100%;
height: 100%;
$header-height: 4rem;
//padding: var(--space);
padding-top: 0;
box-sizing: border-box;
header {
position: fixed;
top: 0;
left: var(--aside-width);
width: calc(100vw - var(--aside-width));
z-index: 9;
background: var(--color-main-bg);
.tabs {
display: flex;
gap: 1.5rem;
.tab {
color: var(--color-font-1);
cursor: pointer;
padding: .3rem;
transition: all .5s;
border-bottom: 2px solid transparent;
display: flex;
align-items: center;
gap: 0.6rem;
&.active {
$main: rgb(64, 158, 255);
border-bottom: 2px solid $main;
}
img {
height: 2rem;
}
}
}
}
.page-content {
padding-top: 4rem;
display: flex;
.dict-list-wrapper {
flex: 1;
overflow: auto;
height: 100%;
.translate {
display: flex;
align-items: center;
color: var(--color-font-1);
margin-bottom: 1rem;
& > span {
font-size: 1.2rem;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import DictListPanel2 from "./DictListPanel2.vue";
import {Icon} from '@iconify/vue'
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<div class="word flex justify-center ">
<div class="container2">
<DictListPanel2
/>
</div>
</div>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.center {
@apply flex justify-center items-center;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -0,0 +1,129 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
import {Icon} from '@iconify/vue'
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<div class="word flex justify-center ">
<div class="w-5/10 pt-5">
<div class="flex gap-6">
<div class="card w-1/2 flex flex-col">
<div class="title">
我的词典
</div>
<div class="grid flex-1 flex gap-5 mt-4">
<div class="p-4 flex-1 rounded-md bg-slate-200 relative" v-for="i in 3">
<span>收藏</span>
<div class="absolute bottom-4 right-4">333个词</div>
</div>
</div>
</div>
<div class="w-1/2">
<div class="card ">
<div class="flex justify-between items-center">
<div class="bg-slate-200 p-3 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold">{{ base.currentDict.name }}</span>
<Icon icon="gg:arrows-exchange" class="text-2xl ml-2"/>
<Icon icon="uil:setting" class="text-2xl ml-2"/>
</div>
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
@click="router.push('/practice')">
开始学习
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" percentage="80" :show-text="false"></el-progress>
</div>
<div class="card flex gap-3">
<div class="bg-slate-200 w-10 h-10 flex center text-2xl rounded">
0
</div>
<div class="flex-1">
<div class="flex justify-between">
<div class="title">
每日目标
</div>
<div style="color:#ac6ed1;" class="cursor-pointer">
更改目标
</div>
</div>
<div class="mt-2 text-xs">学习 50 个单词</div>
<el-progress class="flex-1 mt-1" percentage="80" :show-text="false"></el-progress>
</div>
</div>
</div>
</div>
<div class="card">
<div class="flex justify-between">
<div class="title">
其他学习词典
</div>
<BaseIcon icon="ic:round-add" @click="router.push('/dict')"/>
</div>
<div class="grid grid-cols-2 gap-6 mt-5 ">
<div class=" p-4 rounded-md justify-between items-center bg-slate-200 " v-for="i in 3">
<div class="flex justify-between w-full">
<span>{{ base.currentDict.name }}</span>
<div class="text-2xl ml-2 flex gap-4">
<Icon icon="hugeicons:delete-02"/>
<Icon icon="nonicons:go-16"/>
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" percentage="80" color="white" :show-text="false"></el-progress>
</div>
</div>
<div class="flex justify-center mt-2 text-2xl">
<Icon icon="mingcute:down-line"/>
</div>
</div>
<div class="card">
<div class="title">
学习记录
</div>
<div class="center">
<ActivityCalendar
:data="[{ date: '2023-05-22', count: 5 }]"
:width="40"
:height="7"
:cellLength="16"
:cellInterval="8"
:fontSize="12"
:showLevelFlag="false"
:showWeekDayFlag="false"
:clickEvent="clickEvent"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.center {
@apply flex justify-center items-center;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -7,11 +7,13 @@ import {usePracticeStore} from "@/stores/practice.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useRouter} from "vue-router";
const practiceStore = usePracticeStore()
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const router = useRouter()
</script>
<template>
@@ -19,12 +21,17 @@ const runtimeStore = useRuntimeStore()
<div class="aside">
<div class="top">
<Logo/>
<div class="row">
<div class="row" @click="router.push('/home')">
<Icon icon="material-symbols-light:dictionary-outline-sharp"/>
<!-- <Icon icon="streamline:dictionary-language-book"/>-->
<span>主页</span>
</div>
<div class="row" @click="router.push('/word')">
<Icon icon="material-symbols-light:dictionary-outline-sharp"/>
<!-- <Icon icon="streamline:dictionary-language-book"/>-->
<span>单词</span>
</div>
<div class="row">
<div class="row" @click="router.push('/article')">
<Icon icon="ph:article-ny-times"/>
<span>文章</span>
</div>
@@ -39,14 +46,14 @@ const runtimeStore = useRuntimeStore()
</div>
<div class="bottom">
<div class="row"
:title="`设置(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
:title="`设置(${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
@click="runtimeStore.showSettingModal = true">
<Icon icon="uil:setting"/>
<span>试卷</span>
</div>
</div>
</div>
<div class="content">
<div class="content overflow-auto">
<router-view></router-view>
</div>
</div>
@@ -60,32 +67,33 @@ const runtimeStore = useRuntimeStore()
width: 100vw;
height: 100vh;
display: flex;
font-size: 14rem;
}
.aside {
background: white;
//position: fixed;
position: fixed;
z-index: 999;
top: 0;
left: 0;
height: 100vh;
width: 200rem;
padding: 20rem 10rem;
width: var(--aside-width);
padding: 1rem 1rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
.row {
padding: 10rem;
@apply cursor-pointer;
padding: 0.5rem;
display: flex;
align-items: center;
gap: 10rem;
font-size: 16rem;
gap: 0.5rem;
font-size: 1rem;
//font-weight: bold;
svg {
font-size: 36rem;
font-size: 2rem;
}
}
}

View File

@@ -79,17 +79,17 @@ onUnmounted(() => {
.footer {
width: var(--toolbar-width);
margin-bottom: 10rem;
margin-bottom: .8rem;
transition: all var(--anim-time);
position: relative;
margin-top: 15rem;
margin-top: 1rem;
&.hide {
margin-bottom: -90rem;
margin-top: 50rem;
margin-bottom: -7rem;
margin-top: 3rem;
.progress {
bottom: calc(100% + 25rem);
bottom: calc(100% + 1.8rem);
}
}
@@ -97,15 +97,15 @@ onUnmounted(() => {
position: relative;
width: 100%;
box-sizing: border-box;
border-radius: 10rem;
border-radius: .6rem;
background: var(--color-second-bg);
padding: 3rem var(--space) 6rem var(--space);
padding: .2rem var(--space) .4rem var(--space);
z-index: 2;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
.stat {
margin-top: 8rem;
margin-top: .5rem;
display: flex;
justify-content: space-around;
@@ -113,8 +113,8 @@ onUnmounted(() => {
display: flex;
flex-direction: column;
align-items: center;
gap: 5rem;
width: 80rem;
gap: .3rem;
width: 5rem;
color: gray;
.line {
@@ -129,7 +129,7 @@ onUnmounted(() => {
.progress {
width: 100%;
transition: all .3s;
padding: 0 10rem;
padding: 0 .6rem;
box-sizing: border-box;
position: absolute;
bottom: 0;

View File

@@ -31,30 +31,30 @@ const settingStore = useSettingStore()
v-if="!isSimple"
class="collect"
@click="$emit('toggleSimple')"
:title="`标记为简单词(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
:title="`标记为简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class="fill"
@click="$emit('toggleSimple')"
:title="`取消标记简单词(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
:title="`取消标记简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
icon="material-symbols:check-circle-rounded"/>
<BaseIcon
v-if="!isCollect"
class="collect"
@click="$emit('toggleCollect')"
:title="`收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="$emit('toggleCollect')"
:title="`取消收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill"/>
<Tooltip
:title="`跳过(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<IconWrapper>
<Icon icon="icon-park-outline:go-ahead" class="menu"
@@ -66,9 +66,9 @@ const settingStore = useSettingStore()
<style scoped lang="scss">
.options {
margin-top: 10rem;
margin-top: 1.2rem;
display: flex;
gap: 15rem;
font-size: 18rem;
gap: 1rem;
font-size: 1.1rem;
}
</style>

View File

@@ -89,13 +89,13 @@ const showCollectToggleButton = $computed(() => {
<div class="panel anim" v-show="settingStore.showPanel">
<header>
<div class="tabs">
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">当前</div>
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">当前学习</div>
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">{{ store.collect.name }}</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{ store.simple.name }}</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
</div>
<Tooltip
:title="`关闭(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
:title="`关闭(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
>
<Close @click="settingStore.showPanel = false"/>
</Tooltip>
@@ -226,14 +226,14 @@ const showCollectToggleButton = $computed(() => {
<style scoped lang="scss">
@import "@/assets/css/variable";
$header-height: 50rem;
$header-height: 3rem;
.slide-item {
width: var(--panel-width);
height: 100%;
}
.panel {
border-radius: 8rem;
border-radius: .5rem;
width: var(--panel-width);
background: var(--color-second-bg);
height: 100%;
@@ -246,15 +246,15 @@ $header-height: 50rem;
& > header {
min-height: 50rem;
min-height: 3rem;
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10rem 15rem;
padding: .6rem .9rem;
border-bottom: 1px solid #e1e1e1;
gap: 15rem;
gap: 1rem;
.close {
cursor: pointer;
@@ -263,13 +263,13 @@ $header-height: 50rem;
.tabs {
display: flex;
align-items: center;
gap: 15rem;
font-size: 14rem;
gap: .9rem;
font-size: .8rem;
.tab {
cursor: pointer;
word-break: keep-all;
font-size: 16rem;
font-size: 1rem;
transition: all .3s;
color: gray;

View File

@@ -143,9 +143,7 @@ useStartKeyboardEventListener()
<template>
<div class="practice-wrapper">
<Toolbar/>
<!-- <BaseButton @click="test">test</BaseButton>-->
<PracticeArticle ref="practiceRef" v-if="store.isArticle"/>
<PracticeWord ref="practiceRef" v-else/>
<PracticeWord ref="practiceRef"/>
<Footer/>
</div>
<DictModal/>
@@ -154,7 +152,7 @@ useStartKeyboardEventListener()
<style scoped lang="scss">
.practice-wrapper {
font-size: 13rem;
font-size: 0.9rem;
width: 100%;
height: 100%;
display: flex;

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import {computed, nextTick, onMounted, onUnmounted, watch} from "vue"
import {$ref} from "vue/macros";
import {Article, ArticleWord, DefaultArticle, ShortcutKey, Word} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {usePracticeStore} from "@/stores/practice.ts";
@@ -342,7 +343,7 @@ defineExpose({showSentence, play, del,hideSentence,nextSentence})
<div class="options-wrapper">
<div class="flex gap10">
<BaseIcon
:title="`编辑(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
:title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
icon="tabler:edit"
@click="emit('edit',props.article)"
/>
@@ -350,16 +351,16 @@ defineExpose({showSentence, play, del,hideSentence,nextSentence})
v-if="!isArticleCollect(props.article)"
class="collect"
@click="toggleArticleCollect(props.article)"
:title="`收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(props.article)"
:title="`取消收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill"/>
<BaseIcon
:title="`跳过(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
icon="icon-park-outline:go-ahead"
@click="emit('over')"/>
</div>

View File

@@ -358,7 +358,7 @@ defineExpose({getCurrentPractice})
{{ store.currentDict.name }}
</div>
<Tooltip
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
:title="`下一章(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < articleData.articles .length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>

View File

@@ -154,7 +154,7 @@ defineExpose({del, showWord, hideWord, play})
<div class="typing-word">
<div class="translate"
:style="{
fontSize: settingStore.fontSize.wordTranslateFontSize +'rem',
fontSize: settingStore.fontSize.wordTranslateFontSize +'px',
opacity: settingStore.translate ? 1 : 0
}"
>
@@ -163,7 +163,7 @@ defineExpose({del, showWord, hideWord, play})
<!-- <div class="volumeIcon">-->
<!-- <Tooltip-->
<!-- v-if="i === word.trans.length - 1"-->
<!-- :title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayTranslatePronunciation]})`"-->
<!-- :title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayTranslatePronunciation]})`"-->
<!-- >-->
<!-- <VolumeIcon-->
<!-- ref="volumeTranslateIconRef"-->
@@ -176,7 +176,7 @@ defineExpose({del, showWord, hideWord, play})
<div class="word-wrapper">
<div class="word"
:class="wrong && 'is-wrong'"
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'rem'}"
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'px'}"
>
<span class="input" v-if="input">{{ input }}</span>
<span class="wrong" v-if="wrong">{{ wrong }}</span>
@@ -190,7 +190,7 @@ defineExpose({del, showWord, hideWord, play})
<span class="letter" v-else>{{ displayWord }}</span>
</div>
<Tooltip
:title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
>
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</Tooltip>
@@ -215,19 +215,19 @@ defineExpose({del, showWord, hideWord, play})
color: var(--color-font-2);
.phonetic, .translate {
font-size: 20rem;
font-size: 1.6rem;
transition: all .3s;
}
.phonetic {
margin-top: 5rem;
margin-top: .3rem;
font-family: var(--word-font-family);
}
.translate {
position: absolute;
transform: translateY(-50%);
margin-bottom: 90rem;
margin-bottom: 7rem;
&:hover {
.volumeIcon {
@@ -238,7 +238,7 @@ defineExpose({del, showWord, hideWord, play})
.translate-item {
display: flex;
align-items: center;
gap: 10rem;
gap: .8rem;
}
.volumeIcon {
@@ -248,17 +248,17 @@ defineExpose({del, showWord, hideWord, play})
}
.word-wrapper {
margin-left: 30rem;
margin-left: 2rem;
display: flex;
align-items: center;
gap: 10rem;
gap: .8rem;
color: var(--color-font-1);
.word {
font-size: 48rem;
font-size: 3rem;
line-height: 1;
font-family: var(--word-font-family);
letter-spacing: 5rem;
letter-spacing: .3rem;
.input {
color: rgb(22, 163, 74);

View File

@@ -246,7 +246,7 @@ onUnmounted(() => {
v-if="prevWord">
<Icon class="arrow" icon="bi:arrow-left" width="22"/>
<Tooltip
:title="`上一个(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
>
<div class="word">{{ prevWord.word }}</div>
</Tooltip>
@@ -255,7 +255,7 @@ onUnmounted(() => {
@click="next(false)"
v-if="nextWord">
<Tooltip
:title="`下一个(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<div class="word" :class="settingStore.dictation && 'text-shadow'">{{ nextWord.word }}</div>
</Tooltip>
@@ -287,40 +287,26 @@ onUnmounted(() => {
v-loading="!store.load"
>
<div class="list-header">
<div class="left">
<div class="title">
{{ store.chapterName }}
</div>
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div style="position:relative;"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="showSortOption = !showSortOption"
/>
<MiniDialog
v-model="showSortOption"
style="width: 130rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<BaseIcon icon="bi:arrow-right"
@click="emitter.emit(EventKey.next)"
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < store.currentDict.chapterWords.length - 1"/>
</div>
<div class="right">
{{ data.words.length }}个单词
<div>{{ data.words.length }}个单词</div>
<div style="position:relative;"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="showSortOption = !showSortOption"
/>
<MiniDialog
v-model="showSortOption"
style="width: 9rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
</div>
<WordList
@@ -378,9 +364,9 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 14rem;
font-size: 1rem;
color: gray;
gap: 6rem;
gap: .4rem;
position: relative;
width: var(--toolbar-width);
@@ -395,14 +381,13 @@ onUnmounted(() => {
align-items: center;
.arrow {
min-width: 22rem;
min-height: 22rem;
font-size: .5rem;
}
}
.word {
font-size: 24rem;
margin-bottom: 4rem;
font-size: 1.2rem;
margin-bottom: .2rem;
font-family: var(--word-font-family);
}
@@ -410,31 +395,31 @@ onUnmounted(() => {
cursor: pointer;
display: flex;
float: left;
gap: 10rem;
gap: .8rem;
}
.next {
cursor: pointer;
display: flex;
justify-content: flex-end;
gap: 10rem;
gap: .8rem;
float: right;
}
}
.options-wrapper {
position: absolute;
margin-top: 120rem;
margin-top: 8rem;
}
}
.word-panel-wrapper {
position: fixed;
left: 0;
top: 10rem;
top: .8rem;
z-index: 1;
margin-left: var(--panel-margin-left);
height: calc(100% - 20rem);
height: calc(100% - 1.5rem);
}
</style>

View File

@@ -1,13 +1,129 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
import {Icon} from '@iconify/vue'
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<div class="word flex s font-bold underline">
asdf
</div>
<div class="word flex justify-center ">
<div class="w-5/10 pt-5">
<div class="flex gap-6">
<div class="card w-1/2 flex flex-col">
<div class="title">
我的词典
</div>
<div class="grid flex-1 flex gap-5 mt-4">
<div class="p-4 flex-1 rounded-md bg-slate-200 relative" v-for="i in 3">
<span>收藏</span>
<div class="absolute bottom-4 right-4">333个词</div>
</div>
</div>
</div>
<div class="w-1/2">
<div class="card ">
<div class="flex justify-between items-center">
<div class="bg-slate-200 p-3 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold">{{ base.currentDict.name }}</span>
<Icon icon="gg:arrows-exchange" class="text-2xl ml-2"/>
<Icon icon="uil:setting" class="text-2xl ml-2"/>
</div>
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
@click="router.push('/practice')">
开始学习
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" percentage="80" :show-text="false"></el-progress>
</div>
<div class="card flex gap-3">
<div class="bg-slate-200 w-10 h-10 flex center text-2xl rounded">
0
</div>
<div class="flex-1">
<div class="flex justify-between">
<div class="title">
每日目标
</div>
<div style="color:#ac6ed1;" class="cursor-pointer">
更改目标
</div>
</div>
<div class="mt-2 text-xs">学习 50 个单词</div>
<el-progress class="flex-1 mt-1" percentage="80" :show-text="false"></el-progress>
</div>
</div>
</div>
</div>
<div class="card">
<div class="flex justify-between">
<div class="title">
其他学习词典
</div>
<BaseIcon icon="ic:round-add" @click="router.push('/dict')"/>
</div>
<div class="grid grid-cols-2 gap-6 mt-5 ">
<div class=" p-4 rounded-md justify-between items-center bg-slate-200 " v-for="i in 3">
<div class="flex justify-between w-full">
<span>{{ base.currentDict.name }}</span>
<div class="text-2xl ml-2 flex gap-4">
<Icon icon="hugeicons:delete-02"/>
<Icon icon="nonicons:go-16"/>
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" percentage="80" color="white" :show-text="false"></el-progress>
</div>
</div>
<div class="flex justify-center mt-2 text-2xl">
<Icon icon="mingcute:down-line"/>
</div>
</div>
<div class="card">
<div class="title">
学习记录
</div>
<div class="center">
<ActivityCalendar
:data="[{ date: '2023-05-22', count: 5 }]"
:width="40"
:height="7"
:cellLength="16"
:cellInterval="8"
:fontSize="12"
:showLevelFlag="false"
:showWeekDayFlag="false"
:clickEvent="clickEvent"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.center {
@apply flex justify-center items-center;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -19,15 +19,22 @@ import MusicSetting from "@/pages/mobile/my/setting/MusicSetting.vue";
import OtherSetting from "@/pages/mobile/my/setting/OtherSetting.vue";
import WordHome from "@/pages/pc/word/WordHome.vue";
import PC from "@/pages/pc/index.vue";
import Dict2 from '@/pages/pc/dict2/index.vue'
import ArticleIndex from "@/pages/pc/article/ArticleIndex.vue";
import HomeIndex from "@/pages/pc/home/HomeIndex.vue";
export const routes: RouteRecordRaw[] = [
{
path: '/', component: PC,
redirect: '/word',
children: [
{path: '/word', component: WordHome},
{path: 'home', component: HomeIndex},
{path: 'word', component: WordHome},
{path: 'dict', component: Dict2},
{path: 'practice', component: Practice},
{path: 'article', component: ArticleIndex},
]
},
{path: '/pc/practice', component: Practice},
{path: '/pc/dict', component: Dict},
{path: '/mobile', component: Mobile,},
@@ -44,7 +51,7 @@ export const routes: RouteRecordRaw[] = [
{path: '/mobile/about', component: About},
{path: '/mobile/feedback', component: Feedback},
{path: '/test', component: Test},
{path: '/', redirect: '/pc/practice'},
// {path: '/', redirect: '/pc/practice'},
]
const router = VueRouter.createRouter({

View File

@@ -14,6 +14,7 @@ export const EventKey = {
keyup: 'keyup',
onTyping: 'onTyping',
repeat: 'repeat',
//TODO 废弃
next: 'next',
write: 'write',
editDict: 'editDict',

View File

@@ -4,6 +4,7 @@ import {DefaultSettingState, SettingState} from "@/stores/setting.ts";
import {cloneDeep} from "lodash-es";
import {Dict, DictType} from "@/types.ts";
import {ArchiveReader, libarchiveWasm} from "libarchive-wasm";
import {useRouter} from "vue-router";
export function getRandom(a: number, b: number): number {
return Math.random() * (b - a) + a;
@@ -186,3 +187,13 @@ export function getDictFile(url: string) {
}
})
}
export function useNav() {
const router = useRouter()
function nav(val) {
router.push(val)
}
return {nav, back: router.back}
}

13
uno.config.ts Normal file
View File

@@ -0,0 +1,13 @@
// uno.config.ts
import {defineConfig, presetUno} from 'unocss'
export default defineConfig({
content: {
filesystem: [
'**/*.{html,js,ts,jsx,tsx,vue,svelte,astro}',
],
},
presets: [
presetUno(),
],
})

View File

@@ -1,13 +1,14 @@
import {defineConfig} from 'vite'
import Vue from '@vitejs/plugin-vue'
import VueJsx from '@vitejs/plugin-vue-jsx'
import VueJsx from "@vitejs/plugin-vue-jsx";
import {resolve} from 'path'
import {visualizer} from "rollup-plugin-visualizer";
import {ElementPlusResolver} from "unplugin-vue-components/resolvers";
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {getLastCommit} from "git-last-commit";
import VueMacros from 'unplugin-vue-macros/vite'
import DefineOptions from 'unplugin-vue-define-options/vite' // 引入插件
import UnoCSS from 'unocss/vite'
function pathResolve(dir: string) {
return resolve(__dirname, ".", dir)
@@ -23,16 +24,11 @@ export default defineConfig(async () => {
})
return {
plugins: [
VueMacros({
plugins: {
vue: Vue(),
vueJsx: VueJsx() // if needed
}
// betterDefine: true,
// reactivityTransform: {
// exclude: [/node_modules/, /jQuery\.js/]
// }
Vue({
reactivityTransform: true
}),
VueJsx(),
UnoCSS(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
@@ -40,7 +36,7 @@ export default defineConfig(async () => {
resolvers: [ElementPlusResolver()],
}),
//用于给setup组件定义名字的keep-alive需要name才能正常工作
// DefineOptions(),
DefineOptions(),
lifecycle === 'report' ?
visualizer({
gzipSize: true,