add many pages

This commit is contained in:
zyronon
2023-12-18 02:24:11 +08:00
parent fa448ef25e
commit 6710fbcc5e
21 changed files with 583 additions and 41 deletions

3
components.d.ts vendored
View File

@@ -11,12 +11,15 @@ declare module 'vue' {
ArticleContentDialog: typeof import('./src/components/dialog/ArticleContentDialog.vue')['default']
ArticleList: typeof import('./src/components/list/ArticleList.vue')['default']
Backgorund: typeof import('./src/components/Backgorund.vue')['default']
BackIcon: typeof import('./src/components/icon/BackIcon.vue')['default']
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
BaseList: typeof import('./src/components/list/BaseList.vue')['default']
ChapterName: typeof import('./src/components/toolbar/ChapterName.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
CollectNotice: typeof import('./src/components/CollectNotice.vue')['default']
Delete: typeof import('./src/components/icon/Delete.vue')['default']
DeleteIcon: typeof import('./src/components/icon/DeleteIcon.vue')['default']
Dialog: typeof import('./src/components/dialog/Dialog.vue')['default']
DictDiglog: typeof import('./src/components/dialog/DictDiglog.vue')['default']
DictGroup: typeof import('./src/components/list/DictGroup.vue')['default']

View File

@@ -34,6 +34,7 @@
"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",

26
pnpm-lock.yaml generated
View File

@@ -56,6 +56,9 @@ dependencies:
tesseract.js:
specifier: ^4.1.1
version: 4.1.2
vant:
specifier: ^4.8.1
version: 4.8.1(vue@3.3.4)
vue:
specifier: ^3.3.4
version: 3.3.4
@@ -941,6 +944,18 @@ packages:
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
dev: false
/@vant/popperjs@1.3.0:
resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
dev: false
/@vant/use@1.6.0(vue@3.3.4):
resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==}
peerDependencies:
vue: ^3.0.0
dependencies:
vue: 3.3.4
dev: false
/@vitejs/plugin-vue-jsx@3.0.2(vite@4.4.9)(vue@3.3.4):
resolution: {integrity: sha512-obF26P2Z4Ogy3cPp07B4VaW6rpiu0ue4OT2Y15UxT5BZZ76haUY9guOsZV3uWh/I6xc+VeiW+ZVabRE82FyzWw==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -4976,6 +4991,17 @@ packages:
engines: {node: '>= 0.10'}
dev: true
/vant@4.8.1(vue@3.3.4):
resolution: {integrity: sha512-SkFZM3Z3Bwi5do+iQNfRgDi7b+Ka29rUUNzck06W2KoFie3CLTqSifLa5TuZCEoXPSkqR+fRH/VE5G57mmL8sg==}
peerDependencies:
vue: ^3.0.0
dependencies:
'@vant/popperjs': 1.3.0
'@vant/use': 1.6.0(vue@3.3.4)
'@vue/shared': 3.3.4
vue: 3.3.4
dev: false
/vinyl-fs@3.0.3:
resolution: {integrity: sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==}
engines: {node: '>= 0.10'}

View File

@@ -14,7 +14,9 @@ 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 {isMobile, shakeCommonDict} from "@/utils";
import router from "@/router.ts";
import router, {routes} from "@/router.ts";
import {$ref} from "vue/macros";
import {useRoute} from "vue-router";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -77,18 +79,44 @@ onMounted(() => {
// router.replace('/mobile')
}
})
let transitionName = $ref('go')
const route = useRoute()
watch(() => route.path, (to, from) => {
// console.log('watch', to, from)
// //footer下面的5个按钮对跳不要用动画
let noAnimation = [
'/pc/practice',
'/pc/dict',
'/mobile',
'/'
]
if (noAnimation.indexOf(from) !== -1 && noAnimation.indexOf(to) !== -1) {
return transitionName = ''
}
const toDepth = routes.findIndex(v => v.path === to)
const fromDepth = routes.findIndex(v => v.path === from)
transitionName = toDepth > fromDepth ? 'go' : 'back'
// console.log('transitionName', transitionName, toDepth, fromDepth)
})
</script>
<template>
<Backgorund/>
<router-view/>
<router-view v-slot="{ Component }">
<transition :name="transitionName">
<keep-alive :exclude="runtimeStore.excludeRoutes">
<component :is="Component"/>
</keep-alive>
</transition>
</router-view>
<CollectNotice/>
<ArticleContentDialog/>
<SettingDialog/>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
@import "@/assets/css/style";
.main-page {
position: relative;

View File

@@ -48,4 +48,30 @@
60% {
transform: translate3d(4px, 0, 0);
}
}
}
.go-enter-from {
transform: translate3d(100%, 0, 0);
}
//最终状态
.back-enter-to, .back-enter-from, .go-enter-to, .go-leave-from {
transform: translate3d(0, 0, 0);
}
.go-leave-to {
transform: translate3d(-100%, 0, 0);
}
.go-enter-active, .go-leave-active, .back-enter-active, .back-leave-active {
transition: all .3s;
}
.back-enter-from {
transform: translate3d(-100%, 0, 0);
}
.back-leave-to {
transform: translate3d(100%, 0, 0);
}

View File

@@ -159,6 +159,20 @@ html, body {
flex-direction: column;
}
.mobile-page {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
overflow: auto;
font-size: 18rem;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
#app {
width: 100%;
height: 100%;

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
</script>
<template>
<Icon
class="back-icon"
icon="octicon:arrow-left-24" width="22"
/>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
</script>
<template>
<Icon icon="solar:trash-bin-minimalistic-linear"/>
</template>
<style scoped lang="scss">
</style>

View File

@@ -44,16 +44,16 @@ const state = reactive({
start: {x: 0, y: 0, time: 0},
move: {x: 0, y: 0},
wrapper: {width: 0, height: 0, childrenLength: 0},
slideItemsWidths:[]
slideItemsWidths: []
})
watch(
() => props.index,
(newVal) => {
watch(() => props.index, (newVal) => {
if (state.localIndex !== newVal) {
state.localIndex = newVal
if (props.changeActiveIndexUseAnim) {
GM.$setCss(wrapperEl.value, 'transition-duration', `300ms`)
} else {
GM.$setCss(wrapperEl.value, 'transition-duration', `0ms`)
}
GM.$setCss(wrapperEl.value, 'transform', `translate3d(${getSlideDistance(state, SlideType.HORIZONTAL)}px, 0, 0)`)
}
@@ -84,9 +84,9 @@ function touchEnd(e) {
function canNext(isNext) {
if (isNext){
if (isNext) {
return state.localIndex !== state.wrapper.childrenLength - 1
}else {
} else {
return state.localIndex !== 0
}
}

View File

@@ -18,7 +18,7 @@ export function slideInit(el, state, type) {
if (type === SlideType.HORIZONTAL) dx1 = t
else dx2 = t
console.log('start', dx1)
// console.log('start', dx1)
Utils.$setCss(el, 'transform', `translate3d(${dx1}px, ${dx2}px, 0)`)
}

View File

@@ -0,0 +1,178 @@
<script setup lang="ts">
import SlideHorizontal from "@/components/slide/SlideHorizontal.vue";
import SlideItem from "@/components/slide/SlideItem.vue";
import {$ref} from "vue/macros";
import {useBaseStore} from "@/stores/base.ts";
import {showConfirmDialog, showToast} from "vant";
import 'vant/lib/index.css'
import {onMounted} from "vue";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import {Dict} from "@/types.ts";
import DictPlan from "@/pages/mobile/components/DictPlan.vue";
import BackIcon from "@/components/icon/BackIcon.vue";
import {useRouter} from "vue-router";
const store = useBaseStore()
let index = $ref(0)
const router = useRouter()
const onChange = ({selectedValues}) => {
showToast(`当前值: ${selectedValues.join(',')}`);
};
onMounted(() => {
})
function handleDel(item: Dict, index: number) {
if (item.id === store.currentDict.id) {
//TODO
} else {
showConfirmDialog({title: '确认删除?', message: '删除后无法撤销,确认删除吗?',})
.then(() => {
store.myDictList.splice(index, 1)
})
}
}
</script>
<template>
<div class="mobile-page">
<header>
<BackIcon @click="router.back()"/>
<div class="tabs">
<div class="tab" :class="index === 0 && 'active'" @click="index = 0">修改计划</div>
<div class="tab" :class="index === 1 && 'active'" @click="index = 1">更换词书</div>
</div>
</header>
<SlideHorizontal v-model:index="index">
<SlideItem>
<DictPlan/>
</SlideItem>
<SlideItem>
<div class="my-dcits">
<div class="list">
<div class="dict" v-for="(item,index) in store.myDictList">
<div class="title">
<div class="name">{{ item.name }}</div>
<span v-if="item.id === store.currentDict.id">当前在学</span>
<template v-else>
<DeleteIcon
v-if="index>=3"
@click="handleDel(item,index)"/>
</template>
</div>
<div class="chapter">每日{{ item.chapterWordNumber }} 剩余100天</div>
<el-progress
:show-text="false"
:percentage="90"
/>
<div class="progress">
<span>已学单词</span>
<span>0/{{ item.length }}</span>
</div>
</div>
</div>
<BaseButton size="large">添加新书</BaseButton>
</div>
</SlideItem>
</SlideHorizontal>
</div>
</template>
<style scoped lang="scss">
header {
height: 60rem;
display: flex;
align-items: center;
position: relative;
padding: 0 var(--space);
.back {
position: absolute;
}
.tabs {
width: 100%;
border-top: 1px solid gray;
height: 100%;
display: flex;
justify-content: center;
.tab {
width: 100rem;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.active {
border-bottom: 2px solid gray;
}
}
}
.plan {
padding: 10rem;
.dict {
display: flex;
flex-direction: column;
gap: 10rem;
}
.set-plan {
background: white;
.header {
height: 60rem;
color: black;
display: flex;
justify-content: space-around;
align-items: center;
}
.picker-wrapper {
display: flex;
.van-picker {
flex: 1;
}
}
}
}
.my-dcits {
height: 100%;
padding: var(--space);
box-sizing: border-box;
display: flex;
flex-direction: column;
.list {
flex: 1;
overflow: auto;
margin-bottom: 20rem;
}
.dict {
padding: var(--space);
border-radius: var(--radius);
background: var(--color-second-bg);
display: flex;
flex-direction: column;
gap: 6rem;
margin-bottom: 10rem;
.title {
display: flex;
justify-content: space-between;
}
}
}
</style>

View File

@@ -9,8 +9,9 @@ import {dictionaryResources} from "@/assets/dictionary.ts";
import {DictResource, languageCategoryOptions} from "@/types.ts";
import {onMounted} from "vue";
import DictGroup from "@/components/list/DictGroup.vue";
import router from "@/router.ts";
let index = $ref(0)
let index = $ref(1)
const store = useBaseStore()
@@ -100,6 +101,11 @@ onMounted(() => {
let temp1 = getData('word')
wordData = temp1
})
function selectDict(val) {
console.log('val', val)
router.push('/mobile/set-dict-plan')
}
</script>
<template>
@@ -113,7 +119,8 @@ onMounted(() => {
<span>{{ item.name }}</span>
</div>
</div>
<SlideHorizontal v-model:index="index">
<SlideHorizontal
v-model:index="index">
<SlideItem>
<div class="translate">
<span>翻译</span>
@@ -143,6 +150,7 @@ onMounted(() => {
</el-radio-group>
</div>
<DictGroup
@select-dict="selectDict"
v-for="item in wordData.dictList"
:select-id="store.currentDict.id"
:groupByTag="item[1]"

View File

@@ -8,7 +8,7 @@ import {Icon} from "@iconify/vue";
const store = useBaseStore()
function goPractice() {
router.push('/mobile-practice')
router.push('/mobile/practice')
}
</script>
@@ -16,7 +16,7 @@ function goPractice() {
<div class="page home">
<div class="current-dict">
<div class="top">
<div class="left">
<div class="left" @click="router.push('/mobile/dict-detail')">
<div class="name">{{ store.currentDict.name }}</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import DictPlan from "@/pages/mobile/components/DictPlan.vue";
import NavBar from "@/pages/mobile/components/NavBar.vue";
</script>
<template>
<div class="mobile-page">
<NavBar title="设置任务量"/>
<DictPlan/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,118 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import {useBaseStore} from "@/stores/base.ts";
import {Picker, showToast} from "vant";
import 'vant/lib/index.css'
import {onMounted} from "vue";
import BaseButton from "@/components/BaseButton.vue";
const store = useBaseStore()
let columns = $ref([])
let columns2 = $ref([])
const onChange = ({selectedValues}) => {
showToast(`当前值: ${selectedValues.join(',')}`);
};
onMounted(() => {
columns = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200].map(value => {
return {
text: value,
value,
}
})
columns2 = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200].map(value => {
return {
text: value,
value,
}
})
})
</script>
<template>
<div class="plan">
<div class="content">
<div class="dict">
<div class="name">{{ store.currentDict.name }}</div>
<div class="chapter">每日{{ store.currentDict.chapterWordNumber }} 剩余100天</div>
<el-progress
:show-text="false"
:percentage="90"
/>
<div class="progress">
<span>已学单词</span>
<span>0/{{ store.currentDict.length }}</span>
</div>
</div>
<div class="notice">
<span>完成日期</span>
<span class="date">2023年1月1日</span>
<span>预计每天11分钟</span>
</div>
<div class="set-plan">
<div class="header">
<span>每天背单词</span>
<span>完成天数</span>
</div>
<div class="picker-wrapper">
<Picker
:show-toolbar="false"
:columns="columns"
@change="onChange"
/>
<Picker
:show-toolbar="false"
:columns="columns2"
@change="onChange"
/>
</div>
</div>
</div>
<BaseButton size="large">确认</BaseButton>
</div>
</template>
<style scoped lang="scss">
.plan {
height: 100%;
padding: 10rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
.content {
flex: 1;
.dict {
display: flex;
flex-direction: column;
gap: 10rem;
}
.set-plan {
background: white;
.header {
height: 60rem;
color: black;
display: flex;
justify-content: space-around;
align-items: center;
}
.picker-wrapper {
display: flex;
.van-picker {
flex: 1;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import BackIcon from "@/components/icon/BackIcon.vue";
import {useRouter} from "vue-router";
const router = useRouter()
defineProps<{
title?: string
}>()
</script>
<template>
<div class="nav-bar">
<BackIcon @click="router.back()"/>
<div class="title" v-if="title">{{ title }}</div>
</div>
</template>
<style scoped lang="scss">
.nav-bar {
height: 60rem;
padding: 0 var(--space);
display: flex;
align-items: center;
justify-content: center;
position: relative;
:deep(.back-icon) {
left: var(--space);
position: absolute;
}
}
</style>

View File

@@ -3,23 +3,23 @@ import {Icon} from "@iconify/vue";
import SlideHorizontal from "@/components/slide/SlideHorizontal.vue";
import SlideItem from "@/components/slide/SlideItem.vue";
import Home from "@/pages/mobile/Home.vue";
import DictManage from "@/pages/mobile/DictManage.vue";
import DictListManage from "@/pages/mobile/DictListManage.vue";
import Setting from "@/pages/mobile/Setting.vue";
let state = $ref({
baseIndex: 1
})
let index = $ref(1)
</script>
<template>
<div class="page mobile">
<div class="mobile-page mobile">
<div class="content">
<SlideHorizontal
v-model:index="state.baseIndex">
:changeActiveIndexUseAnim="false"
v-model:index="index">
<SlideItem>
<Home/>
</SlideItem>
<SlideItem>
<DictManage/>
<DictListManage/>
</SlideItem>
<SlideItem>
<Setting/>
@@ -27,15 +27,15 @@ let state = $ref({
</SlideHorizontal>
</div>
<div class="tabs">
<div class="tab" @click="state.baseIndex = 0">
<div class="tab" @click="index = 0">
<Icon width="30" icon="icon-park:word"/>
<span>单词</span>
</div>
<div class="tab" @click="state.baseIndex = 1">
<div class="tab" @click="index = 1">
<Icon width="30" icon="icon-park:word"/>
<span>词典</span>
</div>
<div class="tab" @click="state.baseIndex = 2">
<div class="tab" @click="index = 2">
<Icon width="30" icon="icon-park:word"/>
<span>我的</span>
</div>
@@ -45,8 +45,6 @@ let state = $ref({
<style scoped lang="scss">
.mobile {
display: flex;
flex-direction: column;
.content {
flex: 1;

View File

@@ -134,17 +134,11 @@ onUnmounted(() => {
</script>
<template>
<div class="practice-wrapper">
<div class="mobile-page">
<PracticeWord ref="practiceRef"/>
</div>
</template>
<style scoped lang="scss">
.practice-wrapper {
font-size: 14rem;
width: 100%;
height: 100%;
display: flex;
}
</style>

View File

@@ -5,15 +5,18 @@ import Dict from '@/pages/pc/dict/index.vue'
import Mobile from '@/pages/mobile/index.vue'
import MobilePractice from '@/pages/mobile/practice/index.vue'
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";
const routes: RouteRecordRaw[] = [
export const routes: RouteRecordRaw[] = [
{path: '/pc/practice', component: Practice},
{path: '/pc/dict', component: Dict},
{
path: '/mobile', component: Mobile,
// redirect:'/mobile/home',
},
{path: '/mobile-practice', component: MobilePractice,},
{path: '/mobile', component: Mobile,},
{path: '/mobile/practice', component: MobilePractice},
{path: '/mobile/dict-detail', component: DictDetail},
{path: '/mobile/set-dict-plan', component: SetDictPlan},
{path: '/test', component: Test},
{path: '/', redirect: '/pc/practice'},
]
@@ -21,6 +24,57 @@ const routes: RouteRecordRaw[] = [
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// console.log('savedPosition', savedPosition)
if (savedPosition) {
return savedPosition
} else {
return {top: 0}
}
},
})
router.beforeEach((to, from) => {
// console.log('beforeEach-to',to.path)
// console.log('beforeEach-from',from.path)
const runtimeStore = useRuntimeStore()
//footer下面的5个按钮对跳不要用动画
let noAnimation = [
'/pc/practice',
'/pc/dict',
'/mobile',
'/'
]
if (noAnimation.indexOf(from.path) !== -1 && noAnimation.indexOf(to.path) !== -1) {
return true
}
const toDepth = routes.findIndex(v => v.path === to.path)
const fromDepth = routes.findIndex(v => v.path === from.path)
// const fromDepth = routeDeep.indexOf(from.path)
if (toDepth > fromDepth) {
if (to.matched && to.matched.length) {
let toComponentName = to.matched[0].components.default.name
runtimeStore.updateExcludeRoutes({type: 'remove', value: toComponentName})
// console.log('to', toComponentName)
// console.log('前进')
// console.log('删除', toComponentName)
}
} else {
if (from.matched && from.matched.length) {
let fromComponentName = from.matched[0].components.default.name
runtimeStore.updateExcludeRoutes({type: 'add', value: fromComponentName})
// console.log('后退')
// console.log('添加', fromComponentName)
}
}
// ...
// 返回 false 以取消导航
return true
})
export default router

View File

@@ -135,7 +135,7 @@ export const useBaseStore = defineStore('base', {
].includes(this.currentDict.type)
},
currentDict(): Dict {
return this.myDictList[this.current.index]
return this.myDictList[this.current.index]??{}
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []

View File

@@ -8,6 +8,7 @@ export interface RuntimeState {
editDict: Dict
showDictModal: boolean
showSettingModal: boolean
excludeRoutes: any[]
}
export const useRuntimeStore = defineStore('runtime', {
@@ -18,6 +19,22 @@ export const useRuntimeStore = defineStore('runtime', {
editDict: cloneDeep(DefaultDict),
showDictModal: false,
showSettingModal: false,
excludeRoutes: [],
}
},
actions: {
updateExcludeRoutes(val: any) {
if (val.type === 'add') {
if (!this.excludeRoutes.find(v => v === val.value)) {
this.excludeRoutes.push(val.value)
}
} else {
let resIndex = this.excludeRoutes.findIndex(v => v === val.value)
if (resIndex !== -1) {
this.excludeRoutes.splice(resIndex, 1)
}
}
// console.log('store.excludeRoutes', this.excludeRoutes)
},
}
})