diff --git a/package.json b/package.json
index 6e798bf4..236ce827 100644
--- a/package.json
+++ b/package.json
@@ -25,17 +25,16 @@
"file-saver": "^2.0.5",
"idb-keyval": "^6.2.2",
"libarchive-wasm": "^1.2.0",
+ "md5": "^2.2.1",
"mitt": "^3.0.1",
"nanoid": "^5.1.5",
"pinia": "^3.0.3",
"string-comparison": "^1.3.0",
"vue": "^3.5.17",
"vue-router": "^4.5.1",
- "vue-virtual-scroller": "2.0.0-beta.8",
- "md5": "^2.2.1"
+ "vue-virtual-scroller": "2.0.0-beta.8"
},
"devDependencies": {
- "@types/md5": "^2.1.33",
"@alicloud/pop-core": "^1.8.0",
"@iconify-json/basil": "^1.2.4",
"@iconify-json/bi": "^1.2.6",
@@ -67,6 +66,7 @@
"@iconify-json/uil": "^1.2.3",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
+ "@types/md5": "^2.1.33",
"@unocss/postcss": "^66.4.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vitejs/plugin-vue-jsx": "^5.0.1",
@@ -86,10 +86,11 @@
"unplugin-icons": "^22.2.0",
"unplugin-vue-components": "^29.0.0",
"unplugin-vue-macros": "^2.14.5",
- "vite-plugin-externals": "^0.6.2",
"vite": "^7.0.3",
+ "vite-plugin-externals": "^0.6.2",
"vue-tsc": "^3.0.1",
- "xlsx": "^0.18.5"
+ "xlsx": "^0.18.5",
+ "sitemap": "^8.0.0"
},
"config": {
"commitizen": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index db316201..0a5597b6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -44,6 +44,9 @@ importers:
pinia:
specifier: ^3.0.3
version: 3.0.3(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2))
+ sitemap:
+ specifier: ^8.0.0
+ version: 8.0.0
string-comparison:
specifier: ^1.3.0
version: 1.3.0
@@ -953,12 +956,18 @@ packages:
'@types/md5@2.3.5':
resolution: {integrity: sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==}
+ '@types/node@17.0.45':
+ resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
+
'@types/node@20.19.11':
resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==}
'@types/node@24.3.0':
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
+ '@types/sax@1.2.7':
+ resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
+
'@types/web-bluetooth@0.0.16':
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
@@ -1404,6 +1413,9 @@ packages:
archy@1.0.0:
resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -3293,6 +3305,11 @@ packages:
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
engines: {node: '>=18'}
+ sitemap@8.0.0:
+ resolution: {integrity: sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==}
+ engines: {node: '>=14.0.0', npm: '>=6.0.0'}
+ hasBin: true
+
snapdragon-node@2.1.1:
resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
engines: {node: '>=0.10.0'}
@@ -4595,6 +4612,8 @@ snapshots:
'@types/md5@2.3.5': {}
+ '@types/node@17.0.45': {}
+
'@types/node@20.19.11':
dependencies:
undici-types: 6.21.0
@@ -4602,7 +4621,10 @@ snapshots:
'@types/node@24.3.0':
dependencies:
undici-types: 7.10.0
- optional: true
+
+ '@types/sax@1.2.7':
+ dependencies:
+ '@types/node': 24.3.0
'@types/web-bluetooth@0.0.16':
optional: true
@@ -5278,6 +5300,8 @@ snapshots:
archy@1.0.0: {}
+ arg@5.0.2: {}
+
argparse@2.0.1:
optional: true
@@ -7340,6 +7364,13 @@ snapshots:
mrmime: 2.0.1
totalist: 3.0.1
+ sitemap@8.0.0:
+ dependencies:
+ '@types/node': 17.0.45
+ '@types/sax': 1.2.7
+ arg: 5.0.2
+ sax: 1.4.1
+
snapdragon-node@2.1.1:
dependencies:
define-property: 1.0.0
@@ -7599,8 +7630,7 @@ snapshots:
undici-types@6.21.0: {}
- undici-types@7.10.0:
- optional: true
+ undici-types@7.10.0: {}
unescape@1.0.1:
dependencies:
diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js
new file mode 100644
index 00000000..e1fe58e7
--- /dev/null
+++ b/scripts/generate-sitemap.js
@@ -0,0 +1,41 @@
+const {SitemapStream, streamToPromise} = require('sitemap')
+const {createWriteStream} = require('fs')
+const {resolve} = require('path')
+
+// 你的网站域名
+const SITE_URL = 'https://yourdomain.com'
+
+// 静态路由(首页、练习页等)
+const staticPages = [
+ {url: '/', changefreq: 'daily', priority: 1.0},
+ {url: '/word', changefreq: 'daily', priority: 0.9},
+ {url: '/article', changefreq: 'daily', priority: 0.9},
+ {url: '/setting', changefreq: 'monthly', priority: 0.3},
+]
+
+// 动态页面示例(假设你有文章或单词数据)
+const dynamicPages = [
+ {url: '/article/vue-seo', changefreq: 'weekly', priority: 0.8},
+ {url: '/article/js-tips', changefreq: 'weekly', priority: 0.8},
+ // 如果文章很多,可以用 JSON / API 自动生成数组
+]
+
+async function generateSitemap() {
+ const sitemap = new SitemapStream({hostname: SITE_URL})
+ const writeStream = createWriteStream(resolve(__dirname, '../dist/sitemap.xml'))
+
+ sitemap.pipe(writeStream)
+
+ // 添加静态页
+ staticPages.forEach(page => sitemap.write(page))
+
+ // 添加动态页
+ dynamicPages.forEach(page => sitemap.write(page))
+
+ sitemap.end()
+
+ await streamToPromise(sitemap)
+ console.log('✅ sitemap.xml 已生成在 dist 目录')
+}
+
+generateSitemap()
diff --git a/src/pages/pc/article/ArticleHomePage.vue b/src/pages/pc/article/ArticleHomePage.vue
index be76e642..059017a0 100644
--- a/src/pages/pc/article/ArticleHomePage.vue
+++ b/src/pages/pc/article/ArticleHomePage.vue
@@ -43,7 +43,7 @@ function startStudy() {
custom: base.sbook.custom,
complete: base.sbook.complete,
})
- nav('/study-article', {name: store.sbook.name, id: store.sbook.id})
+ nav('/study-article/' + store.sbook.id)
} else {
window.umami?.track('no-book')
Toast.warning('请先选择一本书籍')
@@ -128,7 +128,7 @@ async function goBookDetail(val: DictResource) {
{{ isMultiple ? '取消' : '管理书籍' }}
- 创建个人书籍
+ 创建个人书籍
diff --git a/src/pages/pc/article/BookDetail.vue b/src/pages/pc/article/BookDetail.vue
index 07876965..7d0f7943 100644
--- a/src/pages/pc/article/BookDetail.vue
+++ b/src/pages/pc/article/BookDetail.vue
@@ -146,7 +146,7 @@ const {
-
+
{{ runtimeStore.editDict.id ? '修改' : '创建' }}书籍
diff --git a/src/pages/pc/article/StudyArticle.vue b/src/pages/pc/article/StudyArticle.vue
index f1510933..b6284c57 100644
--- a/src/pages/pc/article/StudyArticle.vue
+++ b/src/pages/pc/article/StudyArticle.vue
@@ -10,7 +10,7 @@ import useTheme from "@/hooks/theme.ts";
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
import {_getDictDataByUrl, cloneDeep} from "@/utils";
import {usePracticeStore} from "@/stores/practice.ts";
-import {useArticleOptions} from "@/hooks/dict.ts";
+import {getCurrentStudyWord, useArticleOptions} from "@/hooks/dict.ts";
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
import {getDefaultArticle, getDefaultDict} from "@/types/func.ts";
import TypingArticle from "@/pages/pc/article/components/TypingArticle.vue";
@@ -20,7 +20,7 @@ import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import EditSingleArticleModal from "@/pages/pc/article/components/EditSingleArticleModal.vue";
import Tooltip from "@/pages/pc/components/base/Tooltip.vue";
import ConflictNotice from "@/pages/pc/components/ConflictNotice.vue";
-import {enArticle} from "@/assets/dictionary.ts";
+import {dictionaryResources, enArticle} from "@/assets/dictionary.ts";
import {useRoute, useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
@@ -39,7 +39,7 @@ let articleData = $ref({
})
let showEditArticle = $ref(false)
let typingArticleRef = $ref
()
-let loading = $ref(true)
+let loading = $ref(false)
let editArticle = $ref(getDefaultArticle())
function write() {
@@ -87,61 +87,51 @@ function next() {
const router = useRouter()
const route = useRoute()
-const runtimeStore = useRuntimeStore()
-
-watch(() => store.load, (n) => {
- if (n && loading && runtimeStore.editDict.id) {
- console.log('load好了开始加载')
- store.changeBook(runtimeStore.editDict)
- articleData.list = cloneDeep(store.sbook.articles)
- getCurrentPractice()
- loading = false
- }
-},{immediate: true})
-
-useStartKeyboardEventListener()
-useDisableEventListener(() => loading)
-
-function init() {
- if (store.sbook?.articles?.length) {
- articleData.list = cloneDeep(store.sbook.articles)
- getCurrentPractice()
- loading = false
- } else {
- let dictName = route.query.name
- let dictId = route.query.id
- //如果url里有词典id或name,那么直接请求词典数据,并加到bookList里面进行学习
- //todo 这里要处理自定义词典的问题
- if (dictName || dictId) {
- let dictResource = getDefaultDict()
- if (dictId) dictResource = enArticle.find(v => v.id === dictId) as Dict
- else if (dictName) dictResource = enArticle.find(v => v.name === dictName) as Dict
- if (dictResource.id) {
- loading = true
- _getDictDataByUrl(dictResource, DictType.article).then(r => {
- if (!r.articles.length) {
- router.push('/article')
- return Toast.warning('没有文章可学习!')
- }
- runtimeStore.editDict = r
- if (store.load) {
- console.log('直接加载')
- store.changeBook(r)
- articleData.list = cloneDeep(store.sbook.articles)
- getCurrentPractice()
- loading = false
- }
- })
- } else {
+async function init() {
+ console.log('load好了开始加载')
+ let dict = getDefaultDict()
+ let dictId = route.params.id
+ if (dictId) {
+ //先在自己的词典列表里面找,如果没有再在资源列表里面找
+ dict = store.article.bookList.find(v => v.id === dictId)
+ if (!dict) dict = enArticle.find(v => v.id === dictId) as Dict
+ if (dict && dict.id) {
+ //如果是不是自定义词典,就请求数据
+ if (!dict.custom) dict = await _getDictDataByUrl(dict, DictType.article)
+ if (!dict.articles.length) {
router.push('/article')
+ return Toast.warning('没有文章可学习!')
}
+ store.changeBook(dict)
+ articleData.list = cloneDeep(store.sbook.articles)
+ getCurrentPractice()
+ loading = false
} else {
router.push('/article')
}
+ } else {
+ router.push('/article')
}
}
+watch(() => store.load, (n) => {
+ if (n && loading) init()
+}, {immediate: true})
+
+onMounted(() => {
+ if (store.sbook?.articles?.length) {
+ articleData.list = cloneDeep(store.sbook.articles)
+ getCurrentPractice()
+ } else {
+ loading = true
+ }
+})
+
+
+useStartKeyboardEventListener()
+useDisableEventListener(() => loading)
+
function setArticle(val: Article) {
statisticsStore.inputWordNumber = 0
statisticsStore.wrong = 0
@@ -246,8 +236,6 @@ async function onKeyDown(e: KeyboardEvent) {
useOnKeyboardEventListener(onKeyDown, onKeyUp)
-onMounted(init)
-
useEvents([
[EventKey.write, write],
[EventKey.repeatStudy, repeat],
diff --git a/src/pages/pc/word/StudyWord.vue b/src/pages/pc/word/StudyWord.vue
index 16fa0a8f..7f6384e9 100644
--- a/src/pages/pc/word/StudyWord.vue
+++ b/src/pages/pc/word/StudyWord.vue
@@ -61,54 +61,47 @@ let data = $ref({
wrongWords: [],
})
-watch(() => store.load, (n) => {
- if (n && loading && runtimeStore.editDict.id) {
- console.log('load好了开始加载')
- store.changeDict(runtimeStore.editDict)
- studyData = getCurrentStudyWord()
- loading = false
+async function init() {
+ console.log('load好了开始加载')
+ let dict = getDefaultDict()
+ let dictId = route.params.id
+ if (dictId) {
+ //先在自己的词典列表里面找,如果没有再在资源列表里面找
+ dict = store.word.bookList.find(v => v.id === dictId)
+ if (!dict) dict = dictionaryResources.find(v => v.id === dictId) as Dict
+ if (dict && dict.id) {
+ //如果是不是自定义词典,就请求数据
+ if (!dict.custom) dict = await _getDictDataByUrl(dict)
+ if (!dict.words.length) {
+ router.push('/word')
+ return Toast.warning('没有单词可学习!')
+ }
+ store.changeDict(dict)
+ studyData = getCurrentStudyWord()
+ loading = false
+ } else {
+ router.push('/word')
+ }
+ } else {
+ router.push('/word')
}
-}, {immediate: true})
+}
-useStartKeyboardEventListener()
-useDisableEventListener(() => loading)
+watch(() => store.load, (n) => {
+ if (n && loading) init()
+}, {immediate: true})
onMounted(() => {
if (runtimeStore.routeData) {
studyData = runtimeStore.routeData
} else {
- let dictName = route.query.name
- let dictId = route.query.id
- //如果url里有词典id或name,那么直接请求词典数据,并加到bookList里面进行学习
- //todo 这里要处理自定义词典的问题
- if (dictName || dictId) {
- let dictResource = getDefaultDict()
- if (dictId) dictResource = dictionaryResources.find(v => v.id === dictId) as Dict
- else if (dictName) dictResource = dictionaryResources.find(v => v.name === dictName) as Dict
- if (dictResource.id) {
- loading = true
- _getDictDataByUrl(dictResource).then(r => {
- if (!r.words.length) {
- router.push('/word')
- return Toast.warning('没有单词可学习!')
- }
- runtimeStore.editDict = r
- if (store.load) {
- console.log('直接加载')
- store.changeDict(r)
- studyData = getCurrentStudyWord()
- loading = false
- }
- })
- } else {
- router.push('/word')
- }
- } else {
- router.push('/word')
- }
+ loading = true
}
})
+useStartKeyboardEventListener()
+useDisableEventListener(() => loading)
+
watch(() => studyData, () => {
if (studyData.new.length === 0) {
if (studyData.review.length) {
diff --git a/src/pages/pc/word/WordHomePage.vue b/src/pages/pc/word/WordHomePage.vue
index 17de57c2..aee63aa2 100644
--- a/src/pages/pc/word/WordHomePage.vue
+++ b/src/pages/pc/word/WordHomePage.vue
@@ -60,7 +60,7 @@ function startStudy() {
custom: store.sdict.custom,
complete: store.sdict.complete,
})
- nav('study-word', {name: store.sdict.name, id: store.sdict.id}, currentStudy)
+ nav('study-word/' + store.sdict.id, {}, currentStudy)
} else {
window.umami?.track('no-dict')
Toast.warning('请先选择一本词典')
diff --git a/src/router.ts b/src/router.ts
index d1c90c1a..f3bd3286 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -21,11 +21,11 @@ export const routes: RouteRecordRaw[] = [
// {path: 'home', component: HomeIndex},
{path: 'word', component: WordHomePage},
{path: 'dict-list', component: DictList},
- {path: 'study-word', component: StudyWord},
+ {path: 'study-word/:id', component: StudyWord},
{path: 'dict-detail', component: DictDetail},
{path: 'article', component: ArticleHomePage},
- {path: 'study-article', component: StudyArticle},
+ {path: 'study-article/:id', component: StudyArticle},
{path: 'book-detail', component: BookDetail},
{path: 'book-list', component: BookList},
{path: 'edit-article', component: () => import("@/pages/pc/article/EditArticlePage.vue")},
@@ -39,8 +39,8 @@ export const routes: RouteRecordRaw[] = [
]
const router = VueRouter.createRouter({
- // history: VueRouter.createWebHistory(),
- history: VueRouter.createWebHashHistory(),
+ history: VueRouter.createWebHistory(),
+ // history: VueRouter.createWebHashHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// console.log('savedPosition', savedPosition)
diff --git a/src/utils/index.ts b/src/utils/index.ts
index d29c5c76..517670e0 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -414,9 +414,9 @@ export async function sleep(time: number) {
export async function _getDictDataByUrl(val: DictResource, type: DictType = DictType.word): Promise {
// await sleep(2000);
- let dictResourceUrl = `./dicts/${val.language}/word/${val.url}`
+ let dictResourceUrl = `/dicts/${val.language}/word/${val.url}`
if (type === DictType.article) {
- dictResourceUrl = `./dicts/${val.language}/${val.type}/${val.url}`;
+ dictResourceUrl = `/dicts/${val.language}/${val.type}/${val.url}`;
}
let s = await getDictFile(dictResourceUrl)
if (s) {