This commit is contained in:
Zyronon
2025-11-17 23:18:09 +08:00
parent 15240dc501
commit 697db5f975
3 changed files with 154 additions and 24 deletions

63
public/migrate.html Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Migrate Data</title>
</head>
<body>
<script>
async function readIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('type-words', 1); // 你的数据库名
request.onsuccess = function (event) {
const db = event.target.result;
const tx = db.transaction(['typing-word-dict', 'typing-word-setting', 'typing-word-files'], 'readonly');
const result = {};
let count = 0;
const keys = ['typing-word-dict', 'typing-word-setting', 'typing-word-files'];
keys.forEach((storeName) => {
const store = tx.objectStore(storeName);
const allRequest = store.getAll();
allRequest.onsuccess = function (e) {
result[storeName] = e.target.result;
count++;
if (count === keys.length) {
resolve(result);
}
};
allRequest.onerror = function (e) {
result[storeName] = null;
count++;
if (count === keys.length) resolve(result);
};
});
};
request.onerror = function (e) {
resolve({});
};
});
}
// 监听 postMessage
window.addEventListener('message', async (e) => {
if (e.data && e.data.type === 'requestData') {
const local = {};
['PracticeSaveWord', 'PracticeSaveArticle'].forEach(key => {
local[key] = localStorage.getItem(key) || null;
});
const indexed = await readIndexedDB();
// 回复新域名
e.source.postMessage({
type: 'responseData',
localStorageData: local,
indexedDBData: indexed
}, e.origin);
}
});
</script>
</body>
</html>

View File

@@ -10,9 +10,9 @@
<meta name="keywords"
content="Type Words, Typing Word, Type Words 官网, 官方网站, 英语打字练习, 单词跟打, 文章跟打, 键盘练习, 英语学习, 文章学习, 打字练习软件, 单词记忆工具, 英语学习软件, 背单词神器, 英语肌肉记忆, 键盘工作者, 免费英语学习, 音标发音, 默写练习, 在线学英语, CET-4, CET-6, TOEFL, IELTS, GRE, GMAT, SAT, 考研英语, 专四专八, 程序员英语, JavaScript API, Node.js API, Java API, Linux命令, 编程词汇, 技术英语, VSCode插件, 开源项目, GitHub趋势榜, V2EX热搜, Gitee GVP, 少数派推荐, 英语打字训练, WPM统计, 准确率分析, 商务英语, BEC, 雅思听力, 日语学习, 多语言学习, 英语口语练习, 单词拼写训练">
<meta name="author" content="zyronon" />
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<link rel="canonical" href="https://typewords.cc/" />
<meta name="author" content="zyronon"/>
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"/>
<link rel="canonical" href="https://typewords.cc/"/>
<!-- Open Graph用于社交媒体分享微信/QQ/知乎/Facebook 等) -->
<meta property="og:title" content="Type Words 官网 - 英语打字练习平台">
@@ -31,22 +31,22 @@
<link rel="icon" type="image/svg+xml" href="/favicon.png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<!-- 苹果设备iOS Safari在用户添加到主屏时显示的图标-->
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.png" />
<!-- 设置浏览器地址栏颜色(在 Android Chrome 特别明显)。-->
<meta name="theme-color" content="#818CF8" />
<!-- 苹果设备iOS Safari在用户添加到主屏时显示的图标-->
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.png"/>
<!-- 设置浏览器地址栏颜色(在 Android Chrome 特别明显)。-->
<meta name="theme-color" content="#818CF8"/>
<link rel="manifest" href="/manifest.json">
<!-- 阻止 iOS 自动把数字识别为电话号码。-->
<!-- HandheldFriendly 和 MobileOptimized 是旧手机浏览器的优化提示(现在作用不大)。-->
<meta name="format-detection" content="telephone=no" />
<meta name="HandheldFriendly" content="True" />
<meta name="MobileOptimized" content="320" />
<!-- 阻止 iOS 自动把数字识别为电话号码。-->
<!-- HandheldFriendly 和 MobileOptimized 是旧手机浏览器的优化提示(现在作用不大)。-->
<meta name="format-detection" content="telephone=no"/>
<meta name="HandheldFriendly" content="True"/>
<meta name="MobileOptimized" content="320"/>
<!-- referrer 控制请求来源信息-->
<meta name="referrer" content="origin-when-cross-origin" />
<!-- color-scheme 告诉浏览器支持亮/暗模式-->
<meta name="color-scheme" content="light dark" />
<!-- referrer 控制请求来源信息-->
<meta name="referrer" content="origin-when-cross-origin"/>
<!-- color-scheme 告诉浏览器支持亮/暗模式-->
<meta name="color-scheme" content="light dark"/>
<style>
body {
@@ -243,7 +243,7 @@
.bottom {
display: flex;
gap: 1rem;
margin:1rem 0 2rem 0;
margin: 1rem 0 2rem 0;
width: 100%;
padding-top: 1.5rem;
border-top: 1px solid #c4c4c4;
@@ -306,6 +306,7 @@
toggleEl('.mask')
toggleEl('#wechatDialog')
}
function toggleQQDialog() {
toggleEl('.mask')
toggleEl('#qqDialog')
@@ -323,6 +324,66 @@
toggleEl('#qqDialog', true)
}
</script>
<script>
function migrateFromOldDomain() {
return new Promise((resolve) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'https://2study.top/migrate.html';
document.body.appendChild(iframe);
// 接收数据
window.addEventListener('message', async function handler(e) {
if (e.data && e.data.type === 'responseData') {
// 写入 localStorage
const localData = e.data.localStorageData;
for (const key in localData) {
if (localData[key] !== null) localStorage.setItem(key, localData[key]);
}
// 写入 IndexedDB
const indexedData = e.data.indexedDBData;
const request = indexedDB.open('type-words', 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
// 建 store
['typing-word-dict', 'typing-word-setting', 'typing-word-files'].forEach(name => {
if (!db.objectStoreNames.contains(name)) {
db.createObjectStore(name, {autoIncrement: true});
}
});
};
request.onsuccess = function (event) {
const db = event.target.result;
const tx = db.transaction(['typing-word-dict', 'typing-word-setting', 'typing-word-files'], 'readwrite');
for (const storeName in indexedData) {
const store = tx.objectStore(storeName);
const items = indexedData[storeName];
if (items) {
items.forEach(item => store.put(item));
}
}
tx.oncomplete = function () {
resolve(true);
window.removeEventListener('message', handler);
iframe.remove();
};
};
// 发送请求
iframe.contentWindow.postMessage({type: 'requestData'}, 'https://2study.top');
}
});
});
}
if (location.href === 'https://typewords.cc/') {
migrateFromOldDomain().then(() => {
console.log('数据迁移完成!');
});
}
</script>
</head>
<body>
<div class="wrapper">
@@ -458,7 +519,13 @@
</div>
<div class="icon" onclick="toggleQQDialog()">
<svg viewBox="0 0 24 24" width="1.4em" height="1.4em"><g fill="none"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M12 2a6.285 6.285 0 0 0-6.276 5.937l-.146 2.63a28 28 0 0 0-.615 1.41c-1.24 3.073-1.728 5.773-1.088 6.032c.335.135.913-.426 1.566-1.432a6.67 6.67 0 0 0 1.968 3.593c-1.027.35-1.91.828-1.91 1.33c0 .509 2.48.503 4.239.5h.001c.549-.002 1.01-.008 1.38-.057a6.7 6.7 0 0 0 1.76 0c.37.05.833.055 1.382.056c1.76.004 4.239.01 4.239-.499c0-.502-.883-.979-1.909-1.33a6.67 6.67 0 0 0 1.967-3.586c.65 1.002 1.227 1.56 1.56 1.425c.64-.259.154-2.96-1.088-6.032a28 28 0 0 0-.607-1.395l-.147-2.645A6.285 6.285 0 0 0 12 2"/></g></svg>
<svg viewBox="0 0 24 24" width="1.4em" height="1.4em">
<g fill="none">
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/>
<path fill="currentColor"
d="M12 2a6.285 6.285 0 0 0-6.276 5.937l-.146 2.63a28 28 0 0 0-.615 1.41c-1.24 3.073-1.728 5.773-1.088 6.032c.335.135.913-.426 1.566-1.432a6.67 6.67 0 0 0 1.968 3.593c-1.027.35-1.91.828-1.91 1.33c0 .509 2.48.503 4.239.5h.001c.549-.002 1.01-.008 1.38-.057a6.7 6.7 0 0 0 1.76 0c.37.05.833.055 1.382.056c1.76.004 4.239.01 4.239-.499c0-.502-.883-.979-1.909-1.33a6.67 6.67 0 0 0 1.967-3.586c.65 1.002 1.227 1.56 1.56 1.425c.64-.259.154-2.96-1.088-6.032a28 28 0 0 0-.607-1.395l-.147-2.645A6.285 6.285 0 0 0 12 2"/>
</g>
</svg>
</div>
<div class="icon" onclick="toggleXhsDialog()">
<svg viewBox="0 0 24 24" width="1.4em" height="1.4em">
@@ -492,7 +559,7 @@
</div>
</a>
</div>
<div><a href="https://beian.miit.gov.cn/" target="_blank">蜀ICP备2025157466号</a></div>
<div><a href="https://beian.miit.gov.cn/" target="_blank">蜀ICP备2025157466号-2</a></div>
</div>
<div class="mask" onclick="closeDialog()"></div>

View File

@@ -77,10 +77,10 @@ async function init() {
onMounted(init)
let transitionName = $ref('go')
const route = useRoute()
watch(() => route.path, (to, from) => {
return transitionName = ''
// let transitionName = $ref('go')
// const route = useRoute()
// watch(() => route.path, (to, from) => {
// return transitionName = ''
// console.log('watch', to, from)
// //footer下面的5个按钮对跳不要用动画
// let noAnimation = [
@@ -97,7 +97,7 @@ watch(() => route.path, (to, from) => {
// const fromDepth = routes.findIndex(v => v.path === from)
// transitionName = toDepth > fromDepth ? 'go' : 'back'
// console.log('transitionName', transitionName, toDepth, fromDepth)
})
// })
</script>
<template>