Develop mobile pages

This commit is contained in:
zyronon
2024-01-01 17:23:47 +08:00
parent c7c41a5488
commit c9eed4da54
19 changed files with 1001 additions and 250 deletions

View File

@@ -175,6 +175,7 @@ html, body {
& > .page-content {
padding: 10rem;
box-sizing: border-box;
overflow: auto;
}
}

View File

@@ -1,131 +1,132 @@
import {onMounted, onUnmounted, watch} from "vue";
import {onMounted, onUnmounted, watch,onDeactivated} from "vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
export function useWindowClick(cb: (e: PointerEvent) => void) {
onMounted(() => {
emitter.on(EventKey.closeOther, cb)
window.addEventListener('click', cb)
})
onUnmounted(() => {
window.removeEventListener('click', cb)
})
onMounted(() => {
emitter.on(EventKey.closeOther, cb)
window.addEventListener('click', cb)
})
onUnmounted(() => {
window.removeEventListener('click', cb)
})
}
export function useEventListener(type: string, listener: EventListenerOrEventListenerObject) {
onMounted(() => window.addEventListener(type, listener))
onUnmounted(() => window.removeEventListener(type, listener))
onMounted(() => window.addEventListener(type, listener))
onUnmounted(() => window.removeEventListener(type, listener))
onDeactivated(() => window.removeEventListener(type, listener))
}
export function getShortcutKey(e: KeyboardEvent) {
let shortcutKey = ''
if (e.ctrlKey) shortcutKey += 'Ctrl+'
if (e.altKey) shortcutKey += 'Alt+'
if (e.shiftKey) shortcutKey += 'Shift+'
if (e.key !== 'Control' && e.key !== 'Alt' && e.key !== 'Shift') {
if (e.keyCode >= 65 && e.keyCode <= 90) {
shortcutKey += e.key.toUpperCase()
} else {
if (e.key === 'ArrowRight') {
shortcutKey += '➡'
} else if (e.key === 'ArrowLeft') {
shortcutKey += '⬅'
} else if (e.key === 'ArrowUp') {
shortcutKey += '⬆'
} else if (e.key === 'ArrowDown') {
shortcutKey += '⬇'
} else {
shortcutKey += e.key
}
}
let shortcutKey = ''
if (e.ctrlKey) shortcutKey += 'Ctrl+'
if (e.altKey) shortcutKey += 'Alt+'
if (e.shiftKey) shortcutKey += 'Shift+'
if (e.key !== 'Control' && e.key !== 'Alt' && e.key !== 'Shift') {
if (e.keyCode >= 65 && e.keyCode <= 90) {
shortcutKey += e.key.toUpperCase()
} else {
if (e.key === 'ArrowRight') {
shortcutKey += '➡'
} else if (e.key === 'ArrowLeft') {
shortcutKey += '⬅'
} else if (e.key === 'ArrowUp') {
shortcutKey += '⬆'
} else if (e.key === 'ArrowDown') {
shortcutKey += '⬇'
} else {
shortcutKey += e.key
}
}
shortcutKey = shortcutKey.trim()
}
shortcutKey = shortcutKey.trim()
// console.log('key', shortcutKey)
return shortcutKey
// console.log('key', shortcutKey)
return shortcutKey
}
export function useStartKeyboardEventListener() {
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
useEventListener('keydown', (e: KeyboardEvent) => {
// console.log('e',e.keyCode,e.code)
if (!runtimeStore.disableEventListener) {
e.preventDefault()
let shortcutKey = getShortcutKey(e)
// console.log('shortcutKey', shortcutKey)
let list = Object.entries(settingStore.shortcutKeyMap)
let shortcutEvent = ''
for (let i = 0; i < list.length; i++) {
let [k, v] = list[i]
if (v === shortcutKey) {
console.log('快捷键', k)
shortcutEvent = k
break
}
}
if (shortcutEvent) {
emitter.emit(shortcutEvent, e)
} else {
//非英文模式下,输入区域的 keyCode 均为 229时
if ((e.keyCode >= 65 && e.keyCode <= 90)
|| (e.keyCode >= 48 && e.keyCode <= 57)
|| e.code === 'Space'
|| e.code === 'Slash'
|| e.code === 'Quote'
|| e.code === 'Comma'
|| e.code === 'BracketLeft'
|| e.code === 'BracketRight'
|| e.code === 'Period'
|| e.code === 'Minus'
|| e.code === 'Equal'
|| e.code === 'Semicolon'
// || e.code === 'Backquote'
|| e.keyCode === 229
) {
emitter.emit(EventKey.onTyping, e)
} else {
emitter.emit(EventKey.keydown, e)
}
}
useEventListener('keydown', (e: KeyboardEvent) => {
// console.log('e',e.keyCode,e.code)
if (!runtimeStore.disableEventListener) {
e.preventDefault()
let shortcutKey = getShortcutKey(e)
// console.log('shortcutKey', shortcutKey)
let list = Object.entries(settingStore.shortcutKeyMap)
let shortcutEvent = ''
for (let i = 0; i < list.length; i++) {
let [k, v] = list[i]
if (v === shortcutKey) {
console.log('快捷键', k)
shortcutEvent = k
break
}
})
useEventListener('keyup', (e: KeyboardEvent) => {
if (!runtimeStore.disableEventListener) {
emitter.emit(EventKey.keyup, e)
}
if (shortcutEvent) {
emitter.emit(shortcutEvent, e)
} else {
//非英文模式下,输入区域的 keyCode 均为 229时
if ((e.keyCode >= 65 && e.keyCode <= 90)
|| (e.keyCode >= 48 && e.keyCode <= 57)
|| e.code === 'Space'
|| e.code === 'Slash'
|| e.code === 'Quote'
|| e.code === 'Comma'
|| e.code === 'BracketLeft'
|| e.code === 'BracketRight'
|| e.code === 'Period'
|| e.code === 'Minus'
|| e.code === 'Equal'
|| e.code === 'Semicolon'
// || e.code === 'Backquote'
|| e.keyCode === 229
) {
emitter.emit(EventKey.onTyping, e)
} else {
emitter.emit(EventKey.keydown, e)
}
})
}
}
})
useEventListener('keyup', (e: KeyboardEvent) => {
if (!runtimeStore.disableEventListener) {
emitter.emit(EventKey.keyup, e)
}
})
}
export function useOnKeyboardEventListener(onKeyDown: (e: KeyboardEvent) => void, onKeyUp: (e: KeyboardEvent) => void) {
onMounted(() => {
emitter.on(EventKey.keydown, onKeyDown)
emitter.on(EventKey.keyup, onKeyUp)
})
onUnmounted(() => {
emitter.off(EventKey.keydown, onKeyDown)
emitter.off(EventKey.keyup, onKeyUp)
})
onMounted(() => {
emitter.on(EventKey.keydown, onKeyDown)
emitter.on(EventKey.keyup, onKeyUp)
})
onUnmounted(() => {
emitter.off(EventKey.keydown, onKeyDown)
emitter.off(EventKey.keyup, onKeyUp)
})
}
export function useDisableEventListener(watchVal: any) {
const runtimeStore = useRuntimeStore()
watch(watchVal, (n: any) => {
if (n === true) runtimeStore.disableEventListener = true
if (n === false) runtimeStore.disableEventListener = false
})
onMounted(() => {
if (watchVal() === undefined) {
runtimeStore.disableEventListener = true
}
})
onUnmounted(() => {
if (watchVal() === undefined) {
runtimeStore.disableEventListener = false
}
})
const runtimeStore = useRuntimeStore()
watch(watchVal, (n: any) => {
if (n === true) runtimeStore.disableEventListener = true
if (n === false) runtimeStore.disableEventListener = false
})
onMounted(() => {
if (watchVal() === undefined) {
runtimeStore.disableEventListener = true
}
})
onUnmounted(() => {
if (watchVal() === undefined) {
runtimeStore.disableEventListener = false
}
})
}

View File

@@ -0,0 +1,85 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
import WordList from "@/components/list/WordList.vue";
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import {$computed, $ref} from "vue/macros";
import {Dict, DictType} from "@/types.ts";
import {useRouter} from "vue-router";
import {useBaseStore} from "@/stores/base.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {cloneDeep} from "lodash-es";
import {useWordOptions} from "@/hooks/dict.ts";
const router = useRouter()
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
let practiceType = $ref(DictType.word)
const showCollectToggleButton = $computed(() => {
if (store.currentDict.type === DictType.collect) {
if (store.current.practiceType !== practiceType) {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
} else {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
return false
})
function changeIndex(dict: Dict) {
store.changeDict(dict, practiceType)
}
function addCollect() {
runtimeStore.editDict = cloneDeep(store.collect)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
const {
delWrongWord,
delSimpleWord,
toggleWordCollect,
} = useWordOptions()
</script>
<template>
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.collect.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addCollect"/>
</div>
<template v-if="showCollectToggleButton">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.collect.words.length"
class="word-list"
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -23,10 +23,11 @@ import ArticleList from "@/components/list/ArticleList.vue";
import Slide from "@/components/Slide.vue";
import SlideHorizontal from "@/components/slide/SlideHorizontal.vue";
import SlideItem from "@/components/slide/SlideItem.vue";
import CollectList from "@/pages/mobile/components/CollectList.vue";
import WrongList from "@/pages/mobile/components/WrongList.vue";
import SimpleList from "@/pages/mobile/components/SimpleList.vue";
const router = useRouter()
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
let tabIndex = $ref(0)
provide('tabIndex', computed(() => tabIndex))
@@ -37,12 +38,6 @@ watch(() => settingStore.showPanel, n => {
}
})
let practiceType = $ref(DictType.word)
function changeIndex(dict: Dict) {
store.changeDict(dict, practiceType)
}
onMounted(() => {
emitter.on(EventKey.changeDict, () => {
tabIndex = 0
@@ -53,38 +48,6 @@ onUnmounted(() => {
emitter.off(EventKey.changeDict)
})
const {
delWrongWord,
delSimpleWord,
toggleWordCollect,
} = useWordOptions()
const {
toggleArticleCollect
} = useArticleOptions()
function addCollect() {
runtimeStore.editDict = cloneDeep(store.collect)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
function addSimple() {
runtimeStore.editDict = cloneDeep(store.simple)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
const showCollectToggleButton = $computed(() => {
if (store.currentDict.type === DictType.collect) {
if (store.current.practiceType !== practiceType) {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
} else {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
return false
})
</script>
<template>
@@ -93,8 +56,8 @@ const showCollectToggleButton = $computed(() => {
<div class="tabs">
<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 class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{ store.wrong.name }}</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.simple.name }}</div>
</div>
</header>
<SlideHorizontal v-model:index="tabIndex">
@@ -102,93 +65,13 @@ const showCollectToggleButton = $computed(() => {
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</SlideItem>
<SlideItem>
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.collect.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addCollect"/>
</div>
<template v-if="showCollectToggleButton">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.collect.words.length"
class="word-list"
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
<CollectList/>
</SlideItem>
<SlideItem>
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.simple.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addSimple"/>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.simple.words.length"
class="word-list"
:list="store.simple.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delSimpleWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
<WrongList/>
</SlideItem>
<SlideItem>
<div class="panel-page-item" v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数{{ store.wrong.words.length }}</div>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
<Empty v-else/>
<SimpleList/>
</SlideItem>
</SlideHorizontal>
</div>

View File

@@ -0,0 +1,71 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
import WordList from "@/components/list/WordList.vue";
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import {$ref} from "vue/macros";
import {Dict, DictType} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useWordOptions} from "@/hooks/dict.ts";
import Empty from "@/components/Empty.vue";
import {cloneDeep} from "lodash-es";
import {useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
const router = useRouter()
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
let practiceType = $ref(DictType.word)
function changeIndex(dict: Dict) {
store.changeDict(dict, practiceType)
}
const {
delSimpleWord,
} = useWordOptions()
function addSimple() {
runtimeStore.editDict = cloneDeep(store.simple)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
</script>
<template>
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.simple.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addSimple"/>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.simple.words.length"
class="word-list"
:list="store.simple.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delSimpleWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,59 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
import WordList from "@/components/list/WordList.vue";
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import {$ref} from "vue/macros";
import {Dict, DictType} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useWordOptions} from "@/hooks/dict.ts";
import Empty from "@/components/Empty.vue";
const store = useBaseStore()
let practiceType = $ref(DictType.word)
function changeIndex(dict: Dict) {
store.changeDict(dict, practiceType)
}
const {
delWrongWord,
} = useWordOptions()
</script>
<template>
<div class="panel-page-item">
<template v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数{{ store.wrong.words.length }}</div>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</template>
<Empty v-else/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -2,14 +2,14 @@
import {Icon} from "@iconify/vue";
import Home from "@/pages/mobile/Home.vue";
import DictListManage from "@/pages/mobile/DictListManage.vue";
import My from "@/pages/mobile/My.vue";
import My from "@/pages/mobile/my/My.vue";
import {onMounted} from "vue";
defineOptions({
name: 'Practice'
})
let index = $ref(0)
let index = $ref(2)
onMounted(() => {
console.log('onMounted')

View File

@@ -0,0 +1,81 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import {APP_NAME} from "../../../utils/const.ts";
</script>
<template>
<div class="mobile-page">
<NavBar title="关于我们"/>
<div class="page-content">
<div class="name">{{ APP_NAME }}</div>
<div class="desc">可在网页上使用的背单词软件</div>
<div class="git">
Github地址 <a target="_blank" href="https://github.com/zyronon/typing-word">https://github.com/zyronon/typing-word</a>
</div>
<div class="features">功能列表</div>
<ul>
<li>
<div class="title">背单词</div>
<div class="txt">可以选择记忆或默写单词提供了音标显示发音功能均可选美音英音错误统计</div>
</li>
<li>
<div class="title">背文章</div>
<div class="txt">
内置经典教材书籍可以练习和背诵文章逐句输入自动发音也可以自行添加导入文章提供一键翻译译文对照功能
</div>
</li>
<li>
<div class="title">生词本错词本简单词</div>
<div class="txt">
默写单词时输入错误会自动添加到错词本以便后续复习也可以添加到简单词之后再遇到这个词便会自动跳过同时也可以将其添加到生词本中以便巩固复习
</div>
</li>
<li>
<div class="title">默写模式</div>
<div class="txt">
在用户完成一个章节的练习后如果有错误词那么会重复练习错误词直到没有错误词为止完成之后弹出选项可选择默写本章重复本章下一章
</div>
</li>
<li>
<div class="title">词库</div>
<div class="txt">内置了常用的 CET-4 CET-6 GMAT GRE IELTS SAT TOEFL
考研英语专业四级英语专业八级英语也有程序员常见英语单词以及多种编程语言
API 等词库 尽可能满足大部分用户对背单词的需求也非常欢迎社区贡献更多的词库
</div>
</li>
</ul>
</div>
</div>
</template>
<style scoped lang="scss">
.mobile-page {
.name {
padding: 30rem 0;
font-size: 46rem;
text-align: center;
border-bottom: 1px solid #c9c9c9;
margin-bottom: 20rem;
}
.desc {
font-size: 22rem;
}
.features {
margin-top: 20rem;
font-weight: bold;
font-size: 22rem;
}
li {
margin: 10rem 0;
.title {
font-weight: bold;
}
}
}
</style>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import CollectList from "@/pages/mobile/components/CollectList.vue";
</script>
<template>
<div class="mobile-page">
<NavBar title="收藏"/>
<CollectList/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import {GITHUB} from "@/config/ENV.ts";
import BaseButton from "@/components/BaseButton.vue";
</script>
<template>
<div class="mobile-page">
<NavBar title="反馈问题"/>
<div class="page-content">
<div>
给我发Email<a href="mailto:zyronon@163.com">zyronon@163.com</a>
</div>
<p>or</p>
<div class="github">
<span><a :href="GITHUB" target="_blank">Github</a>上给我提一个
<a :href="`${GITHUB}/issues`" target="_blank">Issue</a>
</span>
<div class="options">
<BaseButton>
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=%E5%8D%95%E8%AF%8D%E9%94%99%E8%AF%AF---word-error.md&title=%E5%8D%95%E8%AF%8D%E9%94%99%E8%AF%AF+%7C+Word+error`"
target="_blank">词典错误</a>
</BaseButton>
<BaseButton>
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=问题报告---bug-report-.md&title=问题报告+%7C+Bug+report+`"
target="_blank">反馈BUG</a>
</BaseButton>
<BaseButton>
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=功能请求---feature-request.md&title=功能请求+%7C+Feature+request`"
target="_blank">功能请求</a>
</BaseButton>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.page-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
p {
font-size: 30rem;
}
.github {
display: flex;
align-items: center;
gap: var(--space);
.options {
display: flex;
flex-direction: column;
gap: 10rem;
}
}
</style>

View File

@@ -47,15 +47,15 @@ function $no() {
<header ref="header"></header>
<div class="detail">
<div class="heat">
<div class="text" @click="isShowStarCount = true">
<div class="text" @click="router.push('/mobile/collect')">
<span>收藏</span>
<span class="num">123</span>
</div>
<div class="text" @click="$nav('/people/follow-and-fans',{type:0})">
<div class="text" @click="router.push('/mobile/wrong')">
<span>错误</span>
<span class="num">123</span>
</div>
<div class="text" @click="$nav('/people/follow-and-fans',{type:1})">
<div class="text" @click="router.push('/mobile/simple')">
<span>已掌握</span>
<span class="num">123</span>
</div>
@@ -64,16 +64,16 @@ function $no() {
<span>您已坚持了164天加油</span>
</div>
<div class="grid">
<div class="item" @click="router.push('/mobile/data-manage')">
<img src="@/assets/img/collect.png" alt="">
<div class="item" @click="router.push('/mobile/collect')">
<img src="../../../assets/img/collect.png" alt="">
<span>收藏</span>
</div>
<div class="item" @click="router.push('/mobile/data-manage')">
<img src="@/assets/img/book2.png" alt="">
<div class="item" @click="router.push('/mobile/wrong')">
<img src="../../../assets/img/book2.png" alt="">
<span>错词本</span>
</div>
<div class="item" @click="router.push('/mobile/data-manage')">
<img src="@/assets/img/complete.png" alt="">
<div class="item" @click="router.push('/mobile/simple')">
<img src="../../../assets/img/complete.png" alt="">
<span>简单词</span>
</div>
</div>
@@ -92,14 +92,14 @@ function $no() {
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
</div>
<div class="item">
<div class="item" @click="router.push('/mobile/feedback')">
<Icon icon="pepicons-pencil:letter-open" width="22"/>
<div class="right">
<span>反馈问题</span>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
</div>
<div class="item">
<div class="item" @click="router.push('/mobile/about')">
<Icon icon="mdi:about-circle-outline" width="22"/>
<div class="right" style="border-bottom: none">
<span>关于我们</span>
@@ -113,7 +113,7 @@ function $no() {
</template>
<style scoped lang="scss">
@import "common.scss";
@import "../common";
.my {
@@ -147,7 +147,7 @@ function $no() {
header {
color: white;
height: 200rem;
background-image: url('../../assets/img/a.png');
background-image: url('../../../assets/img/a.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import WrongList from "@/pages/mobile/components/WrongList.vue";
import SimpleList from "@/pages/mobile/components/SimpleList.vue";
</script>
<template>
<div class="mobile-page">
<NavBar title="简单词"/>
<SimpleList/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import WrongList from "@/pages/mobile/components/WrongList.vue";
</script>
<template>
<div class="mobile-page">
<NavBar title="错词本"/>
<WrongList/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,242 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import {getAudioFileUrl, useChangeAllSound, usePlayAudio} from "@/hooks/sound.ts";
import {SoundFileOptions} from "@/utils/const.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useBaseStore} from "@/stores/base.ts";
const settingStore = useSettingStore()
const store = useBaseStore()
</script>
<template>
<div class="mobile-page">
<NavBar title="音效设置"/>
<div class="page-content">
<div class="row">
<label class="main-title">所有音效</label>
<div class="wrapper">
<el-switch v-model="settingStore.allSound"
@change="useChangeAllSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">单词/句子自动发音</label>
<div class="wrapper">
<el-switch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="sub-title">单词/句子发音口音</label>
<div class="wrapper">
<el-select v-model="settingStore.wordSoundType"
placeholder="请选择"
>
<el-option label="美音" value="us"/>
<el-option label="英音" value="uk"/>
</el-select>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.wordSoundVolume"/>
<span>{{ settingStore.wordSoundVolume }}%</span>
</div>
</div>
<div class="row">
<label class="sub-title">倍速</label>
<div class="wrapper">
<el-slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
<span>{{ settingStore.wordSoundSpeed }}</span>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">按键音</label>
<div class="wrapper">
<el-switch v-model="settingStore.keyboardSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="item-title">按键音效</label>
<div class="wrapper">
<el-select v-model="settingStore.keyboardSoundFile"
placeholder="请选择"
>
<el-option
v-for="item in SoundFileOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<div class="el-option-row">
<span>{{ item.label }}</span>
<VolumeIcon
:time="100"
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
</div>
</el-option>
</el-select>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.keyboardSoundVolume"/>
<span>{{ settingStore.keyboardSoundVolume }}%</span>
</div>
</div>
<div class="line"></div>
<!-- <div class="row">-->
<!-- <label class="item-title">释义发音</label>-->
<!-- <div class="wrapper">-->
<!-- <el-switch v-model="settingStore.translateSound"-->
<!-- inline-prompt-->
<!-- active-text="开"-->
<!-- inactive-text="关"-->
<!-- />-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="row">-->
<!-- <label class="sub-title">音量</label>-->
<!-- <div class="wrapper">-->
<!-- <el-slider v-model="settingStore.translateSoundVolume"/>-->
<!-- <span>{{ settingStore.translateSoundVolume }}%</span>-->
<!-- </div>-->
<!-- </div>-->
<div class="line"></div>
<div class="row">
<label class="item-title">效果音(章节结算页烟花音效)</label>
<div class="wrapper">
<el-switch v-model="settingStore.effectSound"
inline-prompt
active-text="开"
inactive-text="关"
/>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.effectSoundVolume"/>
<span>{{ settingStore.effectSoundVolume }}%</span>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.page-content {
background: var(--color-header-bg);
flex: 1;
height: 100%;
overflow: auto;
padding: 10rem var(--space);
.row {
min-height: 40rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: calc(var(--space) * 5);
.wrapper {
height: 30rem;
flex: 1;
display: flex;
justify-content: flex-end;
gap: var(--space);
span {
text-align: right;
//width: 30rem;
font-size: 12rem;
color: gray;
}
.set-key {
align-items: center;
input {
width: 150rem;
box-sizing: border-box;
margin-right: 10rem;
height: 28rem;
outline: none;
font-size: 16rem;
border: 1px solid gray;
border-radius: 3rem;
padding: 0 5rem;
background: var(--color-second-bg);
color: var(--color-font-1);
}
}
}
.main-title {
font-size: 22rem;
}
.item-title {
font-size: 16rem;
}
.sub-title {
font-size: 14rem;
}
}
.body {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.scroll {
flex: 1;
padding-right: 10rem;
overflow: auto;
}
.footer {
margin-bottom: 20rem;
}
.desc {
margin-bottom: 10rem;
font-size: 12rem;
}
.line {
border-bottom: 1px solid #c4c3c3;
}
}
.el-option-row {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.icon-wrapper {
transform: translateX(10rem);
}
}
</style>

View File

@@ -0,0 +1,202 @@
<script setup lang="ts">
import NavBar from "@/pages/mobile/components/NavBar.vue";
import {getAudioFileUrl, useChangeAllSound, usePlayAudio} from "@/hooks/sound.ts";
import {SoundFileOptions} from "@/utils/const.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useBaseStore} from "@/stores/base.ts";
import {ShortcutKey} from "@/types.ts";
const settingStore = useSettingStore()
const store = useBaseStore()
</script>
<template>
<div class="mobile-page">
<NavBar title="其他设置"/>
<div class="page-content">
<div class="row">
<label class="item-title">显示上一个/下一个单词</label>
<div class="wrapper">
<el-switch v-model="settingStore.showNearWord"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="desc">
开启后练习中会在上方显示上一个/下一个单词
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">忽略大小写</label>
<div class="wrapper">
<el-switch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="desc">
开启后输入时不区分大小写如输入helloHello都会被认为是正确的
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">允许默写模式下显示提示</label>
<div class="wrapper">
<el-switch v-model="settingStore.allowWordTip"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="desc">
开启后可以通过鼠标 hover 单词或者按 {{ settingStore.shortcutKeyMap[ShortcutKey.ShowWord] }} 显示正确答案
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">字体设置(仅可调整单词练习)</label>
</div>
<div class="row">
<label class="sub-title">外语字体</label>
<div class="wrapper">
<el-slider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordForeignFontSize"/>
<span>{{ settingStore.fontSize.wordForeignFontSize }}</span>
</div>
</div>
<div class="row">
<label class="sub-title">中文字体</label>
<div class="wrapper">
<el-slider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordTranslateFontSize"/>
<span>{{ settingStore.fontSize.wordTranslateFontSize }}</span>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">其他设置</label>
</div>
<div class="row">
<label class="sub-title">切换下一个单词时间</label>
<div class="wrapper">
<el-input-number v-model="settingStore.waitTimeForChangeWord"
:min="6"
:max="100"
type="number"
/>
<span>毫秒</span>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.page-content {
background: var(--color-header-bg);
flex: 1;
height: 100%;
overflow: auto;
padding: 10rem var(--space);
.row {
min-height: 40rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: calc(var(--space) * 5);
.wrapper {
height: 30rem;
flex: 1;
display: flex;
justify-content: flex-end;
gap: var(--space);
span {
text-align: right;
//width: 30rem;
font-size: 12rem;
color: gray;
}
.set-key {
align-items: center;
input {
width: 150rem;
box-sizing: border-box;
margin-right: 10rem;
height: 28rem;
outline: none;
font-size: 16rem;
border: 1px solid gray;
border-radius: 3rem;
padding: 0 5rem;
background: var(--color-second-bg);
color: var(--color-font-1);
}
}
}
.main-title {
font-size: 22rem;
}
.item-title {
font-size: 16rem;
}
.sub-title {
font-size: 14rem;
}
}
.body {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.scroll {
flex: 1;
padding-right: 10rem;
overflow: auto;
}
.footer {
margin-bottom: 20rem;
}
.desc {
margin-bottom: 10rem;
font-size: 12rem;
}
.line {
border-bottom: 1px solid #c4c3c3;
}
}
.el-option-row {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.icon-wrapper {
transform: translateX(10rem);
}
}
</style>

View File

@@ -5,6 +5,7 @@ import useTheme from "@/hooks/theme.ts";
import {useSettingStore} from "@/stores/setting.ts";
import NavBar from "@/pages/mobile/components/NavBar.vue";
import {ref} from "vue";
import router from "@/router.ts";
const {toggleTheme} = useTheme()
const settingStore = useSettingStore()
@@ -18,14 +19,14 @@ const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
<NavBar title="设置"/>
<div class="content">
<div class="setting-list">
<div class="item">
<div class="item" @click="router.push('music-setting')">
<Icon icon="bx:headphone" width="22"/>
<div class="right">
<span>音效设置</span>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
</div>
<div class="item">
<div class="item" @click="router.push('other-setting')">
<Icon icon="icon-park-outline:setting-config" width="22"/>
<div class="right" style="border-bottom: none">
<span>其他设置</span>
@@ -41,7 +42,7 @@ const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
</template>
<style scoped lang="scss">
@import "common";
@import "../../common";
.setting {
display: flex;

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {onMounted, onUnmounted, watch} from "vue";
import {onDeactivated, onMounted, onUnmounted, watch} from "vue";
import {usePracticeStore} from "@/stores/practice.ts";
import {useBaseStore} from "@/stores/base.ts";
import {$ref} from "vue/macros";

View File

@@ -8,8 +8,15 @@ import Test from "@/pages/test/test.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import DictDetail from "@/pages/mobile/DictDetail.vue";
import SetDictPlan from "@/pages/mobile/SetDictPlan.vue";
import Setting from "@/pages/mobile/Setting.vue";
import DataManage from "@/pages/mobile/DataManage.vue";
import Setting from "@/pages/mobile/my/setting/Setting.vue";
import DataManage from "@/pages/mobile/my/DataManage.vue";
import CollectPage from "@/pages/mobile/my/CollectPage.vue";
import WrongPage from "@/pages/mobile/my/WrongPage.vue";
import SimplePage from "@/pages/mobile/my/SimplePage.vue";
import About from "@/pages/mobile/my/About.vue";
import Feedback from "@/pages/mobile/my/Feedback.vue";
import MusicSetting from "@/pages/mobile/my/setting/MusicSetting.vue";
import OtherSetting from "@/pages/mobile/my/setting/OtherSetting.vue";
export const routes: RouteRecordRaw[] = [
{path: '/pc/practice', component: Practice},
@@ -20,7 +27,14 @@ export const routes: RouteRecordRaw[] = [
{path: '/mobile/dict-detail', component: DictDetail},
{path: '/mobile/set-dict-plan', name: 'set-dict-plan', component: SetDictPlan},
{path: '/mobile/setting', component: Setting},
{path: '/mobile/music-setting', component: MusicSetting},
{path: '/mobile/other-setting', component: OtherSetting},
{path: '/mobile/data-manage', component: DataManage},
{path: '/mobile/collect', component: CollectPage},
{path: '/mobile/wrong', component: WrongPage},
{path: '/mobile/simple', component: SimplePage},
{path: '/mobile/about', component: About},
{path: '/mobile/feedback', component: Feedback},
{path: '/test', component: Test},
{path: '/', redirect: '/pc/practice'},
]