feat: fix Slider component display bug and add IE browser detection prompt

add remove infrequently used js libraries
This commit is contained in:
Zyronon
2025-11-30 02:24:38 +08:00
parent eb2ac5a555
commit 47dd667dbd
15 changed files with 668 additions and 584 deletions

View File

@@ -78,6 +78,47 @@
<div>你需要启用 JavaScript 来运行 Type Words.</div>
</noscript>
<div id="app"></div>
<script>
(function(){
var ua = navigator.userAgent || ''
var isIE = !!document.documentMode || /MSIE|Trident/i.test(ua)
if (!isIE) return
var style = document.createElement('style')
style.type = 'text/css'
style.appendChild(document.createTextNode(
'.ie-mask{position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,.35);z-index:9998}'+
'.ie-dialog{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:28rem;max-width:90vw;background:#fff;color:#111;border-radius:.6rem;box-shadow:0 10px 30px rgba(0,0,0,.15);z-index:9999;padding:1.2rem}'+
'.ie-dialog .title{font-size:1.2rem;font-weight:700;margin-bottom:.6rem}'+
'.ie-dialog .desc{font-size:.95rem;line-height:1.6;color:#555}'+
'.ie-dialog .actions{display:flex;justify-content:flex-end;margin-top:1rem}'+
'.ie-dialog .actions > * + *{margin-left:.6rem}'+
'.ie-dialog .btn{display:inline-flex;align-items:center;justify-content:center;height:2.2rem;padding:0 1rem;border-radius:.4rem;background:#0C8CE9;color:#fff;text-decoration:none}'+
'.ie-dialog .btn-secondary{display:inline-flex;align-items:center;justify-content:center;height:2.2rem;padding:0 .9rem;border-radius:.4rem;background:#eee;color:#333;border:1px solid #ddd}'+
'@media (prefers-color-scheme: dark){.ie-dialog{background:#1e1f22;color:#e6e6e6}.ie-dialog .desc{color:#c6c6c6}.ie-dialog .btn-secondary{background:#2a2b2f;color:#e6e6e6;border-color:#3a3b3f}}'
))
document.head.appendChild(style)
var mask = document.createElement('div')
mask.className = 'ie-mask'
var dialog = document.createElement('div')
dialog.className = 'ie-dialog'
dialog.innerHTML = '<div class="title">不支持 IE 浏览器</div>'+
'<div class="desc">Type Words 使用现代技术构建,请使用 Chrome、Edge、Firefox 或 Safari 等现代浏览器访问。</div>'+
'<div class="actions">'+
'<a class="btn" href="https://www.google.cn/chrome/" target="_blank" rel="noreferrer">下载 Chrome</a>'+
'<button class="btn-secondary" type="button">我知道了</button>'+
'</div>'
function close(){
try{document.body.removeChild(mask)}catch(e){}
try{document.body.removeChild(dialog)}catch(e){}
}
mask.addEventListener('click', close)
var btn = null
try{btn = dialog.querySelector('.btn-secondary')}catch(e){}
if (btn) btn.addEventListener('click', close)
document.body.appendChild(mask)
document.body.appendChild(dialog)
})()
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -21,7 +21,6 @@
"@floating-ui/dom": "^1.7.4",
"@imengyu/vue3-context-menu": "^1.5.1",
"@vueuse/core": "14.0.0-alpha.0",
"@zumer/snapdom": "^2.0.0",
"axios": "^1.12.0",
"compromise": "^14.14.4",
"copy-to-clipboard": "^3.3.3",
@@ -32,7 +31,6 @@
"mitt": "^3.0.1",
"nanoid": "^5.1.5",
"pinia": "^3.0.3",
"shepherd.js": "^14.5.1",
"string-comparison": "^1.3.0",
"vue": "^3.5.17",
"vue-router": "^4.5.1",

32
pnpm-lock.yaml generated
View File

@@ -17,9 +17,6 @@ importers:
'@vueuse/core':
specifier: 14.0.0-alpha.0
version: 14.0.0-alpha.0(vue@3.5.18(typescript@5.9.2))
'@zumer/snapdom':
specifier: ^2.0.0
version: 2.0.0
axios:
specifier: ^1.12.0
version: 1.13.2
@@ -50,9 +47,6 @@ importers:
pinia:
specifier: ^3.0.3
version: 3.0.3(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2))
shepherd.js:
specifier: ^14.5.1
version: 14.5.1
string-comparison:
specifier: ^1.3.0
version: 1.3.0
@@ -866,9 +860,6 @@ packages:
cpu: [x64]
os: [win32]
'@scarf/scarf@1.4.0':
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
'@tybys/wasm-util@0.10.0':
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
@@ -1272,9 +1263,6 @@ packages:
peerDependencies:
vue: ^3.5.0
'@zumer/snapdom@2.0.0':
resolution: {integrity: sha512-e/fkm5wCUd+9CssUIyH09xTeR4DvRTmZLGVOlnXLhr4HeI7sdc6ed8cLPiZKFtiQDRiwD3EKx4RIUrpQOJQY7A==}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
@@ -1807,10 +1795,6 @@ packages:
dedent@0.7.0:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
deepmerge-ts@7.1.5:
resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
engines: {node: '>=16.0.0'}
default-compare@1.0.0:
resolution: {integrity: sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==}
engines: {node: '>=0.10.0'}
@@ -3224,10 +3208,6 @@ packages:
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
engines: {node: '>=0.10.0'}
shepherd.js@14.5.1:
resolution: {integrity: sha512-VuvPvLG1QjNOLP7AIm2HGyfmxEIz8QdskvWOHwUcxLDibYWjLRBmCWd8LSL5FlwhBW7D/GU+3gNVC/ASxAWdxg==}
engines: {node: 18.* || >= 20}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
@@ -4492,8 +4472,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.46.2':
optional: true
'@scarf/scarf@1.4.0': {}
'@tybys/wasm-util@0.10.0':
dependencies:
tslib: 2.8.1
@@ -5095,8 +5073,6 @@ snapshots:
dependencies:
vue: 3.5.18(typescript@5.9.2)
'@zumer/snapdom@2.0.0': {}
acorn@8.15.0: {}
address@1.2.2: {}
@@ -5683,8 +5659,6 @@ snapshots:
dedent@0.7.0: {}
deepmerge-ts@7.1.5: {}
default-compare@1.0.0:
dependencies:
kind-of: 5.1.0
@@ -7224,12 +7198,6 @@ snapshots:
is-plain-object: 2.0.4
split-string: 3.1.0
shepherd.js@14.5.1:
dependencies:
'@floating-ui/dom': 1.7.4
'@scarf/scarf': 1.4.0
deepmerge-ts: 7.1.5
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0

View File

@@ -0,0 +1,10 @@
.shepherd-button{background:#3288e6;border:0;border-radius:3px;color:hsla(0,0%,100%,.75);cursor:pointer;margin-right:.5rem;padding:.5rem 1.5rem;transition:all .5s ease}.shepherd-button:not(:disabled):hover{background:#196fcc;color:hsla(0,0%,100%,.75)}.shepherd-button.shepherd-button-secondary{background:#f1f2f3;color:rgba(0,0,0,.75)}.shepherd-button.shepherd-button-secondary:not(:disabled):hover{background:#d6d9db;color:rgba(0,0,0,.75)}.shepherd-button:disabled{cursor:not-allowed}
.shepherd-footer{border-bottom-left-radius:5px;border-bottom-right-radius:5px;display:flex;justify-content:flex-end;padding:0 .75rem .75rem}.shepherd-footer .shepherd-button:last-child{margin-right:0}
.shepherd-cancel-icon{background:transparent;border:none;color:hsla(0,0%,50%,.75);cursor:pointer;font-size:2em;font-weight:400;margin:0;padding:0;transition:color .5s ease}.shepherd-cancel-icon:hover{color:rgba(0,0,0,.75)}.shepherd-has-title .shepherd-content .shepherd-cancel-icon{color:hsla(0,0%,50%,.75)}.shepherd-has-title .shepherd-content .shepherd-cancel-icon:hover{color:rgba(0,0,0,.75)}
.shepherd-title{color:rgba(0,0,0,.75);display:flex;flex:1 0 auto;font-size:1rem;font-weight:400;margin:0;padding:0}
.shepherd-header{align-items:center;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;justify-content:flex-end;line-height:2em;padding:.75rem .75rem 0}.shepherd-has-title .shepherd-content .shepherd-header{background:#e6e6e6;padding:1em}
.shepherd-text{color:rgba(0,0,0,.75);font-size:1rem;line-height:1.3em;padding:.75em}.shepherd-text p{margin-top:0}.shepherd-text p:last-child{margin-bottom:0}
.shepherd-content{border-radius:5px;outline:none;padding:0}
.shepherd-element{background:#fff;border:none;border-radius:5px;box-shadow:0 1px 4px rgba(0,0,0,.2);margin:0;max-width:400px;opacity:0;outline:none;padding:0;transition:opacity .3s,visibility .3s;visibility:hidden;width:100%;z-index:9999}.shepherd-enabled.shepherd-element{opacity:1;visibility:visible}.shepherd-element[data-popper-reference-hidden]:not(.shepherd-centered){opacity:0;pointer-events:none;visibility:hidden}.shepherd-element,.shepherd-element *,.shepherd-element :after,.shepherd-element :before{box-sizing:border-box}.shepherd-arrow,.shepherd-arrow:before{height:16px;position:absolute;width:16px;z-index:-1}.shepherd-arrow:before{background:#fff;content:"";transform:rotate(45deg)}.shepherd-element[data-popper-placement^=top]>.shepherd-arrow{bottom:-8px}.shepherd-element[data-popper-placement^=bottom]>.shepherd-arrow{top:-8px}.shepherd-element[data-popper-placement^=left]>.shepherd-arrow{right:-8px}.shepherd-element[data-popper-placement^=right]>.shepherd-arrow{left:-8px}.shepherd-element.shepherd-centered>.shepherd-arrow{opacity:0}.shepherd-element.shepherd-has-title[data-popper-placement^=bottom]>.shepherd-arrow:before{background-color:#e6e6e6}.shepherd-target-click-disabled.shepherd-enabled.shepherd-target,.shepherd-target-click-disabled.shepherd-enabled.shepherd-target *{pointer-events:none}
.shepherd-modal-overlay-container{height:0;left:0;opacity:0;overflow:hidden;pointer-events:none;position:fixed;top:0;transition:all .3s ease-out,height 0s .3s,opacity .3s 0s;width:100vw;z-index:9997}.shepherd-modal-overlay-container.shepherd-modal-is-visible{height:100vh;opacity:.5;transform:translateZ(0);transition:all .3s ease-out,height 0s 0s,opacity .3s 0s}.shepherd-modal-overlay-container.shepherd-modal-is-visible path{pointer-events:all}

View File

@@ -1,5 +1,5 @@
@use "anim" as *;
@use 'shepherd.js/dist/css/shepherd.css';
@use 'shepherd.css';
:root {
--color-reverse-white: white;

View File

@@ -1,15 +1,14 @@
<script setup lang="ts">
import { APP_NAME, GITHUB, Origin } from "@/config/env.ts";
import { APP_NAME, GITHUB, LIB_JS_URL, Origin } from "@/config/env.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import { defineAsyncComponent, watch } from "vue";
import { usePracticeStore } from "@/stores/practice.ts";
import { useBaseStore } from "@/stores/base.ts";
import { msToHourMinute } from "@/utils";
import { loadJsLib, msToHourMinute } from "@/utils";
import dayjs from "dayjs";
import Toast from "@/components/base/toast/Toast.ts";
import { useUserStore } from "@/stores/user.ts";
import Progress from "@/components/base/Progress.vue";
import { snapdom } from "@zumer/snapdom";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
@@ -21,6 +20,8 @@ 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)
// 计算学习统计数据
@@ -47,6 +48,8 @@ watch(() => showShareDialog, (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'})
if (!blob) throw new Error('capture failed')
@@ -59,12 +62,17 @@ async function copyImageToClipboard() {
} catch (error) {
Toast.error('复制失败!')
await downloadImage()
} finally {
loading1 = false
}
}
// 下载图片
async function downloadImage() {
loading2 = true
const snapdom = await loadJsLib('snapdom',LIB_JS_URL.SNAPDOM);
snapdom.download(posterEl, {scale: 2})
loading2 = false
}
let imgIndex = $ref(Math.floor(Math.random() * 10))
@@ -127,7 +135,8 @@ const sentence = $computed(() => {
<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]">
<a :href="GITHUB" target="_blank" rel="noreferrer" aria-label="GITHUB 项目地址"
class="color-[--color-reverse-black]">
<BaseIcon>
<IconSimpleIconsGithub/>
</BaseIcon>
@@ -264,13 +273,15 @@ const sentence = $computed(() => {
<!-- 分享战绩 -->
<div @click="copyImageToClipboard"
class="flex items-center justify-start gap-space px-6 py-3 bg-gradient-to-r from-green-500 to-green-600 text-white cp rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-200">
<IconFluentCopy20Regular class="w-5 h-5"/>
<IconEosIconsLoading class="text-xl" v-if="loading1"/>
<IconFluentCopy20Regular class="w-5 h-5" v-else/>
<span class="font-medium">复制到剪贴板</span>
</div>
<div @click="downloadImage"
class="flex items-center justify-start gap-space px-6 py-3 bg-gradient-to-r from-purple-500 to-purple-600 text-white cp rounded-lg hover:from-purple-600 hover:to-purple-700 transition-all duration-200">
<IconFluentArrowDownload20Regular class="w-5 h-5"/>
<IconEosIconsLoading class="text-xl" v-if="loading2"/>
<IconFluentArrowDownload20Regular class="w-5 h-5" v-else/>
<span class="font-medium">保存高清海报</span>
</div>
</div>

View File

@@ -84,8 +84,15 @@ export const TourConfig = {
modalOverlayOpeningPadding: 10,
modalOverlayOpeningRadius: 6,
floatingUIOptions: {
middleware: [offset({mainAxis:30})]
middleware: [offset({mainAxis: 30})]
},
},
total: 7
}
export const LIB_JS_URL = {
SHEPHERD: import.meta.env.MODE === 'development' ?
'https://cdn.jsdelivr.net/npm/shepherd.js@14.5.1/dist/esm/shepherd.mjs'
: Origin + '/libs/Shepherd.14.5.1.mjs',
SNAPDOM: `${Origin}/libs/snapdom.min.js`
}

View File

@@ -1,27 +1,35 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import {useRouter} from "vue-router";
import { useBaseStore } from "@/stores/base.ts";
import { useRouter } from "vue-router";
import BasePage from "@/components/BasePage.vue";
import {_getDictDataByUrl, _nextTick, isMobile, msToHourMinute, resourceWrap, total, useNav} from "@/utils";
import {DictResource, DictType} from "@/types/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {
_getDictDataByUrl,
_nextTick,
isMobile,
loadJsLib,
msToHourMinute,
resourceWrap,
total,
useNav
} from "@/utils";
import { DictResource, DictType } from "@/types/types.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Book from "@/components/Book.vue";
import Progress from '@/components/base/Progress.vue';
import Toast from '@/components/base/toast/Toast.ts'
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import {watch} from "vue";
import {getDefaultDict} from "@/types/func.ts";
import { watch } from "vue";
import { getDefaultDict } from "@/types/func.ts";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import isoWeek from 'dayjs/plugin/isoWeek'
import {useFetch} from "@vueuse/core";
import {AppEnv, DICT_LIST, Host, PracticeSaveArticleKey, TourConfig} from "@/config/env.ts";
import {myDictList} from "@/apis";
import Shepherd from "shepherd.js";
import {useSettingStore} from "@/stores/setting.ts";
import { useFetch } from "@vueuse/core";
import { AppEnv, DICT_LIST, Host, LIB_JS_URL, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
import { myDictList } from "@/apis";
import { useSettingStore } from "@/stores/setting.ts";
dayjs.extend(isoWeek)
dayjs.extend(isBetween);
@@ -73,7 +81,8 @@ async function init() {
watch(() => store?.sbook?.id, (n) => {
console.log('n', n)
if (!n) {
_nextTick(() => {
_nextTick(async () => {
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');

View File

@@ -18,7 +18,7 @@ import {
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import Toast from '@/components/base/toast/Toast.ts'
import {_getDictDataByUrl, _nextTick, cloneDeep, isMobile, msToMinute, resourceWrap, total} from "@/utils";
import { _getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total } from "@/utils";
import { usePracticeStore } from "@/stores/practice.ts";
import { useArticleOptions } from "@/hooks/dict.ts";
import { genArticleSectionData, usePlaySentenceAudio } from "@/hooks/article.ts";
@@ -34,10 +34,9 @@ import { useRoute, useRouter } from "vue-router";
import PracticeLayout from "@/components/PracticeLayout.vue";
import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
import VolumeSetting from "@/pages/article/components/VolumeSetting.vue";
import { AppEnv, DICT_LIST, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
import { AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
import { addStat, setDictProp } from "@/apis";
import { useRuntimeStore } from "@/stores/runtime.ts";
import Shepherd from "shepherd.js";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -160,7 +159,8 @@ watch([() => store.load, () => loading], ([a, b]) => {
watch(() => articleData?.article?.id, id => {
if (id) {
_nextTick(() => {
_nextTick(async () => {
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ import { DictId } from "@/types/types.ts";
import BasePage from "@/components/BasePage.vue";
import { computed, onMounted, reactive, ref, shallowReactive, watch } from "vue";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { _getDictDataByUrl, _nextTick, convertToWord, isMobile, loadJsLib, sleep, useNav } from "@/utils";
import { _getDictDataByUrl, _nextTick, convertToWord, isMobile, loadJsLib, useNav } from "@/utils";
import { nanoid } from "nanoid";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseTable from "@/components/BaseTable.vue";
@@ -26,9 +26,8 @@ import { getCurrentStudyWord } from "@/hooks/dict.ts";
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { MessageBox } from "@/utils/MessageBox.tsx";
import { AppEnv, Origin, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { AppEnv, LIB_JS_URL, Origin, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { detail } from "@/apis";
import Shepherd from "shepherd.js";
const runtimeStore = useRuntimeStore()
const base = useBaseStore()
@@ -388,7 +387,8 @@ function searchWord() {
watch(() => loading, (val) => {
if (!val) return
_nextTick(() => {
_nextTick(async () => {
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {_nextTick, groupBy, isMobile, resourceWrap, useNav} from "@/utils";
import { _nextTick, groupBy, isMobile, loadJsLib, resourceWrap, useNav } from "@/utils";
import BasePage from "@/components/BasePage.vue";
import { DictResource } from "@/types/types.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
@@ -11,12 +11,11 @@ import BackIcon from "@/components/BackIcon.vue";
import DictGroup from "@/components/list/DictGroup.vue";
import { useBaseStore } from "@/stores/base.ts";
import { useRouter } from "vue-router";
import { computed, onMounted, watch } from "vue";
import { computed, watch } from "vue";
import { getDefaultDict } from "@/types/func.ts";
import { useFetch } from "@vueuse/core";
import { DICT_LIST, TourConfig } from "@/config/env.ts";
import { DICT_LIST, LIB_JS_URL, TourConfig } from "@/config/env.ts";
import BaseInput from "@/components/base/BaseInput.vue";
import Shepherd from "shepherd.js";
import { useSettingStore } from "@/stores/setting.ts";
const {nav} = useNav()
@@ -84,7 +83,8 @@ watch(dict_list, (val) => {
if (!val.length) return
let cet4 = val.find(v => v.id === 'cet4')
if (!cet4) return
_nextTick(() => {
_nextTick(async () => {
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import { onMounted, provide, ref, toRef, watch } from "vue";
import { onMounted, provide, ref, watch } from "vue";
import Statistics from "@/pages/word/Statistics.vue";
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { Dict, PracticeData, WordPracticeType, ShortcutKey, TaskWords, Word, WordPracticeMode } from "@/types/types.ts";
import { Dict, PracticeData, ShortcutKey, TaskWords, Word, WordPracticeMode, WordPracticeType } from "@/types/types.ts";
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import { getCurrentStudyWord, useWordOptions } from "@/hooks/dict.ts";
import {_getDictDataByUrl, _nextTick, cloneDeep, isMobile, resourceWrap, shuffle} from "@/utils";
import { _getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, resourceWrap, shuffle } from "@/utils";
import { useRoute, useRouter } from "vue-router";
import Footer from "@/pages/word/components/Footer.vue";
import Panel from "@/components/Panel.vue";
@@ -25,11 +25,9 @@ import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
import ConflictNotice from "@/components/ConflictNotice.vue";
import PracticeLayout from "@/components/PracticeLayout.vue";
import { DICT_LIST, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { DICT_LIST, LIB_JS_URL, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { ToastInstance } from "@/components/base/toast/type.ts";
import { watchOnce } from "@vueuse/core";
import Shepherd from "shepherd.js";
import { offset } from '@floating-ui/dom';
const {
isWordCollect,
@@ -117,7 +115,8 @@ onMounted(() => {
watchOnce(() => data.words.length, (newVal, oldVal) => {
//如果是从无值变有值,代表是开始
if (!oldVal && newVal) {
_nextTick(() => {
_nextTick(async () => {
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');

View File

@@ -2,10 +2,19 @@
import { useBaseStore } from "@/stores/base.ts";
import { useRouter } from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
import {_getAccomplishDate, _getDictDataByUrl, _nextTick, isMobile, resourceWrap, shuffle, useNav} from "@/utils";
import {
_getAccomplishDate,
_getDictDataByUrl,
_nextTick,
isMobile,
loadJsLib,
resourceWrap,
shuffle,
useNav
} from "@/utils";
import BasePage from "@/components/BasePage.vue";
import { DictResource, WordPracticeMode } from "@/types/types.ts";
import { onMounted, watch } from "vue";
import { watch } from "vue";
import { getCurrentStudyWord } from "@/hooks/dict.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import Book from "@/components/Book.vue";
@@ -19,11 +28,10 @@ import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { useFetch } from "@vueuse/core";
import { AppEnv, DICT_LIST, Host, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { AppEnv, DICT_LIST, Host, LIB_JS_URL, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { myDictList } from "@/apis";
import PracticeWordListDialog from "@/pages/word/components/PracticeWordListDialog.vue";
import ShufflePracticeSettingDialog from "@/pages/word/components/ShufflePracticeSettingDialog.vue";
import Shepherd from "shepherd.js";
import SettingDialog from "@/pages/word/components/SettingDialog.vue";
@@ -45,7 +53,8 @@ let currentStudy = $ref({
watch(() => store.load, n => {
if (n) {
init()
_nextTick(() => {
_nextTick(async () => {
const Shepherd = await loadJsLib('Shepherd', LIB_JS_URL.SHEPHERD);
const tour = new Shepherd.Tour(TourConfig);
tour.on('cancel', () => {
localStorage.setItem('tour-guide', '1');
@@ -237,7 +246,7 @@ let isNewHost = $ref(window.location.host === Host)
2study.top 域名将在不久后停止使用
</div>
<SettingDialog/>
<!-- <SettingDialog/>-->
<div class="card flex flex-col md:flex-row gap-8">
<div class="flex-1 w-full flex flex-col justify-between">

View File

@@ -1,13 +1,13 @@
import {BaseState, getDefaultBaseState, useBaseStore} from "@/stores/base.ts";
import {getDefaultSettingState, SettingState} from "@/stores/setting.ts";
import {Dict, DictId, DictResource, DictType} from "@/types/types.ts";
import {useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import { BaseState, getDefaultBaseState, useBaseStore } from "@/stores/base.ts";
import { getDefaultSettingState, SettingState } from "@/stores/setting.ts";
import { Dict, DictId, DictResource, DictType } from "@/types/types.ts";
import { useRouter } from "vue-router";
import { useRuntimeStore } from "@/stores/runtime.ts";
import dayjs from 'dayjs'
import {AppEnv, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/config/env.ts";
import {nextTick} from "vue";
import { AppEnv, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
import { nextTick } from "vue";
import Toast from '@/components/base/toast/Toast.ts'
import {getDefaultDict, getDefaultWord} from "@/types/func.ts";
import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
import duration from "dayjs/plugin/duration";
dayjs.extend(duration);
@@ -424,8 +424,42 @@ export async function loadJsLib(key: string, url: string) {
if (window[key]) return window[key];
return new Promise((resolve, reject) => {
const script = document.createElement("script");
// 判断是否是 .mjs 文件,如果是,则使用 type="module"
if (url.endsWith(".mjs")) {
script.type = "module"; // 需要加上 type="module"
script.src = url;
script.onload = async () => {
try {
// 使用动态 import 加载模块
const module = await import(url); // 动态导入 .mjs 模块
window[key] = module.default || module; // 将模块挂到 window 对象
resolve(window[key]);
} catch (err) {
reject(`${key} 加载失败: ${err.message}`);
}
};
} else {
// 如果是非 .mjs 文件,直接按原方式加载
script.src = url;
script.onload = () => resolve(window[key]);
}
script.onerror = () => reject(key + " 加载失败");
document.head.appendChild(script);
});
}
export async function loadJsLib2(key: string, url: string, module: boolean = false) {
if (window[key]) return window[key];
return new Promise((resolve, reject) => {
const script = document.createElement("script");
if (module) {
script.type = 'module'
}
script.src = url;
script.onload = () => resolve(window[key]);
script.onload = () => {
console.log('key', key)
resolve(window[key])
};
script.onerror = () => reject(key + ' 加载失败')
document.head.appendChild(script);
});