diff --git a/components/app/Container.vue b/components/app/Container.vue index 7c901b3..b1784e7 100644 --- a/components/app/Container.vue +++ b/components/app/Container.vue @@ -1,5 +1,5 @@ <script lang="ts" setup> -import type { SubNavItem } from '../SubNav.vue' +import type { SubNavItem } from '../nav/Secondary.vue' defineProps<{ subnavs?: SubNavItem[] }>() </script> @@ -8,7 +8,7 @@ defineProps<{ subnavs?: SubNavItem[] }>() <div class="flex flex-1 flex-col p-8 page-bg-gradient"> <!-- <h1 class="pl-2 text-xl font-medium">外部标题</h1> --> <slot name="subnav"> - <SubNav + <NavSecondary v-if="subnavs && subnavs.length" :navs="subnavs" /> diff --git a/components/app/Sidebar.vue b/components/app/sidebar/index.vue similarity index 86% rename from components/app/Sidebar.vue rename to components/app/sidebar/index.vue index 0b86c04..33a3cf5 100644 --- a/components/app/Sidebar.vue +++ b/components/app/sidebar/index.vue @@ -1,7 +1,7 @@ <script lang="ts" setup> import type { LucideIcon } from 'lucide-vue-next' import type { RouteLocationRaw } from 'vue-router' -import type { SidebarProps } from '../ui/sidebar' +import type { SidebarProps } from '~/components/ui/sidebar' export interface SidebarNavItem { title: string @@ -46,17 +46,15 @@ const loginState = useLoginState() alt="Logo" class="w-9 max-w-9 aspect-square group-has-[[data-collapsible=icon]]/sidebar-wrapper:w-full transition-all duration-200 ease-in-out" /> - <h1 class="text-lg font-medium"> - 智课教学平台 - </h1> + <h1 class="text-lg font-medium">智课教学平台</h1> </div> </SidebarHeader> <SidebarContent> <slot name="extra-header" /> - <AppNavMain :nav="nav" /> + <AppSidebarNavMain :nav="nav" /> </SidebarContent> <SidebarFooter> - <AppNavUser :user="loginState.user" /> + <AppSidebarNavUser :user="loginState.user" /> </SidebarFooter> <SidebarRail /> </Sidebar> diff --git a/components/app/NavMain.vue b/components/app/sidebar/nav/Main.vue similarity index 100% rename from components/app/NavMain.vue rename to components/app/sidebar/nav/Main.vue diff --git a/components/app/NavUser.vue b/components/app/sidebar/nav/User.vue similarity index 98% rename from components/app/NavUser.vue rename to components/app/sidebar/nav/User.vue index 25d1d7b..c235c42 100644 --- a/components/app/NavUser.vue +++ b/components/app/sidebar/nav/User.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { ChevronsUpDown } from 'lucide-vue-next' -import { useSidebar } from '../ui/sidebar' +import { useSidebar } from '../../../ui/sidebar' import type { IUser } from '~/types' const props = defineProps<{ diff --git a/components/CourseCard.vue b/components/course/Card.vue similarity index 100% rename from components/CourseCard.vue rename to components/course/Card.vue diff --git a/components/SubNav.vue b/components/nav/Secondary.vue similarity index 90% rename from components/SubNav.vue rename to components/nav/Secondary.vue index 046f873..72bf6b5 100644 --- a/components/SubNav.vue +++ b/components/nav/Secondary.vue @@ -29,7 +29,7 @@ const isCurrentPath = (path: string) => { }" > <svg - class="absolute inset-0 aspect-auto" + class="absolute inset-0 aspect-auto top-1" viewBox="0 0 206.5 72" fill="none" xmlns="http://www.w3.org/2000/svg" @@ -62,7 +62,7 @@ const isCurrentPath = (path: string) => { </g> </svg> <NuxtLink - class="text-lg font-medium z-10 select-none" + class="text-base font-medium z-10 select-none pb-0.5" :class="{ 'text-secondary': isCurrentPath(nav.to), 'text-neutral-400 dark:text-neutral-500': !isCurrentPath(nav.to), @@ -77,13 +77,13 @@ const isCurrentPath = (path: string) => { <style scoped> .subnav-item { - @apply relative flex justify-center items-center px-4 pt-1 drop-shadow-md; + @apply relative flex justify-center items-center px-5 pt-2 drop-shadow-lg; --svg-stop1: #ffffff; --svg-stop2: #f3f3f3; &:not(:first-of-type) { - @apply -ml-3; + @apply -ml-4; } &.active { diff --git a/package.json b/package.json index 2e8dc63..94598b9 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dotenv": "^16.5.0", + "dplayer": "^1.27.1", "eslint": "^9.0.0", "lucide-vue-next": "^0.484.0", "nuxt": "^3.16.1", @@ -49,6 +50,7 @@ "@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/tailwindcss": "^6.13.2", "@pinia/nuxt": "^0.10.1", + "@types/dplayer": "^1.25.5", "@vueuse/nuxt": "^13.0.0", "dayjs": "^1.11.13", "dayjs-nuxt": "^2.1.11", diff --git a/pages/preview/[resource_url].vue b/pages/preview/[resource_url].vue index 2dfdae0..5d760d9 100644 --- a/pages/preview/[resource_url].vue +++ b/pages/preview/[resource_url].vue @@ -5,6 +5,7 @@ import VueOfficeExcel from '@vue-office/excel' import VueOfficePdf from '@vue-office/pdf' import '@vue-office/docx/lib/index.css' import '@vue-office/excel/lib/index.css' +import DPlayer from 'dplayer' definePageMeta({ hideSidebar: true, @@ -39,6 +40,12 @@ const fileType = computed(() => { return '' }) +const containerClass = computed(() => { + return fileType.value === 'video' + ? 'max-w-6xl mx-auto' + : 'w-full h-full border rounded-lg overflow-hidden' +}) + onMounted(() => { useHead({ title: `${fileType.value.toUpperCase()} 资源预览`, @@ -49,6 +56,18 @@ onMounted(() => { label: `${fileType.value.toUpperCase()} 资源预览`, }, ]) + + // initialize video player + if (fileType.value === 'video') { + const dp = new DPlayer({ + container: document.getElementById('dplayer'), + screenshot: true, + video: { + url: url.value, + }, + }) + dp.play() + } }) const vueOfficeOptions = { @@ -70,10 +89,16 @@ const vueOfficeOptions = { </div> <div v-else - class="w-full h-full border rounded-lg overflow-hidden" + :class="containerClass" > + <!-- Video --> + <div + v-if="fileType === 'video'" + id="dplayer" + /> + <!-- Vue Office --> <VueOfficeDocx - v-if="fileType === 'word'" + v-else-if="fileType === 'word'" class="w-full h-full" :src="url" :options="vueOfficeOptions" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d98b06b..ea0aa29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: dotenv: specifier: ^16.5.0 version: 16.5.0 + dplayer: + specifier: ^1.27.1 + version: 1.27.1 eslint: specifier: ^9.0.0 version: 9.23.0(jiti@2.4.2) @@ -111,6 +114,9 @@ importers: '@pinia/nuxt': specifier: ^0.10.1 version: 0.10.1(magicast@0.3.5)(pinia@3.0.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))) + '@types/dplayer': + specifier: ^1.25.5 + version: 1.25.5 '@vueuse/nuxt': specifier: ^13.0.0 version: 13.0.0(magicast@0.3.5)(nuxt@3.16.1(@parcel/watcher@2.5.1)(db0@0.3.1)(eslint@9.23.0(jiti@2.4.2))(ioredis@5.6.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.37.0)(terser@5.39.0)(typescript@5.8.2)(vite@6.2.3(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))(yaml@2.7.0))(vue@3.5.13(typescript@5.8.2)) @@ -1212,6 +1218,9 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/dplayer@1.25.5': + resolution: {integrity: sha512-p/7O94dHDo0Irn2KWIqFE+fBCA4DS7QL3jfCOjCUPBAOgppyyTjmNZjKEfiJa1M3n1oVQqG7xnPwhiIuCqOzkQ==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1650,6 +1659,9 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1661,12 +1673,18 @@ packages: peerDependencies: postcss: ^8.1.0 + axios@1.2.3: + resolution: {integrity: sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==} + b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balloon-css@1.2.0: + resolution: {integrity: sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==} + bare-events@2.5.4: resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} @@ -1879,6 +1897,10 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2131,6 +2153,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -2203,6 +2229,9 @@ packages: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} + dplayer@1.27.1: + resolution: {integrity: sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2265,6 +2294,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.25.1: resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} engines: {node: '>=18'} @@ -2502,6 +2535,15 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + fontaine@0.5.0: resolution: {integrity: sha512-vPDSWKhVAfTx4hRKT777+N6Szh2pAosAuzLpbppZ6O3UdD/1m6OlHjNcC3vIbgkRTIcLjzySLHXzPeLO2rE8cA==} @@ -2512,6 +2554,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -3718,6 +3764,9 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + promise-polyfill@8.3.0: + resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -3725,6 +3774,9 @@ packages: protocols@2.0.2: resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -6022,6 +6074,8 @@ snapshots: '@types/doctrine@0.0.9': {} + '@types/dplayer@1.25.5': {} + '@types/estree@1.0.6': {} '@types/estree@1.0.7': {} @@ -6528,6 +6582,8 @@ snapshots: async@3.2.6: {} + asynckit@0.4.0: {} + at-least-node@1.0.0: {} autoprefixer@10.4.21(postcss@8.5.3): @@ -6540,10 +6596,20 @@ snapshots: postcss: 8.5.3 postcss-value-parser: 4.2.0 + axios@1.2.3: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + b4a@1.6.7: {} balanced-match@1.0.2: {} + balloon-css@1.2.0: {} + bare-events@2.5.4: optional: true @@ -6769,6 +6835,10 @@ snapshots: colord@2.9.3: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@2.20.3: {} commander@4.1.1: {} @@ -6989,6 +7059,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + delegates@1.0.0: {} denque@2.1.0: {} @@ -7043,6 +7115,14 @@ snapshots: dotenv@16.5.0: {} + dplayer@1.27.1: + dependencies: + axios: 1.2.3 + balloon-css: 1.2.0 + promise-polyfill: 8.3.0 + transitivePeerDependencies: + - debug + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -7091,6 +7171,13 @@ snapshots: dependencies: es-errors: 1.3.0 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.25.1: optionalDependencies: '@esbuild/aix-ppc64': 0.25.1 @@ -7413,6 +7500,8 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.9: {} + fontaine@0.5.0: dependencies: '@capsizecss/metrics': 2.2.0 @@ -7442,6 +7531,13 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + fraction.js@4.3.7: {} fresh@0.5.2: {} @@ -8858,6 +8954,8 @@ snapshots: process@0.11.10: {} + promise-polyfill@8.3.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -8865,6 +8963,8 @@ snapshots: protocols@2.0.2: {} + proxy-from-env@1.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4