fix:The phonetic symbol is displayed incorrectly
& Optimize the feedback page
This commit is contained in:
6
components.d.ts
vendored
6
components.d.ts
vendored
@@ -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']
|
||||
}
|
||||
|
||||
@@ -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
40
src/components/About.vue
Normal 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>
|
||||
82
src/components/ChannelIcons/ChannelIcons.vue
Normal file
82
src/components/ChannelIcons/ChannelIcons.vue
Normal 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>
|
||||
18
src/components/ChannelIcons/Github.vue
Normal file
18
src/components/ChannelIcons/Github.vue
Normal 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>
|
||||
@@ -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>
|
||||
26
src/components/ChannelIcons/WeChat.vue
Normal file
26
src/components/ChannelIcons/WeChat.vue
Normal 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
17
src/pages/feedback.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]})`"
|
||||
|
||||
@@ -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")},
|
||||
|
||||
Reference in New Issue
Block a user