|
@@ -1,10 +1,27 @@
|
|
|
<template>
|
|
<template>
|
|
|
<template v-if="slides.length">
|
|
<template v-if="slides.length">
|
|
|
- <Screen v-if="viewMode !== 'student'" v-show="screening"/>
|
|
|
|
|
- <Editor v-if="viewMode === 'editor'" v-show="_isPC && !screening" :courseid="urlParams.courseid"/>
|
|
|
|
|
- <Editor2 v-else-if="viewMode === 'editor2'" v-show="_isPC && !screening" :courseid="urlParams.courseid"/>
|
|
|
|
|
- <Editor3 v-else-if="viewMode === 'editor3'" v-show="_isPC && !screening" :courseid="urlParams.courseid"/>
|
|
|
|
|
- <Student v-else-if="viewMode === 'student'" :courseid="urlParams.courseid" :type="urlParams.type" :userid="urlParams.userid" :oid="urlParams.oid" :org="urlParams.org" :cid="urlParams.cid" />
|
|
|
|
|
|
|
+ <Screen v-if="viewMode !== 'student' && screening" />
|
|
|
|
|
+ <Editor
|
|
|
|
|
+ v-if="viewMode === 'editor' && _isPC && !screening"
|
|
|
|
|
+ :courseid="urlParams.courseid"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Editor2
|
|
|
|
|
+ v-else-if="viewMode === 'editor2' && _isPC && !screening"
|
|
|
|
|
+ :courseid="urlParams.courseid"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Editor3
|
|
|
|
|
+ v-else-if="viewMode === 'editor3' && _isPC && !screening"
|
|
|
|
|
+ :courseid="urlParams.courseid"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Student
|
|
|
|
|
+ v-else-if="viewMode === 'student'"
|
|
|
|
|
+ :courseid="urlParams.courseid"
|
|
|
|
|
+ :type="urlParams.type"
|
|
|
|
|
+ :userid="urlParams.userid"
|
|
|
|
|
+ :oid="urlParams.oid"
|
|
|
|
|
+ :org="urlParams.org"
|
|
|
|
|
+ :cid="urlParams.cid"
|
|
|
|
|
+ />
|
|
|
<Mobile v-else />
|
|
<Mobile v-else />
|
|
|
</template>
|
|
</template>
|
|
|
<FullscreenSpin :tip="lang.ssInitDataWait" v-else loading :mask="false" />
|
|
<FullscreenSpin :tip="lang.ssInitDataWait" v-else loading :mask="false" />
|
|
@@ -13,128 +30,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
-import { onMounted, ref, provide } from 'vue'
|
|
|
|
|
-import { storeToRefs } from 'pinia'
|
|
|
|
|
-import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from '@/store'
|
|
|
|
|
-import { lang } from '@/main'
|
|
|
|
|
-import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage'
|
|
|
|
|
-import { deleteDiscardedDB } from '@/utils/database'
|
|
|
|
|
-import { isPC } from '@/utils/common'
|
|
|
|
|
-import api from '@/services'
|
|
|
|
|
-
|
|
|
|
|
-import Editor from './views/Editor/index.vue'
|
|
|
|
|
-import Editor2 from './views/Editor/index2.vue'
|
|
|
|
|
-import Editor3 from './views/Editor/index3.vue'
|
|
|
|
|
-import Screen from './views/Screen/index.vue'
|
|
|
|
|
-import Mobile from './views/Mobile/index.vue'
|
|
|
|
|
-import Student from './views/Student/index.vue'
|
|
|
|
|
-import FullscreenSpin from '@/components/FullscreenSpin.vue'
|
|
|
|
|
-
|
|
|
|
|
-const _isPC = isPC()
|
|
|
|
|
-
|
|
|
|
|
-const mainStore = useMainStore()
|
|
|
|
|
-const slidesStore = useSlidesStore()
|
|
|
|
|
-const snapshotStore = useSnapshotStore()
|
|
|
|
|
-const { databaseId } = storeToRefs(mainStore)
|
|
|
|
|
-const { slides } = storeToRefs(slidesStore)
|
|
|
|
|
-const { screening } = storeToRefs(useScreenStore())
|
|
|
|
|
|
|
+import { onMounted, ref, provide } from "vue";
|
|
|
|
|
+import { storeToRefs } from "pinia";
|
|
|
|
|
+import {
|
|
|
|
|
+ useScreenStore,
|
|
|
|
|
+ useMainStore,
|
|
|
|
|
+ useSnapshotStore,
|
|
|
|
|
+ useSlidesStore,
|
|
|
|
|
+} from "@/store";
|
|
|
|
|
+import { lang } from "@/main";
|
|
|
|
|
+import { LOCALSTORAGE_KEY_DISCARDED_DB } from "@/configs/storage";
|
|
|
|
|
+import { deleteDiscardedDB } from "@/utils/database";
|
|
|
|
|
+import { isPC } from "@/utils/common";
|
|
|
|
|
+import api from "@/services";
|
|
|
|
|
+
|
|
|
|
|
+import Editor from "./views/Editor/index.vue";
|
|
|
|
|
+import Editor2 from "./views/Editor/index2.vue";
|
|
|
|
|
+import Editor3 from "./views/Editor/index3.vue";
|
|
|
|
|
+import Screen from "./views/Screen/index.vue";
|
|
|
|
|
+import Mobile from "./views/Mobile/index.vue";
|
|
|
|
|
+import Student from "./views/Student/index.vue";
|
|
|
|
|
+import FullscreenSpin from "@/components/FullscreenSpin.vue";
|
|
|
|
|
+
|
|
|
|
|
+const _isPC = isPC();
|
|
|
|
|
+
|
|
|
|
|
+const mainStore = useMainStore();
|
|
|
|
|
+const slidesStore = useSlidesStore();
|
|
|
|
|
+const snapshotStore = useSnapshotStore();
|
|
|
|
|
+const { databaseId } = storeToRefs(mainStore);
|
|
|
|
|
+const { slides } = storeToRefs(slidesStore);
|
|
|
|
|
+const { screening } = storeToRefs(useScreenStore());
|
|
|
|
|
|
|
|
// 视图模式:'editor', 'student', 'screen'
|
|
// 视图模式:'editor', 'student', 'screen'
|
|
|
// 支持通过URL参数直接访问学生模式
|
|
// 支持通过URL参数直接访问学生模式
|
|
|
const getInitialViewMode = () => {
|
|
const getInitialViewMode = () => {
|
|
|
// 检查URL参数
|
|
// 检查URL参数
|
|
|
- const urlParams = new URLSearchParams(window.location.search)
|
|
|
|
|
- const modeFromUrl = urlParams.get('mode')
|
|
|
|
|
- console.log(modeFromUrl)
|
|
|
|
|
- if (modeFromUrl === 'student') {
|
|
|
|
|
- return 'student'
|
|
|
|
|
|
|
+ const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
|
+ const modeFromUrl = urlParams.get("mode");
|
|
|
|
|
+ console.log(modeFromUrl);
|
|
|
|
|
+ if (modeFromUrl === "student") {
|
|
|
|
|
+ return "student";
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (modeFromUrl === 'editor2') {
|
|
|
|
|
- return 'editor2'
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (modeFromUrl === "editor2") {
|
|
|
|
|
+ return "editor2";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (modeFromUrl === 'editor3') {
|
|
|
|
|
- return 'editor3'
|
|
|
|
|
|
|
+ if (modeFromUrl === "editor3") {
|
|
|
|
|
+ return "editor3";
|
|
|
}
|
|
}
|
|
|
// 检查localStorage
|
|
// 检查localStorage
|
|
|
- const modeFromStorage = localStorage.getItem('viewMode')
|
|
|
|
|
|
|
+ const modeFromStorage = localStorage.getItem("viewMode");
|
|
|
if (modeFromStorage) {
|
|
if (modeFromStorage) {
|
|
|
- return modeFromStorage
|
|
|
|
|
|
|
+ return modeFromStorage;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 默认返回编辑模式
|
|
// 默认返回编辑模式
|
|
|
- return 'editor'
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ return "editor";
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
// 获取URL参数中的courseid和type
|
|
// 获取URL参数中的courseid和type
|
|
|
const getUrlParams = () => {
|
|
const getUrlParams = () => {
|
|
|
- const urlParams = new URLSearchParams(window.location.search)
|
|
|
|
|
|
|
+ const urlParams = new URLSearchParams(window.location.search);
|
|
|
return {
|
|
return {
|
|
|
- courseid: urlParams.get('courseid'),
|
|
|
|
|
- userid: urlParams.get('userid'),
|
|
|
|
|
- oid: urlParams.get('oid'),
|
|
|
|
|
- org: urlParams.get('org'),
|
|
|
|
|
- cid: urlParams.get('cid'),
|
|
|
|
|
- type: urlParams.get('type')
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ courseid: urlParams.get("courseid"),
|
|
|
|
|
+ userid: urlParams.get("userid"),
|
|
|
|
|
+ oid: urlParams.get("oid"),
|
|
|
|
|
+ org: urlParams.get("org"),
|
|
|
|
|
+ cid: urlParams.get("cid"),
|
|
|
|
|
+ type: urlParams.get("type"),
|
|
|
|
|
+ };
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
-const urlParams = getUrlParams()
|
|
|
|
|
|
|
+const urlParams = getUrlParams();
|
|
|
|
|
|
|
|
-const viewMode = ref(getInitialViewMode())
|
|
|
|
|
|
|
+const viewMode = ref(getInitialViewMode());
|
|
|
|
|
|
|
|
// 全局切换视图模式的函数
|
|
// 全局切换视图模式的函数
|
|
|
const switchViewMode = (mode: string) => {
|
|
const switchViewMode = (mode: string) => {
|
|
|
- viewMode.value = mode
|
|
|
|
|
- localStorage.setItem('viewMode', mode)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ viewMode.value = mode;
|
|
|
|
|
+ localStorage.setItem("viewMode", mode);
|
|
|
|
|
+
|
|
|
// 更新URL参数
|
|
// 更新URL参数
|
|
|
- const url = new URL(window.location.href)
|
|
|
|
|
- if (mode === 'student') {
|
|
|
|
|
- url.searchParams.set('mode', 'student')
|
|
|
|
|
|
|
+ const url = new URL(window.location.href);
|
|
|
|
|
+ if (mode === "student") {
|
|
|
|
|
+ url.searchParams.set("mode", "student");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ url.searchParams.delete("mode");
|
|
|
}
|
|
}
|
|
|
- else {
|
|
|
|
|
- url.searchParams.delete('mode')
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 使用 history.pushState 更新URL,不刷新页面
|
|
// 使用 history.pushState 更新URL,不刷新页面
|
|
|
- window.history.pushState({}, '', url.toString())
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ window.history.pushState({}, "", url.toString());
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
// 使用provide提供切换函数,供子组件调用
|
|
// 使用provide提供切换函数,供子组件调用
|
|
|
-provide('switchViewMode', switchViewMode)
|
|
|
|
|
|
|
+provide("switchViewMode", switchViewMode);
|
|
|
|
|
|
|
|
-if (import.meta.env.MODE !== 'development') {
|
|
|
|
|
- window.onbeforeunload = () => false
|
|
|
|
|
|
|
+if (import.meta.env.MODE !== "development") {
|
|
|
|
|
+ window.onbeforeunload = () => false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
- const slides = await api.getFileData('slides')
|
|
|
|
|
- console.log(slides)
|
|
|
|
|
- slidesStore.setSlides(slides)
|
|
|
|
|
|
|
+ const slides = await api.getFileData("slides");
|
|
|
|
|
+ console.log(slides);
|
|
|
|
|
+ slidesStore.setSlides(slides);
|
|
|
// 初始化快照数据库
|
|
// 初始化快照数据库
|
|
|
// await deleteDiscardedDB()
|
|
// await deleteDiscardedDB()
|
|
|
// snapshotStore.initSnapshotDatabase()
|
|
// snapshotStore.initSnapshotDatabase()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 监听视图模式切换事件
|
|
// 监听视图模式切换事件
|
|
|
- window.addEventListener('viewModeChanged', (event: any) => {
|
|
|
|
|
|
|
+ window.addEventListener("viewModeChanged", (event: any) => {
|
|
|
if (event.detail) {
|
|
if (event.detail) {
|
|
|
- switchViewMode(event.detail)
|
|
|
|
|
|
|
+ switchViewMode(event.detail);
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
-})
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
// 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库
|
|
// 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库
|
|
|
-window.addEventListener('beforeunload', () => {
|
|
|
|
|
- const discardedDB = localStorage.getItem(LOCALSTORAGE_KEY_DISCARDED_DB)
|
|
|
|
|
- const discardedDBList: string[] = discardedDB ? JSON.parse(discardedDB) : []
|
|
|
|
|
|
|
+window.addEventListener("beforeunload", () => {
|
|
|
|
|
+ const discardedDB = localStorage.getItem(LOCALSTORAGE_KEY_DISCARDED_DB);
|
|
|
|
|
+ const discardedDBList: string[] = discardedDB ? JSON.parse(discardedDB) : [];
|
|
|
|
|
|
|
|
- discardedDBList.push(databaseId.value)
|
|
|
|
|
|
|
+ discardedDBList.push(databaseId.value);
|
|
|
|
|
|
|
|
- const newDiscardedDB = JSON.stringify(discardedDBList)
|
|
|
|
|
- localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
|
|
|
|
|
-})
|
|
|
|
|
|
|
+ const newDiscardedDB = JSON.stringify(discardedDBList);
|
|
|
|
|
+ localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB);
|
|
|
|
|
+});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
<style lang="scss">
|
|
@@ -145,7 +166,7 @@ window.addEventListener('beforeunload', () => {
|
|
|
.image-preview {
|
|
.image-preview {
|
|
|
position: fixed;
|
|
position: fixed;
|
|
|
inset: 0;
|
|
inset: 0;
|
|
|
- background: rgba(0,0,0,.85);
|
|
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.85);
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
z-index: 6000;
|
|
z-index: 6000;
|
|
@@ -164,20 +185,20 @@ window.addEventListener('beforeunload', () => {
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
border-radius: 10px;
|
|
border-radius: 10px;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- transition: transform .15s ease, box-shadow .2s ease, background .2s ease;
|
|
|
|
|
- box-shadow: 0 2px 8px rgba(47,128,237,.3);
|
|
|
|
|
|
|
+ transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(47, 128, 237, 0.3);
|
|
|
}
|
|
}
|
|
|
.image-preview__toolbar button:hover {
|
|
.image-preview__toolbar button:hover {
|
|
|
transform: translateY(-1px);
|
|
transform: translateY(-1px);
|
|
|
- box-shadow: 0 6px 16px rgba(47,128,237,.35);
|
|
|
|
|
|
|
+ box-shadow: 0 6px 16px rgba(47, 128, 237, 0.35);
|
|
|
}
|
|
}
|
|
|
.image-preview__toolbar button:active {
|
|
.image-preview__toolbar button:active {
|
|
|
transform: translateY(0);
|
|
transform: translateY(0);
|
|
|
- box-shadow: 0 2px 8px rgba(47,128,237,.28);
|
|
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(47, 128, 237, 0.28);
|
|
|
background: linear-gradient(180deg, #2f80ed 0%, #1b6dde 100%);
|
|
background: linear-gradient(180deg, #2f80ed 0%, #1b6dde 100%);
|
|
|
}
|
|
}
|
|
|
.image-preview__toolbar button:focus-visible {
|
|
.image-preview__toolbar button:focus-visible {
|
|
|
- outline: 2px solid rgba(47,128,237,.6);
|
|
|
|
|
|
|
+ outline: 2px solid rgba(47, 128, 237, 0.6);
|
|
|
outline-offset: 2px;
|
|
outline-offset: 2px;
|
|
|
}
|
|
}
|
|
|
.image-preview__stage {
|
|
.image-preview__stage {
|
|
@@ -187,12 +208,14 @@ window.addEventListener('beforeunload', () => {
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
cursor: grab;
|
|
cursor: grab;
|
|
|
}
|
|
}
|
|
|
-.image-preview__stage:active { cursor: grabbing; }
|
|
|
|
|
|
|
+.image-preview__stage:active {
|
|
|
|
|
+ cursor: grabbing;
|
|
|
|
|
+}
|
|
|
.image-preview__img {
|
|
.image-preview__img {
|
|
|
max-width: 92vw;
|
|
max-width: 92vw;
|
|
|
max-height: 92vh;
|
|
max-height: 92vh;
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
- box-shadow: 0 10px 30px rgba(0,0,0,.3);
|
|
|
|
|
|
|
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
|
user-select: none;
|
|
user-select: none;
|
|
|
will-change: transform;
|
|
will-change: transform;
|
|
|
}
|
|
}
|