@@ -42,7 +42,7 @@
|
||||
|
||||
### 单词练习
|
||||
|
||||
- 三种输入模式:跟打 / 复习 / 默写
|
||||
- 四种输入模式:跟打 / 辨认 / 复习 / 默写
|
||||
- 智能模式:记忆曲线自动计算学习单词,并通过默写加深记忆
|
||||
- 自由模式:不受限制,自行规划
|
||||
- 提供音标、发音(美音、英音)、例句、短语、近义词、同根词、词源、错误统计等功能
|
||||
|
||||
12
index.html
12
index.html
@@ -62,18 +62,6 @@
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
|
||||
(function () {
|
||||
var umami = document.createElement("script");
|
||||
umami.src = 'https://typewords.cc/s.js'
|
||||
if (location.href.includes('vercel') || location.href.includes('tw')) {
|
||||
umami.setAttribute("data-website-id", "f630eefc-8b91-4e20-b890-106e6c7bcc10");
|
||||
} else {
|
||||
umami.setAttribute("data-website-id", "160308c9-7900-4b1d-a0b1-c3b25a9530f6");
|
||||
}
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(umami, s);
|
||||
})();
|
||||
|
||||
(function () {
|
||||
var umami2 = document.createElement("script");
|
||||
umami2.src = 'https://stat.typewords.cc/script.js'
|
||||
|
||||
@@ -1896,9 +1896,6 @@
|
||||
"tags": [
|
||||
"通用"
|
||||
],
|
||||
"words": [
|
||||
"private","fuck","add","remove"
|
||||
],
|
||||
"url": "GaoKaoZhenTiHeXinGaoPin.json",
|
||||
"length": 799,
|
||||
"language": "en",
|
||||
|
||||
@@ -2,62 +2,85 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Migrate Data</title>
|
||||
<title>TypeWords 数据迁移(旧域名)</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 = {};
|
||||
<h2>等待新域名发送迁移指令...</h2>
|
||||
<pre id="log"></pre>
|
||||
|
||||
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({});
|
||||
<script>
|
||||
function log(msg) {
|
||||
console.log(msg);
|
||||
document.getElementById('log').textContent += msg + "\n";
|
||||
}
|
||||
|
||||
// 1️⃣ 先动态加载 idb-keyval
|
||||
function loadIDBKeyval() {
|
||||
return new Promise((resolve) => {
|
||||
let script = document.createElement("script");
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/idb-keyval@6.2.2/dist/umd.js';
|
||||
script.onload = function () {
|
||||
log("idb-keyval 加载完成");
|
||||
resolve(window.idbKeyval);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 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;
|
||||
});
|
||||
loadIDBKeyval(); // 确保 idb-keyval 已经加载
|
||||
|
||||
const indexed = await readIndexedDB();
|
||||
// 2️⃣ 读取 IndexedDB
|
||||
async function readAllStorageForMigration(db) {
|
||||
// localStorage 数据
|
||||
const localStorageData = {
|
||||
PracticeSaveWord: localStorage.getItem('PracticeSaveWord'),
|
||||
PracticeSaveArticle: localStorage.getItem('PracticeSaveArticle')
|
||||
};
|
||||
|
||||
// 回复新域名
|
||||
e.source.postMessage({
|
||||
type: 'responseData',
|
||||
localStorageData: local,
|
||||
indexedDBData: indexed
|
||||
}, e.origin);
|
||||
// IndexedDB 数据,key 对应你的老项目
|
||||
const keys = [
|
||||
'type-words-app-version',
|
||||
'typing-word-dict',
|
||||
'typing-word-setting',
|
||||
'typing-word-files'
|
||||
];
|
||||
|
||||
const indexedDBData = {};
|
||||
for (let key of keys) {
|
||||
let res = await db.get(key);
|
||||
if (res) indexedDBData[key] = res
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
localStorage: localStorageData,
|
||||
indexedDB: indexedDBData
|
||||
};
|
||||
}
|
||||
|
||||
// 3️⃣ 接收新域名指令
|
||||
window.addEventListener('message', async (event) => {
|
||||
if (event.data?.type !== 'REQUEST_MIGRATION_DATA') return;
|
||||
|
||||
// 安全校验 origin,可选
|
||||
// if (event.origin !== 'https://typewords.cc') return;
|
||||
|
||||
log("收到迁移指令,开始读取数据...");
|
||||
|
||||
const db = await loadIDBKeyval(); // 确保 idb-keyval 已经加载
|
||||
const data = await readAllStorageForMigration(db);
|
||||
|
||||
log("读取完成,发送数据给新域名");
|
||||
event.source.postMessage({
|
||||
type: 'MIGRATION_RESULT',
|
||||
payload: data
|
||||
}, event.origin);
|
||||
|
||||
log("已发送迁移数据");
|
||||
// 自动关闭窗口(延迟 500ms)
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
!function(){"use strict";(t=>{const{screen:{width:e,height:a},navigator:{language:n,doNotTrack:i,msDoNotTrack:r},location:o,document:s,history:c,top:u,doNotTrack:d}=t,{currentScript:l,referrer:h}=s;if(!l)return;const{hostname:f,href:m,origin:p}=o,y=m.startsWith("data:")?void 0:t.localStorage,g="data-",b="true",v=l.getAttribute.bind(l),w=v(g+"website-id"),S=v(g+"host-url"),k=v(g+"before-send"),N=v(g+"tag")||void 0,T="false"!==v(g+"auto-track"),A=v(g+"do-not-track")===b,j=v(g+"exclude-search")===b,x=v(g+"exclude-hash")===b,$=v(g+"domains")||"",E=$.split(",").map(t=>t.trim()),K=`${(S||"https://api-gateway.umami.dev"||l.src.split("/").slice(0,-1).join("/")).replace(/\/$/,"")}/api/send`,L=`${e}x${a}`,O=/data-umami-event-([\w-_]+)/,_=g+"umami-event",D=300,U=()=>({website:w,screen:L,language:n,title:s.title,hostname:f,url:z,referrer:F,tag:N,id:q||void 0}),W=(t,e,a)=>{a&&(F=z,z=new URL(a,o.href),j&&(z.search=""),x&&(z.hash=""),z=z.toString(),z!==F&&setTimeout(J,D))},B=()=>H||!w||y&&y.getItem("umami.disabled")||$&&!E.includes(f)||A&&(()=>{const t=d||i||r;return 1===t||"1"===t||"yes"===t})(),C=async(e,a="event")=>{if(B())return;const n=t[k];if("function"==typeof n&&(e=n(a,e)),e)try{const t=await fetch(K,{keepalive:!0,method:"POST",body:JSON.stringify({type:a,payload:e}),headers:{"Content-Type":"application/json",...void 0!==R&&{"x-umami-cache":R}},credentials:"omit"}),n=await t.json();n&&(H=!!n.disabled,R=n.cache)}catch(t){}},I=()=>{G||(G=!0,J(),(()=>{const t=(t,e,a)=>{const n=t[e];return(...e)=>(a.apply(null,e),n.apply(t,e))};c.pushState=t(c,"pushState",W),c.replaceState=t(c,"replaceState",W)})(),(()=>{const t=async t=>{const e=t.getAttribute(_);if(e){const a={};return t.getAttributeNames().forEach(e=>{const n=e.match(O);n&&(a[n[1]]=t.getAttribute(e))}),J(e,a)}};s.addEventListener("click",async e=>{const a=e.target,n=a.closest("a,button");if(!n)return t(a);const{href:i,target:r}=n;if(n.getAttribute(_)){if("BUTTON"===n.tagName)return t(n);if("A"===n.tagName&&i){const a="_blank"===r||e.ctrlKey||e.shiftKey||e.metaKey||e.button&&1===e.button;return a||e.preventDefault(),t(n).then(()=>{a||(("_top"===r?u.location:o).href=i)})}}},!0)})())},J=(t,e)=>C("string"==typeof t?{...U(),name:t,data:e}:"object"==typeof t?{...t}:"function"==typeof t?t(U()):U()),P=(t,e)=>("string"==typeof t&&(q=t),R="",C({...U(),data:"object"==typeof t?t:e},"identify"));t.umami||(t.umami={track:J,identify:P});let R,q,z=m,F=h.startsWith(p)?"":h,G=!1,H=!1;T&&!B()&&("complete"===s.readyState?I():s.addEventListener("readystatechange",I,!0))})(window)}();
|
||||
@@ -324,66 +324,6 @@
|
||||
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">
|
||||
|
||||
84
src/App.vue
84
src/App.vue
@@ -1,28 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch } from "vue";
|
||||
import { BaseState, useBaseStore } from "@/stores/base.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import {onMounted, watch} from "vue";
|
||||
import {BaseState, useBaseStore} from "@/stores/base.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import { shakeCommonDict } from "@/utils";
|
||||
import { get, set } from 'idb-keyval'
|
||||
import {loadJsLib, shakeCommonDict} from "@/utils";
|
||||
import {get, set} from 'idb-keyval'
|
||||
|
||||
import { useRoute } from "vue-router";
|
||||
import { DictId } from "@/types/types.ts";
|
||||
import { APP_VERSION, AppEnv, LOCAL_FILE_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
|
||||
import { syncSetting } from "@/apis";
|
||||
import { useUserStore } from "@/stores/auth.ts";
|
||||
import {useRoute} from "vue-router";
|
||||
import {DictId} from "@/types/types.ts";
|
||||
import {APP_VERSION, AppEnv, LOCAL_FILE_KEY, Origin, SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/config/env.ts";
|
||||
import {syncSetting} from "@/apis";
|
||||
import {useUserStore} from "@/stores/auth.ts";
|
||||
import MigrateDialog from "@/pages/MigrateDialog.vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const settingStore = useSettingStore()
|
||||
const userStore = useUserStore()
|
||||
const { setTheme } = useTheme()
|
||||
const {setTheme} = useTheme()
|
||||
|
||||
let lastAudioFileIdList = []
|
||||
watch(store.$state, (n: BaseState) => {
|
||||
let data = shakeCommonDict(n)
|
||||
set(SAVE_DICT_KEY.key, JSON.stringify({ val: data, version: SAVE_DICT_KEY.version }))
|
||||
set(SAVE_DICT_KEY.key, JSON.stringify({val: data, version: SAVE_DICT_KEY.version}))
|
||||
|
||||
//筛选自定义和收藏
|
||||
let bookList = data.article.bookList.filter(v => v.custom || [DictId.articleCollect].includes(v.id))
|
||||
@@ -51,11 +52,11 @@ watch(store.$state, (n: BaseState) => {
|
||||
})
|
||||
|
||||
watch(() => settingStore.$state, (n) => {
|
||||
set(SAVE_SETTING_KEY.key, JSON.stringify({ val: n, version: SAVE_SETTING_KEY.version }))
|
||||
set(SAVE_SETTING_KEY.key, JSON.stringify({val: n, version: SAVE_SETTING_KEY.version}))
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
syncSetting(null, settingStore.$state)
|
||||
}
|
||||
}, { deep: true })
|
||||
}, {deep: true})
|
||||
|
||||
async function init() {
|
||||
await userStore.init()
|
||||
@@ -66,37 +67,48 @@ async function init() {
|
||||
setTheme(settingStore.theme)
|
||||
|
||||
if (settingStore.first) {
|
||||
set(APP_VERSION.key,APP_VERSION.version)
|
||||
}else {
|
||||
set(APP_VERSION.key, APP_VERSION.version)
|
||||
} else {
|
||||
get(APP_VERSION.key).then(r => {
|
||||
runtimeStore.isNew = r ? (APP_VERSION.version > Number(r)) : true
|
||||
})
|
||||
}
|
||||
window.umami?.track('host', { host: window.location.host })
|
||||
window.umami?.track('host', {host: window.location.host})
|
||||
}
|
||||
|
||||
onMounted(init)
|
||||
|
||||
//迁移数据
|
||||
let showTransfer = $ref(false)
|
||||
onMounted(() => {
|
||||
if (new URLSearchParams(window.location.search).get('from_old_site') === '1' && location.origin === Origin) {
|
||||
if (localStorage.getItem('__migrated_from_2study_top__')) return;
|
||||
setTimeout(() => {
|
||||
showTransfer = true
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
// let transitionName = $ref('go')
|
||||
// const route = useRoute()
|
||||
// watch(() => route.path, (to, from) => {
|
||||
// return transitionName = ''
|
||||
// console.log('watch', to, from)
|
||||
// //footer下面的5个按钮,对跳不要用动画
|
||||
// let noAnimation = [
|
||||
// '/pc/practice',
|
||||
// '/pc/dict',
|
||||
// '/mobile',
|
||||
// '/'
|
||||
// ]
|
||||
// if (noAnimation.indexOf(from) !== -1 && noAnimation.indexOf(to) !== -1) {
|
||||
// return transitionName = ''
|
||||
// }
|
||||
//
|
||||
// const toDepth = routes.findIndex(v => v.path === to)
|
||||
// const fromDepth = routes.findIndex(v => v.path === from)
|
||||
// transitionName = toDepth > fromDepth ? 'go' : 'back'
|
||||
// console.log('transitionName', transitionName, toDepth, fromDepth)
|
||||
// console.log('watch', to, from)
|
||||
// //footer下面的5个按钮,对跳不要用动画
|
||||
// let noAnimation = [
|
||||
// '/pc/practice',
|
||||
// '/pc/dict',
|
||||
// '/mobile',
|
||||
// '/'
|
||||
// ]
|
||||
// if (noAnimation.indexOf(from) !== -1 && noAnimation.indexOf(to) !== -1) {
|
||||
// return transitionName = ''
|
||||
// }
|
||||
//
|
||||
// const toDepth = routes.findIndex(v => v.path === to)
|
||||
// const fromDepth = routes.findIndex(v => v.path === from)
|
||||
// transitionName = toDepth > fromDepth ? 'go' : 'back'
|
||||
// console.log('transitionName', transitionName, toDepth, fromDepth)
|
||||
// })
|
||||
</script>
|
||||
|
||||
@@ -109,4 +121,8 @@ onMounted(init)
|
||||
<!-- </transition>-->
|
||||
<!-- </router-view>-->
|
||||
<router-view></router-view>
|
||||
<MigrateDialog
|
||||
v-model="showTransfer"
|
||||
@ok="init"
|
||||
/>
|
||||
</template>
|
||||
@@ -7,13 +7,11 @@ import BaseInput from "@/components/base/BaseInput.vue";
|
||||
interface IProps {
|
||||
list: Article[];
|
||||
showTranslate?: boolean;
|
||||
activeId: string | number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
list: () => [] as Article[],
|
||||
showTranslate: true,
|
||||
activeId: ""
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -79,7 +77,10 @@ defineExpose({ scrollToBottom, scrollToItem })
|
||||
</template>
|
||||
</BaseInput>
|
||||
</div>
|
||||
<BaseList ref="listRef" @click="(e: any) => emit('click', e)" :list="localList" v-bind="$attrs">
|
||||
<BaseList ref="listRef"
|
||||
@click="(e: any) => emit('click', e)"
|
||||
:list="localList"
|
||||
v-bind="$attrs">
|
||||
<template v-slot:prefix="{ item, index }">
|
||||
<slot name="prefix" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
|
||||
@@ -5,13 +5,13 @@ import { nextTick, watch } from 'vue'
|
||||
const props = withDefaults(defineProps<{
|
||||
list?: any[],
|
||||
activeIndex?: number,
|
||||
activeId?: number,
|
||||
activeId?: number | string,
|
||||
isActive?: boolean
|
||||
static?: boolean
|
||||
}>(), {
|
||||
list: [],
|
||||
activeIndex: -1,
|
||||
activeId: null,
|
||||
activeId: '',
|
||||
isActive: false,
|
||||
static: true
|
||||
})
|
||||
@@ -94,7 +94,7 @@ function scrollToItem(index: number) {
|
||||
|
||||
function itemIsActive(item: any, index: number) {
|
||||
return props.activeId ?
|
||||
props.activeId === item.id
|
||||
props.activeId == item.id
|
||||
: props.activeIndex === index
|
||||
}
|
||||
|
||||
|
||||
115
src/pages/MigrateDialog.vue
Normal file
115
src/pages/MigrateDialog.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {Origin} from "@/config/env.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {set} from 'idb-keyval'
|
||||
import {defineAsyncComponent} from "vue";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
const model = defineModel()
|
||||
|
||||
const emit = defineEmits<{ ok: [] }>()
|
||||
|
||||
async function migrateFromOldSite() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 旧域名地址
|
||||
var OLD_ORIGIN = 'https://2study.top';
|
||||
// 需要迁移的 IndexedDB key
|
||||
var IDB_KEYS = [
|
||||
'type-words-app-version',
|
||||
'typing-word-dict',
|
||||
'typing-word-setting',
|
||||
'typing-word-files'
|
||||
];
|
||||
// 需要迁移的 localStorage key
|
||||
var LS_KEYS = [
|
||||
'PracticeSaveWord',
|
||||
'PracticeSaveArticle'
|
||||
];
|
||||
const migrateWin = window.open(`${OLD_ORIGIN}/migrate.html`, '_blank', 'width=400,height=400');
|
||||
|
||||
if (!migrateWin) return reject('弹窗被阻止,请在网址输入栏最右边,点击允许弹窗');
|
||||
|
||||
async function onMessage(event) {
|
||||
if (event.origin !== OLD_ORIGIN) return;
|
||||
if (event.data?.type !== 'MIGRATION_RESULT') return;
|
||||
const payload = event.data.payload;
|
||||
console.log('payload', payload);
|
||||
|
||||
// 写入 localStorage
|
||||
LS_KEYS.forEach(key => {
|
||||
if (payload.localStorage[key] !== undefined) {
|
||||
localStorage.setItem(key, payload.localStorage[key]);
|
||||
}
|
||||
});
|
||||
|
||||
// 写入 IndexedDB
|
||||
for (let key of IDB_KEYS) {
|
||||
if (payload.indexedDB[key] !== undefined) {
|
||||
await set(key, payload.indexedDB[key]);
|
||||
}
|
||||
}
|
||||
|
||||
window.removeEventListener('message', onMessage);
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
window.addEventListener('message', onMessage);
|
||||
|
||||
// 等窗口加载完毕后发请求
|
||||
const timer = setInterval(() => {
|
||||
if (!migrateWin || migrateWin.closed) {
|
||||
clearInterval(timer);
|
||||
reject('迁移窗口已关闭');
|
||||
} else {
|
||||
try {
|
||||
migrateWin.postMessage({type: 'REQUEST_MIGRATION_DATA'}, OLD_ORIGIN);
|
||||
} catch (e) {
|
||||
// 跨域安全错误忽略,等窗口完全加载后再试
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
async function transfer() {
|
||||
try {
|
||||
await migrateFromOldSite();
|
||||
localStorage.setItem('__migrated_from_2study_top__', '1');
|
||||
console.log('迁移完成');
|
||||
Toast.success('迁移完成')
|
||||
model.value = false
|
||||
emit('ok')
|
||||
|
||||
} catch (e) {
|
||||
Toast.error('迁移失败:' + e)
|
||||
console.error('迁移失败', e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model="model" title="迁移数据">
|
||||
<div class="px-4 flex-col center text-align-center w-100">
|
||||
<h2>
|
||||
本网站已启用新域名 <span class="color-blue">{{ Origin }}</span>
|
||||
</h2>
|
||||
<h3>
|
||||
老域名即将停用,由于浏览器安全限制,新老网站数据无法互通,需要您手动点击转移数据
|
||||
</h3>
|
||||
<h3>
|
||||
<BaseButton
|
||||
size="large"
|
||||
@click="transfer">
|
||||
转移数据
|
||||
</BaseButton>
|
||||
</h3>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -42,7 +42,7 @@ const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const settingStore = useSettingStore()
|
||||
const statStore = usePracticeStore()
|
||||
const { toggleTheme } = useTheme()
|
||||
const {toggleTheme} = useTheme()
|
||||
|
||||
let articleData = $ref({
|
||||
list: [],
|
||||
@@ -132,6 +132,7 @@ async function init() {
|
||||
router.push('/articles')
|
||||
}
|
||||
}
|
||||
|
||||
const initAudio = () => {
|
||||
_nextTick(() => {
|
||||
audioRef.volume = settingStore.articleSoundVolume / 100
|
||||
@@ -154,11 +155,11 @@ const handleSpeedUpdate = (speed: number) => {
|
||||
|
||||
watch(() => store.load, (n) => {
|
||||
if (n && loading) init()
|
||||
}, { immediate: true })
|
||||
}, {immediate: true})
|
||||
|
||||
watch(() => settingStore.$state, (n) => {
|
||||
initAudio()
|
||||
}, { immediate: true, deep: true })
|
||||
}, {immediate: true, deep: true})
|
||||
|
||||
onMounted(() => {
|
||||
if (store.sbook?.articles?.length) {
|
||||
@@ -190,9 +191,9 @@ function savePracticeData(init = true, regenerate = true) {
|
||||
let data = obj.val
|
||||
//如果全是0,说明未进行练习,直接重置
|
||||
if (
|
||||
data.practiceData.sectionIndex === 0 &&
|
||||
data.practiceData.sentenceIndex === 0 &&
|
||||
data.practiceData.wordIndex === 0
|
||||
data.practiceData.sectionIndex === 0 &&
|
||||
data.practiceData.sentenceIndex === 0 &&
|
||||
data.practiceData.wordIndex === 0
|
||||
) {
|
||||
throw new Error()
|
||||
}
|
||||
@@ -262,6 +263,10 @@ function setArticle(val: Article) {
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => articleData.article.id, n => {
|
||||
console.log('articleData.article.id', n)
|
||||
})
|
||||
|
||||
async function complete() {
|
||||
clearInterval(timer)
|
||||
setTimeout(() => {
|
||||
@@ -279,7 +284,7 @@ async function complete() {
|
||||
}
|
||||
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await addStat({ ...data, type: 'article' })
|
||||
let res = await addStat({...data, type: 'article'})
|
||||
if (!res.success) {
|
||||
Toast.error(res.msg)
|
||||
}
|
||||
@@ -438,7 +443,8 @@ onUnmounted(() => {
|
||||
timer && clearInterval(timer)
|
||||
})
|
||||
|
||||
const { playSentenceAudio } = usePlaySentenceAudio()
|
||||
const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
|
||||
function play2(e) {
|
||||
_nextTick(() => {
|
||||
if (settingStore.articleSound || e.handle) {
|
||||
@@ -485,7 +491,7 @@ provide('currentPractice', currentPractice)
|
||||
:static="false"
|
||||
:show-translate="settingStore.translate"
|
||||
@click="changeArticle"
|
||||
:active-id="articleData.article.id"
|
||||
:active-id="articleData.article.id??''"
|
||||
:list="articleData.list ">
|
||||
<template v-slot:suffix="{item,index}">
|
||||
<BaseIcon
|
||||
|
||||
Reference in New Issue
Block a user