fix:The phonetic symbol is displayed incorrectly

& Optimize the feedback page
This commit is contained in:
Zyronon
2025-12-11 00:54:53 +08:00
parent bdad26ef96
commit 577b933855
14 changed files with 299 additions and 176 deletions

6
components.d.ts vendored
View File

@@ -8,6 +8,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
About: typeof import('./src/components/About.vue')['default']
ArticleList: typeof import('./src/components/list/ArticleList.vue')['default']
Audio: typeof import('./src/components/base/Audio.vue')['default']
BackIcon: typeof import('./src/components/BackIcon.vue')['default']
@@ -18,7 +19,7 @@ declare module 'vue' {
BasePage: typeof import('./src/components/BasePage.vue')['default']
BaseTable: typeof import('./src/components/BaseTable.vue')['default']
Book: typeof import('./src/components/Book.vue')['default']
ChannelIcons: typeof import('./src/components/ChannelIcons.vue')['default']
ChannelIcons: typeof import('./src/components/ChannelIcons/ChannelIcons.vue')['default']
Checkbox: typeof import('./src/components/base/checkbox/Checkbox.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
ConflictNotice: typeof import('./src/components/ConflictNotice.vue')['default']
@@ -30,6 +31,7 @@ declare module 'vue' {
Empty: typeof import('./src/components/Empty.vue')['default']
Form: typeof import('./src/components/base/form/Form.vue')['default']
FormItem: typeof import('./src/components/base/form/FormItem.vue')['default']
Github: typeof import('./src/components/ChannelIcons/Github.vue')['default']
Header: typeof import('./src/components/Header.vue')['default']
IconBxVolume: typeof import('~icons/bx/volume')['default']
IconBxVolumeFull: typeof import('~icons/bx/volume-full')['default']
@@ -143,6 +145,7 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView']
Select: typeof import('./src/components/base/select/Select.vue')['default']
SettingDialog: typeof import('./src/components/SettingDialog.vue')['default']
ShareIcon: typeof import('./src/components/ChannelIcons/ShareIcon.vue')['default']
Slide: typeof import('./src/components/Slide.vue')['default']
SlideHorizontal: typeof import('./src/components/slide/SlideHorizontal.vue')['default']
SlideItem: typeof import('./src/components/slide/SlideItem.vue')['default']
@@ -152,6 +155,7 @@ declare module 'vue' {
Toast: typeof import('./src/components/base/toast/Toast.vue')['default']
Tooltip: typeof import('./src/components/base/Tooltip.vue')['default']
VolumeIcon: typeof import('./src/components/icon/VolumeIcon.vue')['default']
WeChat: typeof import('./src/components/ChannelIcons/WeChat.vue')['default']
WordItem: typeof import('./src/components/WordItem.vue')['default']
WordList: typeof import('./src/components/list/WordList.vue')['default']
}

View File

@@ -439,6 +439,10 @@ a {
.center {
@apply flex justify-center items-center;
}
.center-col {
@extend .center;
@apply flex-col;
}
.card {
@apply rounded-xl p-4 mb-8 shadow-lg box-border relative;

40
src/components/About.vue Normal file
View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import { GITHUB } from "@/config/env.ts";
import ChannelIcons from "@/components/ChannelIcons/ChannelIcons.vue";
import WeChat from "@/components/ChannelIcons/WeChat.vue";
</script>
<template>
<h1>Type Words</h1>
<div class="w-120">
<p class="text-xl">
感谢使用本项目本项目是开源项目如果觉得有帮助请在 GitHub 点个 Star您的支持是我持续改进的动力
</p>
<p>
GitHub地址<a :href="GITHUB" target="_blank">{{ GITHUB }}</a>
</p>
<div class="flex flex-col gap-2 mt-20">
<div class="flex items-center">
微信反馈<WeChat/>
</div>
<div class="">
工单反馈<a :href="`https://v.wjx.cn/vm/ev0W7fv.aspx#`"
target="_blank">https://v.wjx.cn/vm/ev0W7fv.aspx#</a>
</div>
<div class="flex items-center">
其他渠道
<ChannelIcons type="horizontal"
:share="false"
:wechat="false"
:github="false"
/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { GITHUB } from "@/config/env";
import BaseIcon from "@/components/BaseIcon.vue";
import { defineAsyncComponent } from "vue";
import ShareIcon from "@/components/ChannelIcons/ShareIcon.vue";
import WeChat from "@/components/ChannelIcons/WeChat.vue";
import Github from "@/components/ChannelIcons/Github.vue";
withDefaults(defineProps<{
type?: 'vertical' | 'horizontal',
share?: boolean,
wechat?: boolean,
github?: boolean,
}>(), {
type: "vertical",
share: true,
github: true,
wechat: true
})
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
let showXhsDialog = $ref(false)
let showQQDialog = $ref(false)
</script>
<template>
<div class="center" :class="type === 'vertical' ? 'flex-col gap-1' : 'gap-4'">
<ShareIcon v-if="share"/>
<Github v-if="github"/>
<WeChat v-if="wechat"/>
<BaseIcon title="QQ群" @click="showQQDialog = true">
<IconUiwQq class="color-red"/>
</BaseIcon>
<BaseIcon title="小红书" @click="showXhsDialog = true">
<IconSimpleIconsXiaohongshu class="color-red-500"/>
</BaseIcon>
<a href="https://x.com/typewords2" target="_blank" rel="noreferrer" aria-label="关注我的 X 账户 typewords2">
<BaseIcon title="推特">
<IconRiTwitterFill class="color-blue"/>
</BaseIcon>
</a>
<a href="mailto:zyronon@163.com" target="_blank" rel="noreferrer" aria-label="发送邮件到 zyronon@163.com">
<BaseIcon title="邮箱">
<IconMaterialSymbolsMail class="color-blue"/>
</BaseIcon>
</a>
</div>
<Dialog v-model="showXhsDialog" title="小红书">
<div class="w-120 p-6 pt-0">
<div class="mb-4">
关注小红书后您可以获得开发团队的最新动态和更新内容反馈您的使用体验和建议帮助我们改进产品同时也能够及时了解我们的最新动态和更新内容
</div>
<div class="text-center">
<img src="/xhs.png" alt="小红书二维码" class="w-70 rounded-xl shadow-lg">
</div>
</div>
</Dialog>
<Dialog v-model="showQQDialog" title="QQ 交流群">
<div class="w-120 p-6 pt-0">
<div class="mb-4">
<span>加入我们的用户社群后您可以与我们的开发团队进行沟通分享您的使用体验和建议帮助我们改进产品同时也能够及时了解我们的最新动态和更新内容</span>
</div>
<div class="text-center">
<img src="/qq.jpg" alt="QQ群二维码" class="w-70 rounded-xl shadow-lg">
</div>
</div>
</Dialog>
</template>
<style scoped lang="scss">
.stat-card {
@apply text-center bg-gray-900/30 py-4 rounded-2xl;
}
</style>

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
import { GITHUB } from "@/config/env.ts";
import BaseIcon from "@/components/BaseIcon.vue";
</script>
<template>
<a :href="GITHUB" target="_blank" rel="noreferrer" aria-label="GITHUB 项目地址"
class="color-[--color-reverse-black] github">
<BaseIcon title="Github">
<IconSimpleIconsGithub/>
</BaseIcon>
</a>
</template>
<style scoped lang="scss">
</style>

View File

@@ -1,32 +1,31 @@
<script setup lang="ts">
import { APP_NAME, GITHUB, LIB_JS_URL, Origin } from "@/config/env.ts";
import { APP_NAME, LIB_JS_URL, Origin } from "@/config/env.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import { defineAsyncComponent, watch } from "vue";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
import { usePracticeStore } from "@/stores/practice.ts";
import { useBaseStore } from "@/stores/base.ts";
import { loadJsLib, msToHourMinute } from "@/utils";
import dayjs from "dayjs";
import Toast from "@/components/base/toast/Toast.ts";
import Toass from "../base/toast/Toast.ts";
import { useUserStore } from "@/stores/user.ts";
import Progress from "@/components/base/Progress.vue";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
import { defineAsyncComponent } from "vue";
const practiceStore = usePracticeStore()
const baseStore = useBaseStore()
const userStore = useUserStore()
let showWechatDialog = $ref(false)
let showXhsDialog = $ref(false)
let showQQDialog = $ref(false)
let showShareDialog = $ref(false)
let loading1 = $ref(false)
let loading2 = $ref(false)
let posterEl = $ref<HTMLDivElement | null>(null)
let imgIndex = $ref(Math.floor(Math.random() * 10))
//
const studyStats = $computed(() => {
return {
total: practiceStore.total,
newWords: practiceStore.newWordNumber,
@@ -39,28 +38,22 @@ const studyStats = $computed(() => {
}
})
//
watch(() => showShareDialog, (newVal) => {
if (newVal) {
}
})
//
async function copyImageToClipboard() {
try {
loading1 = true
const snapdom = await loadJsLib('snapdom',LIB_JS_URL.SNAPDOM);
const blob = await snapdom.toBlob(posterEl, {scale: 2, type: 'png'})
const snapdom = await loadJsLib('snapdom', LIB_JS_URL.SNAPDOM);
const blob = await snapdom.toBlob(posterEl, { scale: 2, type: 'png' })
if (!blob) throw new Error('capture failed')
if (navigator.clipboard && (window as any).ClipboardItem) {
await navigator.clipboard.write([new (window as any).ClipboardItem({[blob.type || 'image/png']: blob})])
Toast.success('图片已复制到剪贴板!')
await navigator.clipboard.write([new (window as any).ClipboardItem({ [blob.type || 'image/png']: blob })])
Toass.success('图片已复制到剪贴板!')
} else {
await downloadImage()
}
} catch (error) {
Toast.error('复制失败!')
Toass.error('复制失败!')
await downloadImage()
} finally {
loading1 = false
@@ -70,13 +63,11 @@ async function copyImageToClipboard() {
//
async function downloadImage() {
loading2 = true
const snapdom = await loadJsLib('snapdom',LIB_JS_URL.SNAPDOM);
snapdom.download(posterEl, {scale: 2})
const snapdom = await loadJsLib('snapdom', LIB_JS_URL.SNAPDOM);
snapdom.download(posterEl, { scale: 2 })
loading2 = false
}
let imgIndex = $ref(Math.floor(Math.random() * 10))
//
function changeBackground() {
const newIndex = Math.floor(Math.random() * 9) // 0-8
@@ -91,79 +82,47 @@ const studyProgress = $computed(() => {
const sentence = $computed(() => {
let list = [
{en: 'Actions speak louder than words.', cn: '行动胜于言语'},
{en: 'Keep going, never give up!', cn: '坚持就是胜利'},
{en: 'Where there\'s a will, there\'s a way.', cn: '有志者事竟成'},
{en: 'Every cloud has a silver lining.', cn: '黑暗中总有一线光明'},
{en: 'Time heals all wounds.', cn: '时间能治愈一切创伤'},
{en: 'Never say die.', cn: '永不言败'},
{en: 'The best is yet to come.', cn: '最好的尚未到来'},
{en: 'Believe you can and you\'re halfway there.', cn: '相信你自己,你已经成功了一半'},
{en: 'No pain, no gain.', cn: '没有付出就没有收获'},
{en: 'Dream big and dare to fail.', cn: '大胆梦想,勇于失败'},
{en: 'Home is where the heart is.', cn: '心在哪里,家就在哪里'},
{en: 'Knowledge is power.', cn: '知识就是力量'},
{en: 'Practice makes perfect.', cn: '熟能生巧'},
{en: 'When in Rome, do as the Romans do.', cn: '入乡随俗'},
{en: 'Just do it.', cn: '只管去做'},
{en: 'So far, so good.', cn: '到目前为止,一切还好'},
{en: 'The early bird catches the worm.', cn: '早起的鸟儿有虫吃'},
{en: 'Every day is a new beginning.', cn: '每一天都是新的开始'},
{en: 'Success is a journey, not a destination.', cn: '成功是旅程,不是终点'},
{en: 'Your only limit is your mind.', cn: '你唯一的限制是你的思维'},
{en: 'A friend in need is a friend indeed.', cn: '患难见真情'},
{en: 'Silence is golden.', cn: '沉默是金'},
{en: 'Let bygones be bygones.', cn: '让过去的成为过去'},
{en: 'Keep calm and carry on.', cn: '保持冷静,继续前进'},
{en: 'Live and learn.', cn: '活到老,学到老'},
{en: 'Mistakes are proof that you are trying.', cn: '错误证明你在努力尝试'},
{en: 'Better late than never.', cn: '迟做总比不做好'},
{en: 'Be the change you wish to see in the world.', cn: '成为你希望在世界上看到的改变'},
{en: 'The journey of a thousand miles begins with a single step.', cn: '千里之行,始于足下'},
{en: 'When one door closes, another opens.', cn: '当一扇门关闭时,另一扇会打开'},
{ en: 'Actions speak louder than words.', cn: '行动胜于言语' },
{ en: 'Keep going, never give up!', cn: '坚持就是胜利' },
{ en: 'Where there\'s a will, there\'s a way.', cn: '有志者事竟成' },
{ en: 'Every cloud has a silver lining.', cn: '黑暗中总有一线光明' },
{ en: 'Time heals all wounds.', cn: '时间能治愈一切创伤' },
{ en: 'Never say die.', cn: '永不言败' },
{ en: 'The best is yet to come.', cn: '最好的尚未到来' },
{ en: 'Believe you can and you\'re halfway there.', cn: '相信你自己,你已经成功了一半' },
{ en: 'No pain, no gain.', cn: '没有付出就没有收获' },
{ en: 'Dream big and dare to fail.', cn: '大胆梦想,勇于失败' },
{ en: 'Home is where the heart is.', cn: '心在哪里,家就在哪里' },
{ en: 'Knowledge is power.', cn: '知识就是力量' },
{ en: 'Practice makes perfect.', cn: '熟能生巧' },
{ en: 'When in Rome, do as the Romans do.', cn: '入乡随俗' },
{ en: 'Just do it.', cn: '只管去做' },
{ en: 'So far, so good.', cn: '到目前为止,一切还好' },
{ en: 'The early bird catches the worm.', cn: '早起的鸟儿有虫吃' },
{ en: 'Every day is a new beginning.', cn: '每一天都是新的开始' },
{ en: 'Success is a journey, not a destination.', cn: '成功是旅程,不是终点' },
{ en: 'Your only limit is your mind.', cn: '你唯一的限制是你的思维' },
{ en: 'A friend in need is a friend indeed.', cn: '患难见真情' },
{ en: 'Silence is golden.', cn: '沉默是金' },
{ en: 'Let bygones be bygones.', cn: '让过去的成为过去' },
{ en: 'Keep calm and carry on.', cn: '保持冷静,继续前进' },
{ en: 'Live and learn.', cn: '活到老,学到老' },
{ en: 'Mistakes are proof that you are trying.', cn: '错误证明你在努力尝试' },
{ en: 'Better late than never.', cn: '迟做总比不做好' },
{ en: 'Be the change you wish to see in the world.', cn: '成为你希望在世界上看到的改变' },
{ en: 'The journey of a thousand miles begins with a single step.', cn: '千里之行,始于足下' },
{ en: 'When one door closes, another opens.', cn: '当一扇门关闭时,另一扇会打开' },
]
return list[Math.floor(Math.random() * list.length)]
})
</script>
<template>
<div class="flex-col center gap-1">
<!-- 分享学习总结按钮 -->
<BaseIcon @click="showShareDialog = true"
class="bounce">
<IconFluentShare20Regular class="text-blue-500 hover:text-blue-600"/>
</BaseIcon>
<a :href="GITHUB" target="_blank" rel="noreferrer" aria-label="GITHUB 项目地址"
class="color-[--color-reverse-black]">
<BaseIcon>
<IconSimpleIconsGithub/>
</BaseIcon>
</a>
<BaseIcon @click="showWechatDialog = true">
<IconSimpleIconsWechat class="color-green-500"/>
</BaseIcon>
<BaseIcon @click="showQQDialog = true">
<IconUiwQq class="color-red"/>
</BaseIcon>
<BaseIcon @click="showXhsDialog = true">
<IconSimpleIconsXiaohongshu class="color-red-500"/>
</BaseIcon>
<a href="https://x.com/typewords2" target="_blank" rel="noreferrer" aria-label="关注我的 X 账户 typewords2">
<BaseIcon>
<IconRiTwitterFill class="color-blue"/>
</BaseIcon>
</a>
<a href="mailto:zyronon@163.com" target="_blank" rel="noreferrer" aria-label="发送邮件到 zyronon@163.com">
<BaseIcon>
<IconMaterialSymbolsMail class="color-blue"/>
</BaseIcon>
</a>
</div>
<!-- 分享学习总结按钮 -->
<BaseIcon @click="showShareDialog = true"
class="bounce">
<IconFluentShare20Regular class="text-blue-500 hover:text-blue-600"/>
</BaseIcon>
<!-- 学习总结分享图片生成对话框 -->
<Dialog v-model="showShareDialog" title="分享">
@@ -229,7 +188,7 @@ const sentence = $computed(() => {
<div class="text-base ">{{ Origin }}</div>
<div class="text-xs ">一次敲击一点进步开源单词学习工具</div>
</div>
<img src="/imgs/qr.png" class="w-20 w-20 rounded-md overflow-hidden" alt="">
<img :src="`/imgs/qr.png`" class="w-20 w-20 rounded-md overflow-hidden" alt="">
</div>
</div>
</div>
@@ -288,43 +247,8 @@ const sentence = $computed(() => {
</div>
</div>
</Dialog>
<Dialog v-model="showWechatDialog" title="Type Words 交流群">
<div class="w-120 p-6 pt-0">
<div class="mb-4">
加入我们的用户社群后您可以与我们的开发团队进行沟通分享您的使用体验和建议帮助我们改进产品同时也能够及时了解我们的最新动态和更新内容
</div>
<div class="text-center">
<img src="/wechat.png" alt="微信群二维码" class="w-60 rounded-lg">
</div>
</div>
</Dialog>
<Dialog v-model="showXhsDialog" title="小红书">
<div class="w-120 p-6 pt-0">
<div class="mb-4">
关注小红书后您可以获得开发团队的最新动态和更新内容反馈您的使用体验和建议帮助我们改进产品同时也能够及时了解我们的最新动态和更新内容
</div>
<div class="text-center">
<img src="/xhs.png" alt="小红书二维码" class="w-60 rounded-lg">
</div>
</div>
</Dialog>
<Dialog v-model="showQQDialog" title="QQ 交流群">
<div class="w-120 p-6 pt-0">
<div class="mb-4">
<span>加入我们的用户社群后您可以与我们的开发团队进行沟通分享您的使用体验和建议帮助我们改进产品同时也能够及时了解我们的最新动态和更新内容</span>
</div>
<div class="text-center">
<img src="/qq.jpg" alt="QQ群二维码" class="w-60 rounded-lg">
</div>
</div>
</Dialog>
</template>
<style scoped lang="scss">
.stat-card {
@apply text-center bg-gray-900/30 py-4 rounded-2xl;
}
</style>
</style>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
let showWechatDialog = $ref(false)
</script>
<template>
<BaseIcon title="微信群" @click="showWechatDialog = true">
<IconSimpleIconsWechat class="color-green-500"/>
</BaseIcon>
<Dialog v-model="showWechatDialog" title="微信群">
<div class="w-120 p-6 pt-0">
<div class="mb-4">
加入我们的用户社群后您可以与我们的开发团队进行沟通分享您的使用体验和建议帮助我们改进产品同时也能够及时了解我们的最新动态和更新内容
</div>
<div class="text-center">
<img src="/wechat.png" alt="微信群二维码" class="w-70 rounded-xl shadow-lg">
</div>
</div>
</Dialog>
</template>
<style scoped lang="scss">
</style>

17
src/pages/feedback.vue Normal file
View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import BasePage from "@/components/BasePage.vue";
import About from "@/components/About.vue";
</script>
<template>
<BasePage>
<div class="card center-col">
<About/>
</div>
</BasePage>
</template>
<style scoped lang="scss">
</style>

View File

@@ -45,7 +45,7 @@ function goHome() {
<span v-if="settingStore.sideExpand">设置</span>
<div class="red-point" :class="!settingStore.sideExpand && 'top-1 right-0'" v-if="runtimeStore.isNew"></div>
</div>
<div class="row" @click="jump2Feedback">
<div class="row" @click="router.push('/feedback')">
<IconFluentCommentEdit20Regular/>
<span v-if="settingStore.sideExpand">反馈</span>
</div>

View File

@@ -4,6 +4,14 @@
<template>
<div>
<div class="log-item">
<div class="mb-2">
<div>
<div>日期2025/12/11</div>
<div>内容修复音标显示错误问题优化反馈页面</div>
</div>
</div>
</div>
<div class="log-item">
<div class="mb-2">
<div>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {nextTick, onMounted, ref, watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {getAudioFileUrl, usePlayAudio} from "@/hooks/sound.ts";
import {getShortcutKey, useEventListener} from "@/hooks/event.ts";
import { nextTick, onMounted, ref, watch } from "vue";
import { useSettingStore } from "@/stores/setting.ts";
import { getAudioFileUrl, usePlayAudio } from "@/hooks/sound.ts";
import { getShortcutKey, useEventListener } from "@/hooks/event.ts";
import {
checkAndUpgradeSaveDict,
checkAndUpgradeSaveSetting,
@@ -11,11 +11,11 @@ import {
shakeCommonDict,
sleep
} from "@/utils";
import {DefaultShortcutKeyMap, ShortcutKey, WordPracticeMode} from "@/types/types.ts";
import { DefaultShortcutKeyMap, ShortcutKey, WordPracticeMode } from "@/types/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {saveAs} from "file-saver";
import { useBaseStore } from "@/stores/base.ts";
import { saveAs } from "file-saver";
import {
APP_NAME, APP_VERSION, EMAIL,
EXPORT_DATA_KEY, GITHUB, Host, LIB_JS_URL,
@@ -27,7 +27,7 @@ import {
import dayjs from "dayjs";
import BasePage from "@/components/BasePage.vue";
import Toast from '@/components/base/toast/Toast.ts'
import {Option, Select} from "@/components/base/select";
import { Option, Select } from "@/components/base/select";
import Switch from "@/components/base/Switch.vue";
import Slider from "@/components/base/Slider.vue";
import RadioGroup from "@/components/base/radio/RadioGroup.vue";
@@ -36,18 +36,19 @@ import InputNumber from "@/components/base/InputNumber.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import Textarea from "@/components/base/Textarea.vue";
import SettingItem from "@/pages/setting/SettingItem.vue";
import {get, set} from "idb-keyval";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useUserStore} from "@/stores/user.ts";
import {useExport} from "@/hooks/export.ts";
import { get, set } from "idb-keyval";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { useUserStore } from "@/stores/user.ts";
import { useExport } from "@/hooks/export.ts";
import MigrateDialog from "@/components/MigrateDialog.vue";
import Log from "@/pages/setting/Log.vue";
import About from "@/components/About.vue";
const emit = defineEmits<{
toggleDisabledDialogEscKey: [val: boolean]
}>()
const tabIndex = $ref(3)
const tabIndex = $ref(4)
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const store = useBaseStore()
@@ -107,7 +108,7 @@ useEventListener('keydown', (e: KeyboardEvent) => {
} else {
// 忽略单独的修饰键
if (shortcutKey === 'Ctrl+' || shortcutKey === 'Alt+' || shortcutKey === 'Shift+' ||
e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift') {
e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift') {
return;
}
@@ -179,7 +180,7 @@ function resetShortcutKeyMap() {
let importLoading = $ref(false)
const {loading: exportLoading, exportData} = useExport()
const { loading: exportLoading, exportData } = useExport()
function importJson(str: string, notice: boolean = true) {
importLoading = true
@@ -271,7 +272,7 @@ async function importData(e) {
if (!entry) continue;
const blob = await entry.async("blob");
const id = filename.replace(/^mp3\//, "").replace(/\.mp3$/, "");
records.push({id, file: blob});
records.push({ id, file: blob });
}
}
await set(LOCAL_FILE_KEY, records);
@@ -310,14 +311,16 @@ function transferOk() {
<div class="flex flex-1 overflow-hidden gap-4">
<div class="left">
<div class="tabs">
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">
<IconFluentKeyboardLayoutFloat20Regular width="20"/>
<span>快捷键设置</span>
</div>
<div class="tab" :class="tabIndex === 4 && 'active'" @click="tabIndex = 4">
<IconFluentDatabasePerson20Regular width="20"/>
<span>数据管理</span>
</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">
<IconFluentKeyboardLayoutFloat20Regular width="20"/>
<span>快捷键设置</span>
</div>
<div class="tab" :class="tabIndex === 5 && 'active'" @click="()=>{
tabIndex = 5
runtimeStore.isNew = false
@@ -349,7 +352,7 @@ function transferOk() {
<input ref="shortcutInput" :value="item[1]?item[1]:'未设置快捷键'" readonly type="text"
@blur="handleInputBlur">
<span @click.stop="editShortcutKey = ''">按键盘进行设置<span
class="text-red!">设置完成点击这里</span></span>
class="text-red!">设置完成点击这里</span></span>
</div>
<div v-else>
<div v-if="item[1]">{{ item[1] }}</div>
@@ -368,19 +371,20 @@ function transferOk() {
<div v-if="tabIndex === 4">
<div>
目前用户的所有数据
<b class="text-red">保存在本地</b>如果您需要在不同的设备浏览器或者其他非官方部署上使用 {{ APP_NAME }}
您需要手动进行数据同步和保存
所有用户数据
<b class="text-red">保存在本地浏览器中</b>如果您需要在不同的设备浏览器上使用 {{ APP_NAME }}
您需要手动进行数据导出和导入
</div>
<BaseButton :loading="exportLoading" class="mt-3" @click="exportData()">导出数据</BaseButton>
<BaseButton :loading="exportLoading" size="large" class="mt-3" @click="exportData()">导出数据备份(ZIP)</BaseButton>
<div class="text-gray text-sm mt-2">💾 导出的ZIP文件包含所有学习数据可在其他设备上导入恢复</div>
<div class="line my-3"></div>
<div class="line mt-15 mb-3"></div>
<div>请注意导入数据<b class="text-red"> 完全覆盖 </b>当前所有数据请谨慎操作执行导入操作时会先自动备份当前数据到您的电脑中供您随时恢复
<div>请注意导入数据将<b class="text-red"> 完全覆盖 </b>当前所有数据请谨慎操作执行导入操作时会先自动备份当前数据到您的电脑中供您随时恢复
</div>
<div class="flex gap-space mt-3">
<div class="import hvr-grow">
<BaseButton :loading="importLoading">导入数据</BaseButton>
<BaseButton size="large" :loading="importLoading">导入数据恢复</BaseButton>
<input type="file"
accept="application/json,.zip,application/zip"
@change="importData">
@@ -401,16 +405,7 @@ function transferOk() {
<Log v-if="tabIndex === 5"/>
<div v-if="tabIndex === 6" class="center flex-col">
<h1>Type Words</h1>
<p class="w-100 text-xl">
感谢使用本项目本项目是开源项目如果觉得有帮助请在 GitHub 点个 Star您的支持是我持续改进的动力
</p>
<p>
GitHub地址<a :href="GITHUB" target="_blank">{{ GITHUB }}</a>
</p>
<p>
作者邮箱<a :href="`mailto:${EMAIL}`">{{ EMAIL }}</a>
</p>
<About/>
<div class="text-md color-gray mt-10">
Build {{ gitLastCommitHash }}
</div>
@@ -421,8 +416,8 @@ function transferOk() {
</BasePage>
<MigrateDialog
v-model="showTransfer"
@ok="transferOk"
v-model="showTransfer"
@ok="transferOk"
/>
</template>

View File

@@ -11,7 +11,7 @@ import { defineAsyncComponent, inject, watch } from "vue";
import isoWeek from 'dayjs/plugin/isoWeek'
import { msToHourMinute } from "@/utils";
import Progress from "@/components/base/Progress.vue";
import ChannelIcons from "@/components/ChannelIcons.vue";
import ChannelIcons from "@/components/ChannelIcons/ChannelIcons.vue";
dayjs.extend(isoWeek)
dayjs.extend(isBetween);

View File

@@ -412,11 +412,11 @@ useEvents([
<div class="flex gap-1 mt-30">
<div class="phonetic"
:class="!(!settingStore.dictation || showFullWord || showWordResult) && 'word-shadow'"
v-if="settingStore.soundType === 'us' && word.phonetic0">[{{ word.phonetic0 }}]
v-if="settingStore.soundType === 'uk' && word.phonetic0">[{{ word.phonetic0 }}]
</div>
<div class="phonetic"
:class="((settingStore.dictation || [WordPracticeType.Spell,WordPracticeType.Listen,WordPracticeType.Dictation].includes(settingStore.wordPracticeType)) && !showFullWord && !showWordResult) && 'word-shadow'"
v-if="settingStore.soundType === 'uk' && word.phonetic1">[{{ word.phonetic1 }}]
v-if="settingStore.soundType === 'us' && word.phonetic1">[{{ word.phonetic1 }}]
</div>
<VolumeIcon
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"

View File

@@ -14,6 +14,7 @@ import Setting from "@/pages/setting/Setting.vue";
import Login from "@/pages/user/login.vue";
import User from "@/pages/user/User.vue";
import VipIntro from "@/pages/user/VipIntro.vue";
import Feedback from "@/pages/feedback.vue";
// import { useAuthStore } from "@/stores/user.ts";
export const routes: RouteRecordRaw[] = [
@@ -36,10 +37,14 @@ export const routes: RouteRecordRaw[] = [
{path: 'study-article', redirect: '/articles'},
{path: 'book-detail', component: BookDetail},
{path: 'book-list', component: BookList},
{path: 'setting', component: Setting},
{path: 'login', component: Login},
{path: 'user', component: User},
{path: 'vip', component: VipIntro},
{path: 'setting', component: Setting},
{path: 'feedback', component: Feedback},
]
},
{path: '/batch-edit-article', component: () => import("@/pages/article/BatchEditArticlePage.vue")},