Adapt to mobile devices

This commit is contained in:
zyronon
2023-12-11 02:14:19 +08:00
parent 9f3d0b8273
commit c34ba02248
12 changed files with 251 additions and 201 deletions

View File

@@ -13,7 +13,7 @@ import SettingDialog from "@/components/dialog/SettingDialog.vue";
import ArticleContentDialog from "@/components/dialog/ArticleContentDialog.vue";
import CollectNotice from "@/components/CollectNotice.vue";
import {SAVE_SETTING_KEY, SAVE_DICT_KEY} from "@/utils/const.ts";
import {shakeCommonDict} from "@/utils";
import {isMobile, shakeCommonDict} from "@/utils";
import router from "@/router.ts";
const store = useBaseStore()
@@ -71,7 +71,7 @@ async function init() {
onMounted(() => {
init()
if (/Mobi|Android|iPhone/i.test(navigator.userAgent)) {
if (isMobile()) {
// 当前设备是移动设备
console.log('当前设备是移动设备')
// router.replace('/mobile')

View File

@@ -4,7 +4,6 @@
@import "anim";
@import 'element-plus/theme-chalk/dark/css-vars';
:root {
--color-background: #E6E8EB;
--color-main-bg: #E6E8EB;
@@ -138,7 +137,6 @@ html.dark {
transition: background var(--anim-time), color var(--anim-time), border var(--anim-time);
}
html, body {
font-size: 1px;
padding: 0;
@@ -152,6 +150,14 @@ html, body {
-moz-osx-font-smoothing: grayscale;
}
.page{
position: relative;
z-index: 1;
height: 100%;
width: 100%;
font-size: 14rem;
}
#app {
width: 100%;
height: 100%;

View File

@@ -5,12 +5,16 @@ import Close from "@/components/icon/Close.vue";
import BaseButton from "@/components/BaseButton.vue";
import {watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {$ref} from "vue/macros";
import {isMobile} from "@/utils";
let settingStore = useSettingStore()
let showNotice = $ref(false)
let show = $ref(false)
let num = $ref(5)
let timer = -1
let mobile = $ref(isMobile())
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
function toggleNotice() {
showNotice = true
@@ -32,17 +36,18 @@ watch(() => settingStore.load, (n) => {
}
})
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
</script>
<template>
<transition name="right">
<div class="CollectNotice" v-if="show">
<div class="CollectNotice"
:class="{mobile}"
v-if="show">
<div class="notice">
坚持练习提高外语能力
<span class="active">Typing Word</span>
保存到收藏夹永不迷失
保存为书签永不迷失
</div>
<div class="wrapper">
<transition name="fade">
@@ -63,7 +68,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
点亮它!
</div>
</div>
<div class="collect-keyboard">或使用收藏快捷键<span
<div class="collect-keyboard" v-if="!mobile">或使用收藏快捷键<span
class="active">{{ isMac ? 'Command' : 'Ctrl' }} + D</span></div>
</div>
<BaseButton v-else size="large" @click="toggleNotice">我想收藏</BaseButton>
@@ -108,6 +113,12 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
line-height: 1.5;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
box-sizing: border-box;
&.mobile{
width: 95%;
padding: 10rem;
}
.notice {
margin-top: 30rem;

View File

@@ -39,10 +39,6 @@ const style = $computed(() => {
display: flex;
transition: all .3s;
}
:deep(.page) {
width: 50%;
}
}
</style>

View File

@@ -15,7 +15,7 @@ onMounted(() => {
</script>
<template>
<div id="page" class="anim">
<div class="anim page">
<header class="anim">
<Logo/>
<div class="nav-list">
@@ -36,13 +36,7 @@ onMounted(() => {
</template>
<style scoped lang="scss">
#page {
position: relative;
z-index: 9;
background: var(--color-main-bg);
height: 100%;
width: 100%;
font-size: 14rem;
.page {
header {
background: var(--color-second-bg);

View File

@@ -1,9 +1,17 @@
<script setup lang="ts">
import BaseButton from "@/components/BaseButton.vue";
import router from "@/router.ts";
function goPractice(){
router.push('/mobile-practice')
}
</script>
<template>
<div class="page home">
<BaseButton @click="goPractice">继续</BaseButton>
</div>
</template>
<style scoped lang="scss">

View File

@@ -3,36 +3,35 @@ import {Icon} from "@iconify/vue";
</script>
<template>
<div class="mobile">
<div class="page mobile">
<div class="content">
<router-view/>
</div>
<div class="tabs">
<div class="tab">
<Icon icon="icon-park:word"/>
<Icon width="30" icon="icon-park:word"/>
<span>单词</span>
</div>
<div class="tab">
<Icon icon="icon-park:word"/>
<Icon width="30" icon="icon-park:word"/>
<span>词典</span>
</div>
<div class="tab">
<Icon width="30" icon="icon-park:word"/>
<span>我的</span>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.mobile {
position: relative;
z-index: 1;
font-size: 14rem;
display: flex;
height: 100vh;
width: 100vw;
flex-direction: column;
.content {
flex: 1;
overflow: hidden;
}
.tabs {
@@ -47,6 +46,7 @@ import {Icon} from "@iconify/vue";
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 14rem;
}
}
}

View File

@@ -8,7 +8,7 @@ import {ShortcutKey, Sort, Word} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {syncMyDictList, useWordOptions} from "@/hooks/dict.ts";
import {onMounted, onUnmounted, watch} from "vue";
import {nextTick, onMounted, onUnmounted, watch} from "vue";
import BaseButton from "@/components/BaseButton.vue";
import Options from "@/pages/practice/Options.vue";
import BaseIcon from "@/components/BaseIcon.vue";
@@ -16,6 +16,9 @@ import MobilePanel from "@/pages/mobile/components/MobilePanel.vue";
import MiniDialog from "@/components/dialog/MiniDialog.vue";
import WordList from "@/components/list/WordList.vue";
import Empty from "@/components/Empty.vue";
import {Icon} from "@iconify/vue";
import router from "@/router.ts";
import Typing from "@/pages/practice/practice-word/Typing.vue";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -71,7 +74,9 @@ watch(() => store.load, n => {
getCurrentPractice()
})
let bodyHeight = $ref('100vh')
onMounted(() => {
bodyHeight = document.body.clientHeight + 'px'
getCurrentPractice()
})
@@ -86,45 +91,80 @@ const {
} = useWordOptions()
let showSortOption = $ref(false)
let showTranslate = $ref(false)
let keyboardKeys = [
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
['z', 'x', 'c', 'v', 'b', 'n', 'm']
]
let inputVal = $ref('')
let inputRef = $ref<HTMLInputElement>()
function change(e) {
console.log('e', e)
e.key = e.data
emitter.emit(EventKey.onTyping, e)
inputRef.value = ''
}
function know() {
settingStore.translate = false
setTimeout(() => {
wordData.index++
}, 300)
}
function unknow() {
settingStore.translate = true
inputRef.focus()
}
</script>
<template>
<div id="mobile">
<div class="practice-center" :style="{height:bodyHeight}">
<div class="slide">
<div class="slide-list" :class="{showPanel:settingStore.showPanel}">
<div class="practice" @click.stop="settingStore.showPanel = false">
<div class="tool-bar">
<BaseIcon
v-if="!isWordCollect(word)"
class="collect"
@click="toggleWordCollect(word)"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordCollect(word)"
icon="ph:star-fill"/>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
icon="tdesign:menu-unfold"/>
</div>
<div class="word-content">
<div class="translate">
<div class="translate-item" v-for="(v,i) in word.trans">
<span>{{ v }}</span>
</div>
<div class="left">
<Icon icon="octicon:arrow-left-24" width="22"
@click="router.back()"
/>
</div>
<div class="word">
{{ word.name }}
<div class="right">
<BaseIcon
v-if="!isWordCollect(word)"
class="collect"
@click="toggleWordCollect(word)"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordCollect(word)"
icon="ph:star-fill"/>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
icon="tdesign:menu-unfold"/>
</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'us' && word.usphone">[{{ word.usphone }}]</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'uk' && word.ukphone">[{{ word.ukphone }}]</div>
</div>
<input ref="inputRef"
style="position:fixed;top:200vh;"
@input="change"
type="text">
<Typing
style="width: 90%;"
v-loading="!store.load"
ref="typingRef"
:word="word"
@next="next"
/>
<div class="options">
<div class="wrapper">
<BaseButton>不认识</BaseButton>
<BaseButton @click="wordData.index++">认识</BaseButton>
<BaseButton @click="unknow">不认识</BaseButton>
<BaseButton @click="know">认识</BaseButton>
</div>
</div>
</div>
@@ -216,13 +256,15 @@ let showSortOption = $ref(false)
</template>
<style scoped lang="scss">
#mobile {
position: relative;
.practice-center {
position: fixed;
z-index: 1;
font-size: 14rem;
color: black;
width: 100%;
height: 100%;
left: 0;
top: 0;
height: 100vh;
$list-width: 75vw;
@@ -236,7 +278,8 @@ let showSortOption = $ref(false)
display: flex;
transition: all .5s;
}
.showPanel{
.showPanel {
transform: translateX(-$list-width);
}
}
@@ -246,46 +289,22 @@ let showSortOption = $ref(false)
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10rem;
.tool-bar {
width: 100%;
height: 50rem;
display: flex;
padding: 0 10rem;
align-items: center;
justify-content: flex-end;
justify-content: space-between;
gap: 10rem;
}
.word-content {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.translate {
width: 80%;
font-size: 18rem;
.translate-item {
display: flex;
align-items: center;
gap: 10rem;
}
}
.word {
font-size: 26rem;
}
.phonetic {
font-size: 16rem;
}
}
.options {
width: 100%;
display: flex;
align-items: center;
justify-content: center;

View File

@@ -214,10 +214,10 @@ defineExpose({del, showWord, hideWord, play})
align-items: center;
justify-content: center;
word-break: break-word;
position: relative;
.phonetic, .translate {
font-size: 20rem;
margin-left: -30rem;
transition: all .3s;
}
@@ -251,6 +251,7 @@ defineExpose({del, showWord, hideWord, play})
}
.word-wrapper {
margin-left: 30rem;
display: flex;
align-items: center;
gap: 10rem;

View File

@@ -424,8 +424,6 @@ onUnmounted(() => {
.options-wrapper {
position: absolute;
//bottom: 0;
margin-left: -30rem;
margin-top: 120rem;
}
}

View File

@@ -2,19 +2,32 @@ import * as VueRouter from 'vue-router'
import Practice from "@/pages/practice/index.vue";
import Dict from '@/pages/dict/index.vue'
import Mobile from '@/pages/mobile/index.vue'
import MobileHome from '@/pages/mobile/home.vue'
import MobilePractice from '@/pages/mobile/practice.vue'
import Test from "@/pages/test.vue";
import {RouteRecordRaw} from "vue-router";
const routes: any[] = [
{path: '/practice', component: Practice},
{path: '/dict', component: Dict},
{path: '/mobile', component: Mobile},
{path: '/test', component: Test},
{path: '/', redirect: '/practice'},
const routes: RouteRecordRaw[] = [
{path: '/practice', component: Practice},
{path: '/dict', component: Dict},
{
path: '/mobile', component: Mobile,
redirect:'/mobile/home',
children: [
{
path: 'home',
component: MobileHome,
},
]
},
{path: '/mobile-practice', component: MobilePractice,},
{path: '/test', component: Test},
{path: '/', redirect: '/practice'},
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
history: VueRouter.createWebHashHistory(),
routes,
})
export default router

View File

@@ -6,120 +6,124 @@ import {cloneDeep} from "lodash-es";
import {Dict, DictType} from "@/types.ts";
export function getRandom(a: number, b: number): number {
return Math.random() * (b - a) + a;
return Math.random() * (b - a) + a;
}
export function no() {
ElMessage.warning('未现实')
ElMessage.warning('未现实')
}
export function checkAndUpgradeSaveDict(val: string) {
// console.log(configStr)
// console.log('s', new Blob([val]).size)
// val = ''
if (val) {
try {
let data
if (typeof val === 'string') {
data = JSON.parse(val)
} else {
data = val
}
let state: BaseState = data.val
if (typeof state !== 'object') {
return {}
}
if (!data.version) {
return {}
}
state.load = false
let version = Number(data.version)
// console.log('state', state)
let defaultBaseState = DefaultBaseState()
if (version === SAVE_DICT_KEY.version) {
//防止人为删除数据,导致数据不完整报错
for (const [key, value] of Object.entries(defaultBaseState)) {
if (state[key] !== undefined) defaultBaseState[key] = state[key]
}
return defaultBaseState
} else {
//防止人为删除数据,导致数据不完整报错
for (const [key, value] of Object.entries(defaultBaseState)) {
if (state[key] !== undefined) defaultBaseState[key] = state[key]
}
return defaultBaseState
}
} catch (e) {
return {}
// console.log(configStr)
// console.log('s', new Blob([val]).size)
// val = ''
if (val) {
try {
let data
if (typeof val === 'string') {
data = JSON.parse(val)
} else {
data = val
}
let state: BaseState = data.val
if (typeof state !== 'object') {
return {}
}
if (!data.version) {
return {}
}
state.load = false
let version = Number(data.version)
// console.log('state', state)
let defaultBaseState = DefaultBaseState()
if (version === SAVE_DICT_KEY.version) {
//防止人为删除数据,导致数据不完整报错
for (const [key, value] of Object.entries(defaultBaseState)) {
if (state[key] !== undefined) defaultBaseState[key] = state[key]
}
return defaultBaseState
} else {
//防止人为删除数据,导致数据不完整报错
for (const [key, value] of Object.entries(defaultBaseState)) {
if (state[key] !== undefined) defaultBaseState[key] = state[key]
}
return defaultBaseState
}
} catch (e) {
return {}
}
return {}
}
return {}
}
export function checkAndUpgradeSaveSetting(val: string) {
// console.log(configStr)
// console.log('s', new Blob([val]).size)
// val = ''
if (val) {
try {
let data
if (typeof val === 'string') {
data = JSON.parse(val)
} else {
data = val
}
let state: SettingState = data.val
if (typeof state !== 'object') {
return {}
}
if (!data.version) {
return {}
}
state.load = false
let version = Number(data.version)
let defaultSettingState = DefaultSettingState()
if (version === SAVE_SETTING_KEY.version) {
//防止人为删除数据,导致数据不完整报错
for (const [key, value] of Object.entries(defaultSettingState)) {
if (state[key] !== undefined) defaultSettingState[key] = state[key]
}
return defaultSettingState
} else {
//为了保持永远是最新的快捷键选项列表,但保留住用户的自定义设置,去掉无效的快捷键选项
//例: 2版本可能有快捷键A。3版本没有了
for (const [key, value] of Object.entries(defaultSettingState.shortcutKeyMap)) {
if (state.shortcutKeyMap[key] !== undefined) defaultSettingState.shortcutKeyMap[key] = state.shortcutKeyMap[key]
}
delete state.shortcutKeyMap
for (const [key, value] of Object.entries(defaultSettingState)) {
if (state[key] !== undefined) defaultSettingState[key] = state[key]
}
return defaultSettingState
}
} catch (e) {
return {}
// console.log(configStr)
// console.log('s', new Blob([val]).size)
// val = ''
if (val) {
try {
let data
if (typeof val === 'string') {
data = JSON.parse(val)
} else {
data = val
}
let state: SettingState = data.val
if (typeof state !== 'object') {
return {}
}
if (!data.version) {
return {}
}
state.load = false
let version = Number(data.version)
let defaultSettingState = DefaultSettingState()
if (version === SAVE_SETTING_KEY.version) {
//防止人为删除数据,导致数据不完整报错
for (const [key, value] of Object.entries(defaultSettingState)) {
if (state[key] !== undefined) defaultSettingState[key] = state[key]
}
return defaultSettingState
} else {
//为了保持永远是最新的快捷键选项列表,但保留住用户的自定义设置,去掉无效的快捷键选项
//例: 2版本可能有快捷键A。3版本没有了
for (const [key, value] of Object.entries(defaultSettingState.shortcutKeyMap)) {
if (state.shortcutKeyMap[key] !== undefined) defaultSettingState.shortcutKeyMap[key] = state.shortcutKeyMap[key]
}
delete state.shortcutKeyMap
for (const [key, value] of Object.entries(defaultSettingState)) {
if (state[key] !== undefined) defaultSettingState[key] = state[key]
}
return defaultSettingState
}
} catch (e) {
return {}
}
return {}
}
return {}
}
//筛选未自定义的词典,未自定义的词典不需要保存单词,用的时候再下载
export function shakeCommonDict(n: BaseState): BaseState {
let data: BaseState = cloneDeep(n)
data.myDictList.map((v: Dict) => {
if (v.isCustom) {
if (v.type === DictType.article) {
v.articles.map(s => {
delete s.sections
})
}
} else {
if (v.type === DictType.word) v.originWords = []
if (v.type === DictType.article) v.articles = []
v.words = []
v.chapterWords = []
}
})
return data
let data: BaseState = cloneDeep(n)
data.myDictList.map((v: Dict) => {
if (v.isCustom) {
if (v.type === DictType.article) {
v.articles.map(s => {
delete s.sections
})
}
} else {
if (v.type === DictType.word) v.originWords = []
if (v.type === DictType.article) v.articles = []
v.words = []
v.chapterWords = []
}
})
return data
}
export function isMobile():boolean {
return /Mobi|Android|iPhone/i.test(navigator.userAgent)
}