This commit is contained in:
Zyronon
2025-11-06 12:06:15 +00:00
parent 00b1c521cb
commit 90fcd70604
15 changed files with 92 additions and 266 deletions

14
Note.md
View File

@@ -65,4 +65,16 @@ I found this note on my car: 'Sir, we welcome you to our city. This is a 'No Par
Food and talk
A new play is coming to "The Globe"soon, I said. Will you be seeing it?
26的 of curse
26的 of curse
1、例句可以选中单词并添加到收藏
1、域名你需要一个真正的品牌type域名契合内容让人记得住
2、口号太墨迹记不住要简介学习英语一次敲击一点进步记忆不再盲目学习更高效开源单词与文章练习工具
3、布局没有逻辑首页进去看到标题然后两个小的单词练习、文章联系下面又是一大堆大的没有逻辑和划分
4、下面联系方式你需要放一个QQ群社群资源全放联系方式等于没放
5、ABC页面弹出来的保存书签不是加入书签就不迷失是要让他们肌肉记忆住这个网站
6、ABC页面太墨迹不简洁进度复杂本周学习记录改成日历有个标记+激励分享功能,满足炫耀欲望
7、设置不要用那个烂字体真的很山寨
8、更新日志要按v版本和日期跟踪要写的多做得少显示你在认真维护是个令人尊重的程序员
9、必须加后端、账号注册

View File

@@ -6,7 +6,7 @@
"start": "vite",
"dev": "vite",
"test": "",
"build": "vite build && node scripts/generate-sitemap.js",
"build": "vite build && node scripts/do.js",
"build-nocdn": "vite build",
"build-tsc": "vue-tsc && vite build",
"report": "vite build",

View File

@@ -40,7 +40,8 @@
</script>
<script>
function nav(url) {
history.pushState(null, "", url);
window.location.href = url;
// history.pushState(null, "", url);
}
function toggleEl(val, close = false) {

49
scripts/do.js Normal file
View File

@@ -0,0 +1,49 @@
const {SitemapStream, streamToPromise} = require('sitemap')
const {createWriteStream} = require('fs')
const {resolve} = require('path')
const fs = require('fs')
async function generateSitemap() {
const bookList = require('../public/list/article.json')
const dictList = require('../public/list/word.json')
const SITE_URL = 'https://2study.top'
// 静态路由(首页、练习页等)
const staticPages = [
{url: '/', changefreq: 'daily', priority: 1.0},
{url: '/words', changefreq: 'daily', priority: 0.9},
{url: '/articles', changefreq: 'daily', priority: 0.9},
{url: '/setting', changefreq: 'monthly', priority: 0.3},
]
// 动态页面示例(假设你有文章或单词数据)
const dynamicPages = bookList.flat().map(book => {
return {url: '/practice-articles/' + book.id, changefreq: 'weekly', priority: 0.8}
}).concat(dictList.flat().map(book => {
return {url: '/practice-words/' + book.id, changefreq: 'weekly', priority: 0.8}
}))
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 目录')
}
function renameHtml() {
//首页为了seo被剥离出去了现在是一个静态页面用nginx 重定向控制对应的跳转
fs.renameSync('dist/index.html', 'dist/app.html')
fs.renameSync('dist/static-home.html', 'dist/index.html')
}
generateSitemap()
renameHtml()

View File

@@ -1,42 +0,0 @@
const {SitemapStream, streamToPromise} = require('sitemap')
const {createWriteStream} = require('fs')
const {resolve} = require('path')
const bookList = require('../public/list/article.json')
const dictList = require('../public/list/word.json')
// 你的网站域名
const SITE_URL = 'https://2study.top'
// 静态路由(首页、练习页等)
const staticPages = [
{url: '/', changefreq: 'daily', priority: 1.0},
{url: '/words', changefreq: 'daily', priority: 0.9},
{url: '/articles', changefreq: 'daily', priority: 0.9},
{url: '/setting', changefreq: 'monthly', priority: 0.3},
]
// 动态页面示例(假设你有文章或单词数据)
const dynamicPages = bookList.flat().map(book => {
return {url: '/practice-articles/' + book.id, changefreq: 'weekly', priority: 0.8}
}).concat(dictList.flat().map(book => {
return {url: '/practice-words/' + book.id, changefreq: 'weekly', priority: 0.8}
}))
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()

View File

@@ -1,189 +0,0 @@
<script setup lang="ts">
import Close from "@/components/icon/Close.vue";
import BaseButton from "@/components/BaseButton.vue";
import {watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {isMobile} from "@/utils";
import {ProjectName, Host} from "@/config/env.ts";
let settingStore = useSettingStore()
let showNotice = $ref(false)
let show = $ref(false)
let num = $ref(5)
let timer = -1
let mobile = $ref(isMobile())
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
function toggleNotice() {
showNotice = true
settingStore.first = false
timer = setInterval(() => {
num--
if (num <= 0) close()
}, 1000)
}
function close() {
clearInterval(timer)
show = settingStore.first = false
}
watch(() => settingStore.load, (n) => {
if (n && settingStore.first) {
setTimeout(() => {
show = true
}, 1000)
}
}, {immediate: true})
</script>
<template>
<transition name="right">
<div class="CollectNotice"
:class="{mobile}"
v-if="show">
<div class="notice">
坚持练习提高外语能力
<span class="active">{{ ProjectName }}</span>
保存为书签永不迷失
</div>
<div class="wrapper">
<transition name="fade">
<div class="collect" v-if="showNotice">
<div class="href-wrapper">
<div class="round">
<div class="href">{{ Host }}</div>
<IconFluentStar12Regular width="22"/>
</div>
<div class="right">
👈
<IconFluentStar20Filled class="star" width="22"/>
点亮它!
</div>
</div>
<div class="collect-keyboard" v-if="!mobile">或使用收藏快捷键<span
class="active">{{ isMac ? 'Command' : 'Ctrl' }} + D</span></div>
</div>
<BaseButton v-else size="large" @click="toggleNotice">我想收藏</BaseButton>
</transition>
</div>
<div class="close-wrapper">
<span v-show="showNotice"><span class="active">{{ num }}s</span> 后自动关闭</span>
<Close @click="close" title="关闭"/>
</div>
</div>
</transition>
</template>
<style scoped lang="scss">
.right-enter-active,
.right-leave-active {
transition: all .5s ease;
}
.right-enter-from,
.right-leave-to {
transform: translateX(110%);
}
.CollectNotice {
position: fixed;
right: var(--space);
top: var(--space);
z-index: 2;
font-size: 1.2rem;
display: flex;
flex-direction: column;
align-items: center;
background: var(--color-notice-bg);
padding: 1.8rem;
border-radius: 0.7rem;
width: 30rem;
gap: 2.4rem;
color: var(--color-font-1);
line-height: 1.5;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
box-sizing: border-box;
&.mobile {
width: 95%;
padding: 0.6rem;
}
.notice {
margin-top: 2.4rem;
}
.active {
color: var(--color-select-bg);
}
.wrapper {
.collect {
display: flex;
flex-direction: column;
align-items: center;
.href-wrapper {
display: flex;
font-size: 1rem;
align-items: center;
gap: 0.6rem;
.round {
color: var(--color-font-1);
border-radius: 3rem;
padding: 0.6rem 0.6rem;
padding-left: 1.2rem;
gap: 2rem;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-primary);
.href {
font-size: 0.9rem;
}
}
.star {
color: var(--color-select-bg);
}
.right {
display: flex;
align-items: center;
}
}
.collect-keyboard {
margin-top: 1.2rem;
font-size: 1rem;
span {
margin-left: 0.6rem;
}
}
}
}
.close-wrapper {
right: var(--space);
top: var(--space);
position: absolute;
font-size: 0.9rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: var(--color-font-1);
gap: 0.6rem;
}
}
</style>

View File

@@ -8,13 +8,15 @@ import useTheme from "@/hooks/theme.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const router = useRouter()
const {toggleTheme,getTheme} = useTheme()
const {toggleTheme, getTheme} = useTheme()
//首页为了seo被剥离出去了现在是一个静态页面用nginx 重定向控制对应的跳转
function goHome() {
window.location.href = '/';
}
</script>
<template>
@@ -24,7 +26,7 @@ const {toggleTheme,getTheme} = useTheme()
<div class="aside anim fixed" :class="{'expand':settingStore.sideExpand}">
<div class="top">
<Logo v-if="settingStore.sideExpand"/>
<div class="row" @click="router.push('/')">
<div class="row" @click="goHome">
<IconFluentHome20Regular/>
<span v-if="settingStore.sideExpand">主页</span>
</div>
@@ -42,21 +44,21 @@ const {toggleTheme,getTheme} = useTheme()
<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="router.push('/user')">-->
<!-- <IconFluentPerson20Regular/>-->
<!-- <span v-if="settingStore.sideExpand">用户</span>-->
<!-- </div>-->
<!-- <div class="row" @click="router.push('/user')">-->
<!-- <IconFluentPerson20Regular/>-->
<!-- <span v-if="settingStore.sideExpand">用户</span>-->
<!-- </div>-->
</div>
<div class="bottom flex justify-evenly ">
<BaseIcon
@click="settingStore.sideExpand = !settingStore.sideExpand">
@click="settingStore.sideExpand = !settingStore.sideExpand">
<IconFluentChevronLeft20Filled v-if="settingStore.sideExpand"/>
<IconFluentChevronLeft20Filled class="transform-rotate-180" v-else/>
</BaseIcon>
<BaseIcon
v-if="settingStore.sideExpand"
:title="`切换主题(${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
@click="toggleTheme"
v-if="settingStore.sideExpand"
:title="`切换主题(${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
@click="toggleTheme"
>
<IconFluentWeatherMoon16Regular v-if="getTheme() === 'light'"/>
<IconFluentWeatherSunny16Regular v-else/>

View File

@@ -237,7 +237,7 @@ async function startPractice() {
wordPracticeMode: settingStore.wordPracticeMode
})
let currentStudy = getCurrentStudyWord()
nav('practice-words/' + store.sdict.id, {}, currentStudy)
nav('practice-words/' + store.sdict.id, {}, {taskWords:currentStudy})
}
async function addMyStudyList() {

View File

@@ -9,6 +9,7 @@ import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import {defineAsyncComponent, inject, watch} from "vue";
import isoWeek from 'dayjs/plugin/isoWeek'
import {msToHourMinute, msToMinute} from "@/utils";
dayjs.extend(isoWeek)
dayjs.extend(isBetween);
@@ -135,8 +136,7 @@ function options(emitType: string) {
</div>
<div class="text-xl text-center flex flex-col justify-around">
<div>非常棒! 坚持了 <span class="color-emerald-500 font-bold text-2xl">
{{ dayjs().diff(statStore.startDate, 'm') }}</span>分钟
<div>非常棒! 坚持了 <span class="color-emerald-500 font-bold text-2xl">{{msToHourMinute(statStore.spend) }}</span>
</div>
</div>
<div class="flex justify-center gap-10">

View File

@@ -18,7 +18,6 @@ import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
import {useSettingStore} from "@/stores/setting.ts";
import CollectNotice from "@/components/CollectNotice.vue";
import {useFetch} from "@vueuse/core";
import {CAN_REQUEST, DICT_LIST, PracticeSaveWordKey} from "@/config/env.ts";
import {myDictList} from "@/apis";
@@ -173,7 +172,7 @@ async function onShufflePracticeSettingOk(total) {
localStorage.removeItem(PracticeSaveWordKey.key)
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
currentStudy.shuffle = shuffle(store.sdict.words.filter(v => !ignoreList.includes(v.word))).slice(0, total)
currentStudy.shuffle = shuffle(store.sdict.words.slice(0, store.sdict.lastLearnIndex).filter(v => !ignoreList.includes(v.word))).slice(0, total)
nav('practice-words/' + store.sdict.id, {}, {
taskWords: currentStudy,
total //用于再来一组时,随机出正确的长度,因为练习中可能会点击已掌握,导致重学一遍之后长度变少,如果再来一组,此时长度就不正确
@@ -379,7 +378,6 @@ const {
v-model="showShufflePracticeSettingDialog"
@ok="onShufflePracticeSettingOk"/>
<CollectNotice/>
</template>
<style scoped lang="scss">

View File

@@ -141,6 +141,7 @@ function unknown(e) {
if (!showWordResult) {
showWordResult = true
emit('wrong')
if (settingStore.wordSound) volumeIconRef?.play()
return
}
}

View File

@@ -18,9 +18,9 @@ export const routes: RouteRecordRaw[] = [
{
path: '/',
component: Layout,
redirect: '/',
children: [
{path: '/', component: Home},
// {path: '/', component: Home},
{path: '/', redirect: '/words'},
{path: 'words', component: WordsPage},
{path: 'word', redirect: '/words'},
{path: 'practice-words/:id', component: PracticeWords},

View File

@@ -69,7 +69,7 @@ export const getDefaultSettingState = (): SettingState => ({
keyboardSound: true,
keyboardSoundVolume: 100,
keyboardSoundFile: '机械键盘2',
keyboardSoundFile: '笔记本键盘',
effectSound: true,
effectSoundVolume: 100,

View File

@@ -1,16 +1,16 @@
import { defineConfig } from 'vite'
import {defineConfig} from 'vite'
import Vue from '@vitejs/plugin-vue'
import VueJsx from "@vitejs/plugin-vue-jsx";
import { resolve } from 'path'
import { visualizer } from "rollup-plugin-visualizer";
import {resolve} from 'path'
import {visualizer} from "rollup-plugin-visualizer";
import SlidePlugin from './src/components/slide/data.js';
import { getLastCommit } from "git-last-commit";
import {getLastCommit} from "git-last-commit";
import UnoCSS from 'unocss/vite'
import VueMacros from 'unplugin-vue-macros/vite'
import Icons from 'unplugin-icons/vite'
import Components from 'unplugin-vue-components/vite'
import IconsResolver from 'unplugin-icons/resolver'
import { viteExternalsPlugin } from 'vite-plugin-externals'
import {viteExternalsPlugin} from 'vite-plugin-externals'
function pathResolve(dir: string) {
return resolve(__dirname, ".", dir)
@@ -79,9 +79,6 @@ export default defineConfig(() => {
],
build: {
rollupOptions: {
input: {
app: 'app.html' // 默认入口
},
// 因为已经把包复制过来了里面的axios实例用的项目的所以这行代码可以不要了
// external: isCdnBuild ? ['axios'] : [],// 使用全局的 axios。因为百度翻译库内部用了0.19版本的axios会被打包到代码里面
output: {
@@ -128,9 +125,6 @@ export default defineConfig(() => {
port: 3000,
open: false,
host: '0.0.0.0',
fs: {
strict: false,
},
proxy: {
'/baidu': 'https://api.fanyi.baidu.com/api/trans/vip/translate'
}