This commit is contained in:
zyronon
2023-12-22 02:47:22 +08:00
parent 7d2cd00ca2
commit e266a3ccc2
10 changed files with 311 additions and 54 deletions

View File

@@ -2,7 +2,6 @@
import {onMounted, watch} from "vue";
import {BaseState, useBaseStore} from "@/stores/base.ts";
import {Dict, DictType} from "@/types.ts"
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {cloneDeep} from "lodash-es";
@@ -12,9 +11,9 @@ import * as localforage from "localforage";
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 {SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/utils/const.ts";
import {isMobile, shakeCommonDict} from "@/utils";
import router, {routes} from "@/router.ts";
import {routes} from "@/router.ts";
import {$ref} from "vue/macros";
import {useRoute} from "vue-router";

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import {APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/utils/const.ts";
import BaseButton from "@/components/BaseButton.vue";
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict} from "@/utils";
import {saveAs} from "file-saver";
import {useSettingStore} from "@/stores/setting.ts";
import {useBaseStore} from "@/stores/base.ts";
import NavBar from "@/pages/mobile/components/NavBar.vue";
const settingStore = useSettingStore()
const store = useBaseStore()
function exportData() {
let data = {
version: EXPORT_DATA_KEY.version,
val: {
setting: {
version: SAVE_SETTING_KEY.version,
val: settingStore.$state
},
dict: {
version: SAVE_DICT_KEY.version,
val: shakeCommonDict(store.$state)
}
}
}
let blob = new Blob([JSON.stringify(data)], {type: "text/plain;charset=utf-8"});
let date = new Date()
let dateStr = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`
saveAs(blob, `${APP_NAME}-User-Data-${dateStr}.json`);
ElMessage.success('导出成功!')
}
function importData(e) {
let file = e.target.files[0]
if (!file) return
// no()
let reader = new FileReader();
reader.onload = function (v) {
let str = v.target.result;
if (str) {
let obj = JSON.parse(str)
if (obj.version === EXPORT_DATA_KEY.version) {
} else {
//TODO
}
let data = obj.val
let settingState = checkAndUpgradeSaveSetting(data.setting)
settingStore.setState(settingState)
let dictState = checkAndUpgradeSaveDict(data.dict)
store.init(dictState)
ElMessage.success('导入成功!')
}
}
reader.readAsText(file);
}
</script>
<template>
<div class="mobile-page">
<NavBar title="数据管理"/>
<div class="row">
<div class="main-title">数据导出</div>
</div>
<div class="row">
<label class="sub-title">
目前用户的所有数据(自定义设置自定义词典练习进度等)
<b>仅保存在本地</b>
如果您需要在不同的设备浏览器或者其他非官方部署上使用 {{ APP_NAME }} 您需要手动进行数据同步和保存
</label>
</div>
<div class="row">
<BaseButton @click="exportData">数据导出</BaseButton>
</div>
<div class="row">
<div class="main-title">数据导入</div>
</div>
<div class="row">
<label class="sub-title">
请注意导入数据将
<b style="color: red"> 完全覆盖 </b>
当前数据请谨慎操作
</label>
</div>
<div class="row">
<div class="import hvr-grow">
<BaseButton>数据导入</BaseButton>
<input type="file"
accept="application/json"
@change="importData">
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>

94
src/pages/mobile/My.vue Normal file
View File

@@ -0,0 +1,94 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
import IconWrapper from "@/components/IconWrapper.vue";
import useTheme from "@/hooks/theme.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {go} from "@/utils";
import router from "@/router.ts";
const {toggleTheme} = useTheme()
const settingStore = useSettingStore()
</script>
<template>
<div class="page setting">
<div class="setting-list">
<div class="item">
<div class="left">
<Icon icon="uil:setting" width="22"/>
<span>收藏</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item">
<div class="left">
<Icon icon="uil:setting" width="22"/>
<span>错词本</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item" @click="router.push('/mobile/setting')">
<div class="left">
<Icon icon="uil:setting" width="22"/>
<span>设置</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item">
<div class="left">
<Icon icon="mdi:about-circle-outline" width="22"/>
<span>问题反馈</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item">
<div class="left">
<Icon icon="mdi:about-circle-outline" width="22"/>
<span>关于我们</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item">
<IconWrapper>
<Icon icon="ep:moon" v-if="settingStore.theme === 'dark'"
@click="toggleTheme"/>
<Icon icon="tabler:sun" v-else @click="toggleTheme"/>
</IconWrapper>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.setting {
font-size: 18rem;
display: flex;
flex-direction: column;
align-items: center;
.setting-list {
margin-top: 100rem;
background: var(--color-third-bg);
border-radius: 8rem;
width: 80%;
.item {
height: 60rem;
padding: 0 20rem;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
gap: 10rem;
}
}
}
}
</style>

View File

@@ -4,6 +4,8 @@ import {Icon} from "@iconify/vue";
import IconWrapper from "@/components/IconWrapper.vue";
import useTheme from "@/hooks/theme.ts";
import {useSettingStore} from "@/stores/setting.ts";
import NavBar from "@/pages/mobile/components/NavBar.vue";
import router from "@/router.ts";
const {toggleTheme} = useTheme()
const settingStore = useSettingStore()
@@ -11,47 +13,44 @@ const settingStore = useSettingStore()
</script>
<template>
<div class="page setting">
<div class="mobile-page setting">
<NavBar title="设置"/>
<div class="setting-list">
<div class="item">
<div class="left">
<Icon icon="uil:setting" width="22"/>
<span>设置</span>
<span>音效设置</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item">
<div class="left">
<Icon icon="mdi:about-circle-outline" width="22"/>
<span>关于</span>
<Icon icon="uil:setting" width="22"/>
<span>其他设置</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
<div class="item">
<IconWrapper>
<Icon icon="ep:moon" v-if="settingStore.theme === 'dark'"
@click="toggleTheme"/>
<Icon icon="tabler:sun" v-else @click="toggleTheme"/>
</IconWrapper>
<div class="item" @click="router.push('/mobile/data-manage')">
<div class="left">
<Icon icon="uil:setting" width="22"/>
<span>数据管理</span>
</div>
<Icon class="arrow" icon="mingcute:right-line" width="20"/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.setting {
font-size: 18rem;
display: flex;
flex-direction: column;
align-items: center;
.setting-list {
margin-top: 100rem;
background: var(--color-third-bg);
border-radius: 8rem;
width: 80%;
width: 90%;
.item {
height: 60rem;

View File

@@ -7,6 +7,7 @@ import {onMounted} from "vue";
import BaseButton from "@/components/BaseButton.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import router from "@/router.ts";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -16,50 +17,70 @@ let columns = $ref([])
let columns2 = $ref([])
let chapterWordNumber = $ref([runtimeStore.editDict.chapterWordNumber])
let length = $ref(runtimeStore.editDict.length)
let days = $ref([Math.ceil(length / chapterWordNumber)])
let completeDay = $ref([Math.ceil(length / chapterWordNumber[0])])
const onChange = ({selectedValues}) => {
chapterWordNumber = selectedValues
days = [Math.ceil(length / chapterWordNumber[0])]
console.log('days', days, chapterWordNumber)
completeDay = [Math.ceil(length / chapterWordNumber[0])]
};
const onChange2 = ({selectedValues}) => {
days = selectedValues
chapterWordNumber = [Math.ceil(length / days[0])]
console.log('days', days, chapterWordNumber)
completeDay = selectedValues
for (let i = 0; i < columns.length; i++) {
let v = columns[i]
let s = Math.ceil(length / v.value)
if (s === completeDay[0]) {
chapterWordNumber = [v.value]
break
}
}
};
onMounted(() => {
console.log('runtimeStore.editDict.length',runtimeStore.editDict.length)
let list = []
if (length < 50) {
list = Array.from({length: Math.floor(length / 5)}).map((v, i) => (i + 1) * 5)
}
if (length > 50) {
list = Array.from({length: 10}).map((v, i) => (i + 1) * 5)
}
if (length > 100) {
list = list.concat(Array.from({length: 5}).map((v, i) => 50 + (i + 1) * 10))
} else {
list = list.concat(Array.from({length: Math.floor((length - 50) / 10)}).map((v, i) => 50 + (i + 1) * 10))
}
if (length > 200) {
list = list.concat(Array.from({length: 4}).map((v, i) => 100 + (i + 1) * 25))
} else {
list = list.concat(Array.from({length: Math.floor((length - 100) / 25)}).map((v, i) => 100 + (i + 1) * 25))
}
if (length > 500) {
list = list.concat(Array.from({length: 6}).map((v, i) => 200 + (i + 1) * 50))
} else {
list = list.concat(Array.from({length: Math.floor((length - 200) / 50)}).map((v, i) => 200 + (i + 1) * 50))
}
let d = Math.floor((length - 500) / 100)
console.log('d', d)
if (d) {
list = list.concat(Array.from({length: d}).map((v, i) => 500 + (i + 1) * 100))
if (length > 1000) {
list = list.concat(Array.from({length: 5}).map((v, i) => 500 + (i + 1) * 100))
} else {
list = list.concat(Array.from({length: Math.floor((length - 500) / 100)}).map((v, i) => 500 + (i + 1) * 100))
}
if (length > 3000) {
list = list.concat(Array.from({length: 8}).map((v, i) => 1000 + (i + 1) * 250))
} else {
list = list.concat(Array.from({length: Math.floor((length - 1000) / 250)}).map((v, i) => 1000 + (i + 1) * 250))
}
if (length > 10000) {
list = list.concat(Array.from({length: 14}).map((v, i) => 3000 + (i + 1) * 500))
} else {
list = list.concat(Array.from({length: Math.floor((length - 3000) / 500)}).map((v, i) => 3000 + (i + 1) * 500))
}
let d = Math.floor((length - 10000) / 1000)
if (d > 0) {
list = list.concat(Array.from({length: d}).map((v, i) => 10000 + (i + 1) * 1000))
}
list.push(length)
// if (runtimeStore.editDict.length < 50) {
// } else if (runtimeStore.editDict.length < 100) {
// list = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200]
// }
console.log('list', length, list)
columns = list.map(value => {
return {
text: value,
@@ -67,9 +88,8 @@ onMounted(() => {
}
})
columns2 = columns.map(v => {
let value = Math.ceil(length / v.value)
// console.log('v', v.value, value)
let days = Array.from(new Set(list.map(v => Math.ceil(length / v))))
columns2 = days.map(value => {
return {
text: value,
value
@@ -77,6 +97,12 @@ onMounted(() => {
})
})
function confirm() {
runtimeStore.editDict.chapterWordNumber = chapterWordNumber[0]
store.changeDict(runtimeStore.editDict)
router.back()
}
</script>
<template>
@@ -84,7 +110,7 @@ onMounted(() => {
<div class="content">
<div class="dict">
<div class="name">{{ runtimeStore.editDict.name }}</div>
<div class="chapter">每日{{ chapterWordNumber[0] }} 剩余{{ days[0] }}</div>
<div class="chapter">每日{{ chapterWordNumber[0] }} 剩余{{ completeDay[0] }}</div>
<el-progress
:show-text="false"
:percentage="90"
@@ -113,14 +139,14 @@ onMounted(() => {
/>
<Picker
:show-toolbar="false"
:model-value="days"
:model-value="completeDay"
:columns="columns2"
@change="onChange2"
/>
</div>
</div>
</div>
<BaseButton size="large">确认</BaseButton>
<BaseButton size="large" @click="confirm">确认</BaseButton>
</div>
</template>

View File

@@ -18,12 +18,16 @@ defineProps<{
<style scoped lang="scss">
.nav-bar {
box-sizing: border-box;
width: 100%;
height: 60rem;
padding: 0 var(--space);
display: flex;
align-items: center;
justify-content: center;
position: relative;
font-size: 20rem;
//font-weight: bold;
:deep(.back-icon) {
left: var(--space);

View File

@@ -4,7 +4,7 @@ import SlideHorizontal from "@/components/slide/SlideHorizontal.vue";
import SlideItem from "@/components/slide/SlideItem.vue";
import Home from "@/pages/mobile/Home.vue";
import DictListManage from "@/pages/mobile/DictListManage.vue";
import Setting from "@/pages/mobile/Setting.vue";
import My from "@/pages/mobile/My.vue";
import {onMounted} from "vue";
defineOptions({
@@ -30,7 +30,7 @@ onMounted(() => {
<DictListManage/>
</SlideItem>
<SlideItem>
<Setting/>
<My/>
</SlideItem>
</SlideHorizontal>
</div>

View File

@@ -8,6 +8,8 @@ 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";
export const routes: RouteRecordRaw[] = [
{path: '/pc/practice', component: Practice},
@@ -16,11 +18,9 @@ export const routes: RouteRecordRaw[] = [
{path: '/mobile', component: Mobile,},
{path: '/mobile/practice', component: MobilePractice},
{path: '/mobile/dict-detail', component: DictDetail},
{
path: '/mobile/set-dict-plan',
name: 'set-dict-plan',
component: SetDictPlan
},
{path: '/mobile/set-dict-plan', name: 'set-dict-plan', component: SetDictPlan},
{path: '/mobile/setting', component: Setting},
{path: '/mobile/data-manage', component: DataManage},
{path: '/test', component: Test},
{path: '/', redirect: '/pc/practice'},
]

View File

@@ -1,6 +1,6 @@
import {defineStore} from 'pinia'
import {DefaultDict, Dict, DictType, DisplayStatistics, Word} from "../types.ts"
import {chunk, cloneDeep, merge} from "lodash-es";
import {DefaultDict, Dict, DictType, DisplayStatistics, Sort, Word} from "../types.ts"
import {chunk, cloneDeep, merge, reverse, shuffle} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {useRuntimeStore} from "@/stores/runtime.ts";
import * as localforage from "localforage";
@@ -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] ?? []
@@ -216,7 +216,7 @@ export const useBaseStore = defineStore('base', {
if ([DictType.word].includes(this.currentDict.type)) {
if (!this.currentDict.originWords.length) {
let v = await getDictFile(dictResourceUrl)
v = v.slice(0,50)
v = v.slice(0, 50)
v.map(s => {
s.id = nanoid(6)
})
@@ -262,12 +262,46 @@ export const useBaseStore = defineStore('base', {
dict.chapterWordNumber = dict.words.length
dict.chapterWords = [dict.words]
} else {
//TODO 需要和其他需要下载的地方统一
let url = `./dicts/${dict.language}/${dict.type}/${dict.translateLanguage}/${dict.url}`;
if (dict.type === DictType.article) {
if (!dict.articles.length) {
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
dict.articles = cloneDeep(v)
} else {
dict.length = dict.articles.length
}
if (chapterIndex > dict.articles.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
} else {
//如果不是自定义词典并且有url地址才去下载
if (!dict.isCustom && dict.url) {
if (!dict.originWords.length) {
let v = await getDictFile(url)
v.map(s => {
s.id = nanoid(6)
})
dict.originWords = cloneDeep(v)
if (dict.sort === Sort.normal) {
dict.words = cloneDeep(dict.originWords)
} else if (dict.sort === Sort.random) {
dict.words = shuffle(dict.originWords)
} else {
dict.words = reverse(dict.originWords)
}
dict.words.map(v => v.checked = false)
dict.chapterWords = chunk(dict.words, dict.chapterWordNumber)
dict.length = dict.words.length
} else {
dict.length = dict.words.length
}
}
if (chapterIndex > dict.chapterWords.length) {
dict.chapterIndex = 0
dict.wordIndex = 0

View File

@@ -185,4 +185,4 @@ export function getDictFile(url: string) {
resolve(v)
}
})
}
}