diff --git a/index.html b/index.html index 067721c..4525ea0 100644 --- a/index.html +++ b/index.html @@ -2,18 +2,31 @@ - + - - - 短视频达人 + + + 短视频达人养成记
- + diff --git a/package.json b/package.json index 60311dd..2bdae2a 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "tailwindcss": "^3.4.17", "typescript": "~5.8.3", "typescript-eslint": "^8.43.0", + "vconsole": "^3.15.1", "vite": "^7.1.2", "vue-tsc": "^3.0.5" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43f6d93..b9aab3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: typescript-eslint: specifier: ^8.43.0 version: 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.8.3) + vconsole: + specifier: ^3.15.1 + version: 3.15.1 vite: specifier: ^7.1.2 version: 7.1.5(@types/node@24.3.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(yaml@2.8.1) @@ -230,6 +233,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -1242,6 +1249,13 @@ packages: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} + copy-text-to-clipboard@3.2.2: + resolution: {integrity: sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==} + engines: {node: '>=12'} + + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2087,6 +2101,9 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + mutation-observer@1.0.3: + resolution: {integrity: sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -2639,6 +2656,9 @@ packages: engines: {node: '>=8'} hasBin: true + vconsole@3.15.1: + resolution: {integrity: sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==} + vite@7.1.5: resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2968,6 +2988,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -3933,6 +3955,10 @@ snapshots: dependencies: is-what: 5.5.0 + copy-text-to-clipboard@3.2.2: {} + + core-js@3.49.0: {} + core-util-is@1.0.3: {} cosmiconfig-typescript-loader@6.1.0(@types/node@24.3.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): @@ -4814,6 +4840,8 @@ snapshots: muggle-string@0.4.1: {} + mutation-observer@1.0.3: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -5382,6 +5410,13 @@ snapshots: kleur: 4.1.5 sade: 1.8.1 + vconsole@3.15.1: + dependencies: + '@babel/runtime': 7.29.2 + copy-text-to-clipboard: 3.2.2 + core-js: 3.49.0 + mutation-observer: 1.0.3 + vite@7.1.5(@types/node@24.3.3)(jiti@2.5.1)(less@4.4.1)(lightningcss@1.30.1)(yaml@2.8.1): dependencies: esbuild: 0.25.9 diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..9080420 Binary files /dev/null and b/public/logo.png differ diff --git a/public/maps/dsp_kecheng.xmind b/public/maps/dsp_kecheng.xmind deleted file mode 100644 index e52e2fb..0000000 Binary files a/public/maps/dsp_kecheng.xmind and /dev/null differ diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 90044f0..dca5bc4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,20 @@ @@ -17,9 +25,10 @@ import { useVlogStore, useFilmStore, useInterviewStore, - useBasicElementsStore + useBasicElementsStore, } from '@/stores' import { onMounted } from 'vue' +import { syncAllCompletedYibanLessons } from '@/utils/yibanLessonProgress' // 使用等比例缩放hook const { scaleStyle } = useScale() @@ -37,7 +46,7 @@ onMounted(async () => { if (courseId || uid) { userStore.setCourseInfo({ courseId: courseId ?? null, - uid: uid ?? null + uid: uid ?? null, }) } @@ -45,8 +54,10 @@ onMounted(async () => { vlogStore.loadVlogStore(), filmStore.loadFilmStore(), interviewStore.loadInterviewStore(), - basicElementsStore.loadBasicElementsStore() + basicElementsStore.loadBasicElementsStore(), ]) + + await syncAllCompletedYibanLessons() }) // 容器样式 @@ -55,11 +66,39 @@ const containerStyle = { height: '100vh', overflow: 'hidden', position: 'relative' as const, - background: '#000' + background: '#000', } - + + diff --git a/src/assets/images/components/ProgressBar-bg.png b/src/assets/images/components/ProgressBar-bg.png index 497c710..ffc5304 100644 Binary files a/src/assets/images/components/ProgressBar-bg.png and b/src/assets/images/components/ProgressBar-bg.png differ diff --git a/src/assets/images/components/loading/小龙人.png b/src/assets/images/components/loading/小龙人.png new file mode 100644 index 0000000..207add7 Binary files /dev/null and b/src/assets/images/components/loading/小龙人.png differ diff --git a/src/assets/images/components/loading/省略号.png b/src/assets/images/components/loading/省略号.png new file mode 100644 index 0000000..e47a200 Binary files /dev/null and b/src/assets/images/components/loading/省略号.png differ diff --git a/src/assets/images/components/loading/精彩内容即将呈现.png b/src/assets/images/components/loading/精彩内容即将呈现.png new file mode 100644 index 0000000..ccc8a39 Binary files /dev/null and b/src/assets/images/components/loading/精彩内容即将呈现.png differ diff --git a/src/assets/images/components/loading/精彩内容即将呈现文字.png b/src/assets/images/components/loading/精彩内容即将呈现文字.png new file mode 100644 index 0000000..5285a9d Binary files /dev/null and b/src/assets/images/components/loading/精彩内容即将呈现文字.png differ diff --git a/src/assets/images/components/loading/背景图层.png b/src/assets/images/components/loading/背景图层.png new file mode 100644 index 0000000..09d9fe5 Binary files /dev/null and b/src/assets/images/components/loading/背景图层.png differ diff --git a/src/assets/images/components/loading/进度条亮.png b/src/assets/images/components/loading/进度条亮.png new file mode 100644 index 0000000..73a02fd Binary files /dev/null and b/src/assets/images/components/loading/进度条亮.png differ diff --git a/src/assets/images/components/loading/进度条底板.png b/src/assets/images/components/loading/进度条底板.png new file mode 100644 index 0000000..358bca3 Binary files /dev/null and b/src/assets/images/components/loading/进度条底板.png differ diff --git a/src/assets/images/components/进入-实战操作.png b/src/assets/images/components/进入-实战操作.png deleted file mode 100644 index a4558a0..0000000 Binary files a/src/assets/images/components/进入-实战操作.png and /dev/null differ diff --git a/src/assets/images/components/进入实战操作台-已学习.png b/src/assets/images/components/进入实战操作台-已学习.png new file mode 100644 index 0000000..82982ae Binary files /dev/null and b/src/assets/images/components/进入实战操作台-已学习.png differ diff --git a/src/assets/images/components/进入实战操作台-未学习.png b/src/assets/images/components/进入实战操作台-未学习.png new file mode 100644 index 0000000..1f40110 Binary files /dev/null and b/src/assets/images/components/进入实战操作台-未学习.png differ diff --git a/src/assets/images/cover/Vlogmk_dtgpyzp_batch.jpg b/src/assets/images/cover/Vlogmk_dtgpyzp_batch.jpg new file mode 100644 index 0000000..97041de Binary files /dev/null and b/src/assets/images/cover/Vlogmk_dtgpyzp_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_jcjbyy_batch.jpg b/src/assets/images/cover/Vlogmk_jcjbyy_batch.jpg new file mode 100644 index 0000000..4d961af Binary files /dev/null and b/src/assets/images/cover/Vlogmk_jcjbyy_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_jdjbch_batch.jpg b/src/assets/images/cover/Vlogmk_jdjbch_batch.jpg new file mode 100644 index 0000000..9cb1668 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_jdjbch_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_jjjz_batch.jpg b/src/assets/images/cover/Vlogmk_jjjz_batch.jpg new file mode 100644 index 0000000..a8f84f8 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_jjjz_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_jjqd_batch.jpg b/src/assets/images/cover/Vlogmk_jjqd_batch.jpg new file mode 100644 index 0000000..b7297df Binary files /dev/null and b/src/assets/images/cover/Vlogmk_jjqd_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_jtyy_batch.jpg b/src/assets/images/cover/Vlogmk_jtyy_batch.jpg new file mode 100644 index 0000000..1838335 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_jtyy_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_kcjl_batch.jpg b/src/assets/images/cover/Vlogmk_kcjl_batch.jpg new file mode 100644 index 0000000..c7478be Binary files /dev/null and b/src/assets/images/cover/Vlogmk_kcjl_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_scdrjjpj_batch.jpg b/src/assets/images/cover/Vlogmk_scdrjjpj_batch.jpg new file mode 100644 index 0000000..d4131a5 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_scdrjjpj_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_syyzc_batch.jpg b/src/assets/images/cover/Vlogmk_syyzc_batch.jpg new file mode 100644 index 0000000..31da037 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_syyzc_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_tjbjyy_batch.jpg b/src/assets/images/cover/Vlogmk_tjbjyy_batch.jpg new file mode 100644 index 0000000..b7b3997 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_tjbjyy_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_tsybz_batch.jpg b/src/assets/images/cover/Vlogmk_tsybz_batch.jpg new file mode 100644 index 0000000..eada33f Binary files /dev/null and b/src/assets/images/cover/Vlogmk_tsybz_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_yhpp_batch.jpg b/src/assets/images/cover/Vlogmk_yhpp_batch.jpg new file mode 100644 index 0000000..ed7c055 Binary files /dev/null and b/src/assets/images/cover/Vlogmk_yhpp_batch.jpg differ diff --git a/src/assets/images/cover/Vlogmk_ystyycbbdgd_batch.jpg b/src/assets/images/cover/Vlogmk_ystyycbbdgd_batch.jpg new file mode 100644 index 0000000..064cb8c Binary files /dev/null and b/src/assets/images/cover/Vlogmk_ystyycbbdgd_batch.jpg differ diff --git a/src/assets/images/cover/anlishangxi_meishi_xiaoyuanzi.jpg b/src/assets/images/cover/anlishangxi_meishi_xiaoyuanzi.jpg new file mode 100644 index 0000000..03b5d72 Binary files /dev/null and b/src/assets/images/cover/anlishangxi_meishi_xiaoyuanzi.jpg differ diff --git a/src/assets/images/cover/dygmk_czsjx_batch.jpg b/src/assets/images/cover/dygmk_czsjx_batch.jpg new file mode 100644 index 0000000..55a5bd8 Binary files /dev/null and b/src/assets/images/cover/dygmk_czsjx_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_fjt_batch.jpg b/src/assets/images/cover/dygmk_fjt_batch.jpg new file mode 100644 index 0000000..e6ad965 Binary files /dev/null and b/src/assets/images/cover/dygmk_fjt_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_gx_batch.jpg b/src/assets/images/cover/dygmk_gx_batch.jpg new file mode 100644 index 0000000..f4c99a3 Binary files /dev/null and b/src/assets/images/cover/dygmk_gx_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_hgydjt_batch.jpg b/src/assets/images/cover/dygmk_hgydjt_batch.jpg new file mode 100644 index 0000000..2e3c98b Binary files /dev/null and b/src/assets/images/cover/dygmk_hgydjt_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_jjcnlks_batch.jpg b/src/assets/images/cover/dygmk_jjcnlks_batch.jpg new file mode 100644 index 0000000..bf2b532 Binary files /dev/null and b/src/assets/images/cover/dygmk_jjcnlks_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_lj_batch.jpg b/src/assets/images/cover/dygmk_lj_batch.jpg new file mode 100644 index 0000000..0f3e023 Binary files /dev/null and b/src/assets/images/cover/dygmk_lj_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_mmps_batch.jpg b/src/assets/images/cover/dygmk_mmps_batch.jpg new file mode 100644 index 0000000..e79ca1d Binary files /dev/null and b/src/assets/images/cover/dygmk_mmps_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_tqs_batch.jpg b/src/assets/images/cover/dygmk_tqs_batch.jpg new file mode 100644 index 0000000..0319bdf Binary files /dev/null and b/src/assets/images/cover/dygmk_tqs_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_txjt_batch.jpg b/src/assets/images/cover/dygmk_txjt_batch.jpg new file mode 100644 index 0000000..71767c1 Binary files /dev/null and b/src/assets/images/cover/dygmk_txjt_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_xs_batch.jpg b/src/assets/images/cover/dygmk_xs_batch.jpg new file mode 100644 index 0000000..76ad871 Binary files /dev/null and b/src/assets/images/cover/dygmk_xs_batch.jpg differ diff --git a/src/assets/images/cover/dygmk_zf_batch.jpg b/src/assets/images/cover/dygmk_zf_batch.jpg new file mode 100644 index 0000000..a9a92ce Binary files /dev/null and b/src/assets/images/cover/dygmk_zf_batch.jpg differ diff --git a/src/assets/images/cover/ft_zylt.jpg b/src/assets/images/cover/ft_zylt.jpg new file mode 100644 index 0000000..e86a59e Binary files /dev/null and b/src/assets/images/cover/ft_zylt.jpg differ diff --git a/src/assets/images/cover/ftmk_bpjtynx_batch.jpg b/src/assets/images/cover/ftmk_bpjtynx_batch.jpg new file mode 100644 index 0000000..cbacbfd Binary files /dev/null and b/src/assets/images/cover/ftmk_bpjtynx_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_jlcutyy_batch.jpg b/src/assets/images/cover/ftmk_jlcutyy_batch.jpg new file mode 100644 index 0000000..7b044d8 Binary files /dev/null and b/src/assets/images/cover/ftmk_jlcutyy_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_jsyrgs_.jpg b/src/assets/images/cover/ftmk_jsyrgs_.jpg new file mode 100644 index 0000000..1914152 Binary files /dev/null and b/src/assets/images/cover/ftmk_jsyrgs_.jpg differ diff --git a/src/assets/images/cover/ftmk_jtzjlj_batch.jpg b/src/assets/images/cover/ftmk_jtzjlj_batch.jpg new file mode 100644 index 0000000..cb51bfe Binary files /dev/null and b/src/assets/images/cover/ftmk_jtzjlj_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_jwsz_batch.jpg b/src/assets/images/cover/ftmk_jwsz_batch.jpg new file mode 100644 index 0000000..5758e7a Binary files /dev/null and b/src/assets/images/cover/ftmk_jwsz_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_jzjjjq_batch.jpg b/src/assets/images/cover/ftmk_jzjjjq_batch.jpg new file mode 100644 index 0000000..a41aa97 Binary files /dev/null and b/src/assets/images/cover/ftmk_jzjjjq_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_sczmp_batch.jpg b/src/assets/images/cover/ftmk_sczmp_batch.jpg new file mode 100644 index 0000000..1ea52ff Binary files /dev/null and b/src/assets/images/cover/ftmk_sczmp_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_shjjlc_batch.jpg b/src/assets/images/cover/ftmk_shjjlc_batch.jpg new file mode 100644 index 0000000..eb0c01b Binary files /dev/null and b/src/assets/images/cover/ftmk_shjjlc_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_sytbysl_batch.jpg b/src/assets/images/cover/ftmk_sytbysl_batch.jpg new file mode 100644 index 0000000..6b35d06 Binary files /dev/null and b/src/assets/images/cover/ftmk_sytbysl_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_txjtdzy_batch.jpg b/src/assets/images/cover/ftmk_txjtdzy_batch.jpg new file mode 100644 index 0000000..c41d0f9 Binary files /dev/null and b/src/assets/images/cover/ftmk_txjtdzy_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_ydjtdzy_batch.jpg b/src/assets/images/cover/ftmk_ydjtdzy_batch.jpg new file mode 100644 index 0000000..125f76c Binary files /dev/null and b/src/assets/images/cover/ftmk_ydjtdzy_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_yx_batch.jpg b/src/assets/images/cover/ftmk_yx_batch.jpg new file mode 100644 index 0000000..284689a Binary files /dev/null and b/src/assets/images/cover/ftmk_yx_batch.jpg differ diff --git a/src/assets/images/cover/ftmk_zxyz_batch.jpg b/src/assets/images/cover/ftmk_zxyz_batch.jpg new file mode 100644 index 0000000..aef6a3c Binary files /dev/null and b/src/assets/images/cover/ftmk_zxyz_batch.jpg differ diff --git a/src/assets/images/cover/jiaoben.jpg b/src/assets/images/cover/jiaoben.jpg new file mode 100644 index 0000000..c85d6d1 Binary files /dev/null and b/src/assets/images/cover/jiaoben.jpg differ diff --git a/src/assets/images/cover/xinshou_guangdejichurenzhi.jpg b/src/assets/images/cover/xinshou_guangdejichurenzhi.jpg new file mode 100644 index 0000000..26e1bcb Binary files /dev/null and b/src/assets/images/cover/xinshou_guangdejichurenzhi.jpg differ diff --git a/src/assets/images/cover/xinshou_guangdesidashuxing.jpg b/src/assets/images/cover/xinshou_guangdesidashuxing.jpg new file mode 100644 index 0000000..168af3a Binary files /dev/null and b/src/assets/images/cover/xinshou_guangdesidashuxing.jpg differ diff --git a/src/assets/images/cover/xinshou_jiuzhonggoutufa.jpg b/src/assets/images/cover/xinshou_jiuzhonggoutufa.jpg new file mode 100644 index 0000000..cfb1d51 Binary files /dev/null and b/src/assets/images/cover/xinshou_jiuzhonggoutufa.jpg differ diff --git a/src/assets/images/cover/xinshou_ruhexuanzegoutu.jpg b/src/assets/images/cover/xinshou_ruhexuanzegoutu.jpg new file mode 100644 index 0000000..208ffe8 Binary files /dev/null and b/src/assets/images/cover/xinshou_ruhexuanzegoutu.jpg differ diff --git a/src/assets/images/cover/xinshou_shenqideweijusheying.jpg b/src/assets/images/cover/xinshou_shenqideweijusheying.jpg new file mode 100644 index 0000000..8f85f80 Binary files /dev/null and b/src/assets/images/cover/xinshou_shenqideweijusheying.jpg differ diff --git a/src/assets/images/cover/xinshou_shoujishexiangtoujieshao.jpg b/src/assets/images/cover/xinshou_shoujishexiangtoujieshao.jpg new file mode 100644 index 0000000..e22ff8d Binary files /dev/null and b/src/assets/images/cover/xinshou_shoujishexiangtoujieshao.jpg differ diff --git a/src/assets/images/cover/xinshou_yunjingdejibenfangshi.jpg b/src/assets/images/cover/xinshou_yunjingdejibenfangshi.jpg new file mode 100644 index 0000000..d3dbd5c Binary files /dev/null and b/src/assets/images/cover/xinshou_yunjingdejibenfangshi.jpg differ diff --git a/src/assets/images/cover/xinshou_zhengquedeshoujipaishe.jpg b/src/assets/images/cover/xinshou_zhengquedeshoujipaishe.jpg new file mode 100644 index 0000000..b686d38 Binary files /dev/null and b/src/assets/images/cover/xinshou_zhengquedeshoujipaishe.jpg differ diff --git a/src/assets/images/cover/xsdh_csjyzybjmhgn_batch.jpg b/src/assets/images/cover/xsdh_csjyzybjmhgn_batch.jpg new file mode 100644 index 0000000..c41c40a Binary files /dev/null and b/src/assets/images/cover/xsdh_csjyzybjmhgn_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_djmldyjsy_batch.jpg b/src/assets/images/cover/xsdh_djmldyjsy_batch.jpg new file mode 100644 index 0000000..d62f92a Binary files /dev/null and b/src/assets/images/cover/xsdh_djmldyjsy_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_gjzgnyfxj_batch.jpg b/src/assets/images/cover/xsdh_gjzgnyfxj_batch.jpg new file mode 100644 index 0000000..1ac9447 Binary files /dev/null and b/src/assets/images/cover/xsdh_gjzgnyfxj_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_hhmsgnyfxj_batch.jpg b/src/assets/images/cover/xsdh_hhmsgnyfxj_batch.jpg new file mode 100644 index 0000000..11caab9 Binary files /dev/null and b/src/assets/images/cover/xsdh_hhmsgnyfxj_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_jbgnjs_batch.jpg b/src/assets/images/cover/xsdh_jbgnjs_batch.jpg new file mode 100644 index 0000000..3fbe492 Binary files /dev/null and b/src/assets/images/cover/xsdh_jbgnjs_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_jzgtf_batch.jpg b/src/assets/images/cover/xsdh_jzgtf_batch.jpg new file mode 100644 index 0000000..8dcd425 Binary files /dev/null and b/src/assets/images/cover/xsdh_jzgtf_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_mbgnyfxj_batch.jpg b/src/assets/images/cover/xsdh_mbgnyfxj_batch.jpg new file mode 100644 index 0000000..18cfe6a Binary files /dev/null and b/src/assets/images/cover/xsdh_mbgnyfxj_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_psdhjsj_batch.jpg b/src/assets/images/cover/xsdh_psdhjsj_batch.jpg new file mode 100644 index 0000000..16d048e Binary files /dev/null and b/src/assets/images/cover/xsdh_psdhjsj_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_qxbsgnyfxj_batch.jpg b/src/assets/images/cover/xsdh_qxbsgnyfxj_batch.jpg new file mode 100644 index 0000000..77410c3 Binary files /dev/null and b/src/assets/images/cover/xsdh_qxbsgnyfxj_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_rhghmts_batch.jpg b/src/assets/images/cover/xsdh_rhghmts_batch.jpg new file mode 100644 index 0000000..7f2ffb7 Binary files /dev/null and b/src/assets/images/cover/xsdh_rhghmts_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_rhgspksszm_batch.jpg b/src/assets/images/cover/xsdh_rhgspksszm_batch.jpg new file mode 100644 index 0000000..0c84250 Binary files /dev/null and b/src/assets/images/cover/xsdh_rhgspksszm_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_rhxzgt_batch.jpg b/src/assets/images/cover/xsdh_rhxzgt_batch.jpg new file mode 100644 index 0000000..5d03495 Binary files /dev/null and b/src/assets/images/cover/xsdh_rhxzgt_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_sjmdz_batch.jpg b/src/assets/images/cover/xsdh_sjmdz_batch.jpg new file mode 100644 index 0000000..4d2acea Binary files /dev/null and b/src/assets/images/cover/xsdh_sjmdz_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_sjxdyfxj_batch.jpg b/src/assets/images/cover/xsdh_sjxdyfxj_batch.jpg new file mode 100644 index 0000000..6cbb291 Binary files /dev/null and b/src/assets/images/cover/xsdh_sjxdyfxj_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_sqdwjsy_batch.jpg b/src/assets/images/cover/xsdh_sqdwjsy_batch.jpg new file mode 100644 index 0000000..fff2fd6 Binary files /dev/null and b/src/assets/images/cover/xsdh_sqdwjsy_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_ypdgnyyhcl_batch.jpg b/src/assets/images/cover/xsdh_ypdgnyyhcl_batch.jpg new file mode 100644 index 0000000..ba1a3b1 Binary files /dev/null and b/src/assets/images/cover/xsdh_ypdgnyyhcl_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_yssy_batch.jpg b/src/assets/images/cover/xsdh_yssy_batch.jpg new file mode 100644 index 0000000..a8802fd Binary files /dev/null and b/src/assets/images/cover/xsdh_yssy_batch.jpg differ diff --git a/src/assets/images/cover/xsdh_zctxtzgnxj_batch.jpg b/src/assets/images/cover/xsdh_zctxtzgnxj_batch.jpg new file mode 100644 index 0000000..25fdea1 Binary files /dev/null and b/src/assets/images/cover/xsdh_zctxtzgnxj_batch.jpg differ diff --git a/src/assets/images/equipment/内容-安卓小米.png b/src/assets/images/equipment/内容-安卓小米.png index b542753..13e699c 100644 Binary files a/src/assets/images/equipment/内容-安卓小米.png and b/src/assets/images/equipment/内容-安卓小米.png differ diff --git a/src/assets/images/equipment/标题-安卓小米.png b/src/assets/images/equipment/标题-安卓小米.png index e70e94d..96f4fa1 100644 Binary files a/src/assets/images/equipment/标题-安卓小米.png and b/src/assets/images/equipment/标题-安卓小米.png differ diff --git a/src/assets/images/equipment/设备-手机2.png b/src/assets/images/equipment/设备-手机2.png index 9d9d069..232c4f1 100644 Binary files a/src/assets/images/equipment/设备-手机2.png and b/src/assets/images/equipment/设备-手机2.png differ diff --git a/src/assets/images/film/after-editing/img/关联剪辑.png b/src/assets/images/film/after-editing/img/关联剪辑.png new file mode 100644 index 0000000..d72e9ee Binary files /dev/null and b/src/assets/images/film/after-editing/img/关联剪辑.png differ diff --git a/src/assets/images/film/after-editing/img/声音转场.png b/src/assets/images/film/after-editing/img/声音转场.png new file mode 100644 index 0000000..fbafa19 Binary files /dev/null and b/src/assets/images/film/after-editing/img/声音转场.png differ diff --git a/src/assets/images/film/after-editing/img/快切.png b/src/assets/images/film/after-editing/img/快切.png new file mode 100644 index 0000000..13ce5c3 Binary files /dev/null and b/src/assets/images/film/after-editing/img/快切.png differ diff --git a/src/assets/images/film/after-editing/img/节奏型剪辑.png b/src/assets/images/film/after-editing/img/节奏型剪辑.png new file mode 100644 index 0000000..1bdbed3 Binary files /dev/null and b/src/assets/images/film/after-editing/img/节奏型剪辑.png differ diff --git a/src/assets/images/film/after-editing/img/连续剪辑.png b/src/assets/images/film/after-editing/img/连续剪辑.png new file mode 100644 index 0000000..f445db7 Binary files /dev/null and b/src/assets/images/film/after-editing/img/连续剪辑.png differ diff --git a/src/assets/images/film/after-editing/text/关联剪辑.png b/src/assets/images/film/after-editing/text/关联剪辑.png new file mode 100644 index 0000000..6bf492c Binary files /dev/null and b/src/assets/images/film/after-editing/text/关联剪辑.png differ diff --git a/src/assets/images/film/after-editing/text/声音转场.png b/src/assets/images/film/after-editing/text/声音转场.png new file mode 100644 index 0000000..6ba0abb Binary files /dev/null and b/src/assets/images/film/after-editing/text/声音转场.png differ diff --git a/src/assets/images/film/after-editing/text/快切.png b/src/assets/images/film/after-editing/text/快切.png new file mode 100644 index 0000000..bffe8b3 Binary files /dev/null and b/src/assets/images/film/after-editing/text/快切.png differ diff --git a/src/assets/images/film/after-editing/text/节奏型剪辑.png b/src/assets/images/film/after-editing/text/节奏型剪辑.png new file mode 100644 index 0000000..3aa42c7 Binary files /dev/null and b/src/assets/images/film/after-editing/text/节奏型剪辑.png differ diff --git a/src/assets/images/film/after-editing/text/连续剪辑.png b/src/assets/images/film/after-editing/text/连续剪辑.png new file mode 100644 index 0000000..8d51cb5 Binary files /dev/null and b/src/assets/images/film/after-editing/text/连续剪辑.png differ diff --git a/src/assets/images/film/after-editing/关联剪辑.png b/src/assets/images/film/after-editing/关联剪辑.png deleted file mode 100644 index 042e55a..0000000 Binary files a/src/assets/images/film/after-editing/关联剪辑.png and /dev/null differ diff --git a/src/assets/images/film/after-editing/剪辑初始-亮.png b/src/assets/images/film/after-editing/剪辑初始-亮.png new file mode 100644 index 0000000..70d37c4 Binary files /dev/null and b/src/assets/images/film/after-editing/剪辑初始-亮.png differ diff --git a/src/assets/images/film/after-editing/剪辑底图.png b/src/assets/images/film/after-editing/剪辑底图.png deleted file mode 100644 index 9833105..0000000 Binary files a/src/assets/images/film/after-editing/剪辑底图.png and /dev/null differ diff --git a/src/assets/images/film/after-editing/剪辑底图亮.png b/src/assets/images/film/after-editing/剪辑底图亮.png new file mode 100644 index 0000000..b533d0f Binary files /dev/null and b/src/assets/images/film/after-editing/剪辑底图亮.png differ diff --git a/src/assets/images/film/after-editing/剪辑底图灰.png b/src/assets/images/film/after-editing/剪辑底图灰.png new file mode 100644 index 0000000..1b83d57 Binary files /dev/null and b/src/assets/images/film/after-editing/剪辑底图灰.png differ diff --git a/src/assets/images/film/after-editing/声音转场.png b/src/assets/images/film/after-editing/声音转场.png deleted file mode 100644 index 1fa8208..0000000 Binary files a/src/assets/images/film/after-editing/声音转场.png and /dev/null differ diff --git a/src/assets/images/film/after-editing/快切.png b/src/assets/images/film/after-editing/快切.png deleted file mode 100644 index 919340b..0000000 Binary files a/src/assets/images/film/after-editing/快切.png and /dev/null differ diff --git a/src/assets/images/film/after-editing/点击此处开始.png b/src/assets/images/film/after-editing/点击此处开始.png new file mode 100644 index 0000000..dd61076 Binary files /dev/null and b/src/assets/images/film/after-editing/点击此处开始.png differ diff --git a/src/assets/images/film/after-editing/节奏型剪辑.png b/src/assets/images/film/after-editing/节奏型剪辑.png deleted file mode 100644 index 36006c7..0000000 Binary files a/src/assets/images/film/after-editing/节奏型剪辑.png and /dev/null differ diff --git a/src/assets/images/film/after-editing/连续剪辑.png b/src/assets/images/film/after-editing/连续剪辑.png deleted file mode 100644 index d8a55ad..0000000 Binary files a/src/assets/images/film/after-editing/连续剪辑.png and /dev/null differ diff --git a/src/assets/images/film/专业剖析.png b/src/assets/images/film/work/专业剖析小屏.png similarity index 100% rename from src/assets/images/film/专业剖析.png rename to src/assets/images/film/work/专业剖析小屏.png diff --git a/src/assets/images/film/思路提点全屏.png b/src/assets/images/film/work/思路提点全屏.png similarity index 100% rename from src/assets/images/film/思路提点全屏.png rename to src/assets/images/film/work/思路提点全屏.png diff --git a/src/assets/images/film/思路提点小屏.png b/src/assets/images/film/work/思路提点小屏.png similarity index 100% rename from src/assets/images/film/思路提点小屏.png rename to src/assets/images/film/work/思路提点小屏.png diff --git a/src/assets/images/film/案件赏析小屏.png b/src/assets/images/film/work/案件赏析小屏.png similarity index 100% rename from src/assets/images/film/案件赏析小屏.png rename to src/assets/images/film/work/案件赏析小屏.png diff --git a/src/assets/images/film/分镜制作-拍摄分镜.png b/src/assets/images/film/分镜制作-拍摄分镜.png index f204982..f9afef7 100644 Binary files a/src/assets/images/film/分镜制作-拍摄分镜.png and b/src/assets/images/film/分镜制作-拍摄分镜.png differ diff --git a/src/assets/images/film/脚本编写-拍摄脚本.png b/src/assets/images/film/脚本编写-拍摄脚本.png index 3d6a0d7..bd46fbe 100644 Binary files a/src/assets/images/film/脚本编写-拍摄脚本.png and b/src/assets/images/film/脚本编写-拍摄脚本.png differ diff --git a/src/assets/images/home/action/返回引导页.png b/src/assets/images/home/action/返回引导页.png new file mode 100644 index 0000000..40b9d80 Binary files /dev/null and b/src/assets/images/home/action/返回引导页.png differ diff --git a/src/assets/images/home/framework/open-img/Vlog-发现与记录.png b/src/assets/images/home/framework/open-img/Vlog-发现与记录.png new file mode 100644 index 0000000..8c1926f Binary files /dev/null and b/src/assets/images/home/framework/open-img/Vlog-发现与记录.png differ diff --git a/src/assets/images/home/framework/open-img/新手导航.png b/src/assets/images/home/framework/open-img/新手导航.png new file mode 100644 index 0000000..8618305 Binary files /dev/null and b/src/assets/images/home/framework/open-img/新手导航.png differ diff --git a/src/assets/images/home/framework/open-img/电影-创造与表达.png b/src/assets/images/home/framework/open-img/电影-创造与表达.png new file mode 100644 index 0000000..194f9df Binary files /dev/null and b/src/assets/images/home/framework/open-img/电影-创造与表达.png differ diff --git a/src/assets/images/home/framework/open-img/短视频达人养成记.png b/src/assets/images/home/framework/open-img/短视频达人养成记.png new file mode 100644 index 0000000..811668e Binary files /dev/null and b/src/assets/images/home/framework/open-img/短视频达人养成记.png differ diff --git a/src/assets/images/home/framework/open-img/短视频达人养成记_big.png b/src/assets/images/home/framework/open-img/短视频达人养成记_big.png new file mode 100644 index 0000000..594b2fe Binary files /dev/null and b/src/assets/images/home/framework/open-img/短视频达人养成记_big.png differ diff --git a/src/assets/images/home/framework/open-img/访谈-聆听与对话.png b/src/assets/images/home/framework/open-img/访谈-聆听与对话.png new file mode 100644 index 0000000..b9f2bb0 Binary files /dev/null and b/src/assets/images/home/framework/open-img/访谈-聆听与对话.png differ diff --git a/src/assets/images/home/framework/vlog-亮.png b/src/assets/images/home/framework/vlog-亮.png new file mode 100644 index 0000000..3e5f2f2 Binary files /dev/null and b/src/assets/images/home/framework/vlog-亮.png differ diff --git a/src/assets/images/home/framework/vlog-暗.png b/src/assets/images/home/framework/vlog-暗.png new file mode 100644 index 0000000..fdb4094 Binary files /dev/null and b/src/assets/images/home/framework/vlog-暗.png differ diff --git a/src/assets/images/home/framework/全屏-vlog.png b/src/assets/images/home/framework/全屏-vlog.png new file mode 100644 index 0000000..addb246 Binary files /dev/null and b/src/assets/images/home/framework/全屏-vlog.png differ diff --git a/src/assets/images/home/framework/全屏-新手导航.png b/src/assets/images/home/framework/全屏-新手导航.png new file mode 100644 index 0000000..c3b9436 Binary files /dev/null and b/src/assets/images/home/framework/全屏-新手导航.png differ diff --git a/src/assets/images/home/framework/全屏-电影.png b/src/assets/images/home/framework/全屏-电影.png new file mode 100644 index 0000000..96e5dd2 Binary files /dev/null and b/src/assets/images/home/framework/全屏-电影.png differ diff --git a/src/assets/images/home/framework/全屏-短视频达人养成记.png b/src/assets/images/home/framework/全屏-短视频达人养成记.png new file mode 100644 index 0000000..4559674 Binary files /dev/null and b/src/assets/images/home/framework/全屏-短视频达人养成记.png differ diff --git a/src/assets/images/home/framework/全屏-访谈.png b/src/assets/images/home/framework/全屏-访谈.png new file mode 100644 index 0000000..a13177b Binary files /dev/null and b/src/assets/images/home/framework/全屏-访谈.png differ diff --git a/src/assets/images/home/framework/封面-vlog.png b/src/assets/images/home/framework/封面-vlog.png new file mode 100644 index 0000000..ad52339 Binary files /dev/null and b/src/assets/images/home/framework/封面-vlog.png differ diff --git a/src/assets/images/home/framework/封面-总.png b/src/assets/images/home/framework/封面-总.png new file mode 100644 index 0000000..94bb11a Binary files /dev/null and b/src/assets/images/home/framework/封面-总.png differ diff --git a/src/assets/images/home/framework/封面-新手导航.png b/src/assets/images/home/framework/封面-新手导航.png new file mode 100644 index 0000000..e016a79 Binary files /dev/null and b/src/assets/images/home/framework/封面-新手导航.png differ diff --git a/src/assets/images/home/framework/封面-电影.png b/src/assets/images/home/framework/封面-电影.png new file mode 100644 index 0000000..3f634a6 Binary files /dev/null and b/src/assets/images/home/framework/封面-电影.png differ diff --git a/src/assets/images/home/framework/封面-访谈.png b/src/assets/images/home/framework/封面-访谈.png new file mode 100644 index 0000000..b2d12ee Binary files /dev/null and b/src/assets/images/home/framework/封面-访谈.png differ diff --git a/src/assets/images/home/framework/总课程框架-亮.png b/src/assets/images/home/framework/总课程框架-亮.png new file mode 100644 index 0000000..2ed345d Binary files /dev/null and b/src/assets/images/home/framework/总课程框架-亮.png differ diff --git a/src/assets/images/home/framework/总课程框架-暗.png b/src/assets/images/home/framework/总课程框架-暗.png new file mode 100644 index 0000000..36c3af4 Binary files /dev/null and b/src/assets/images/home/framework/总课程框架-暗.png differ diff --git a/src/assets/images/home/framework/新手导航-亮.png b/src/assets/images/home/framework/新手导航-亮.png new file mode 100644 index 0000000..95b9524 Binary files /dev/null and b/src/assets/images/home/framework/新手导航-亮.png differ diff --git a/src/assets/images/home/framework/新手导航-暗.png b/src/assets/images/home/framework/新手导航-暗.png new file mode 100644 index 0000000..1fc050c Binary files /dev/null and b/src/assets/images/home/framework/新手导航-暗.png differ diff --git a/src/assets/images/home/framework/电影-创造与表达-亮.png b/src/assets/images/home/framework/电影-创造与表达-亮.png new file mode 100644 index 0000000..935b0f2 Binary files /dev/null and b/src/assets/images/home/framework/电影-创造与表达-亮.png differ diff --git a/src/assets/images/home/framework/电影-创造与表达-暗.png b/src/assets/images/home/framework/电影-创造与表达-暗.png new file mode 100644 index 0000000..06e304e Binary files /dev/null and b/src/assets/images/home/framework/电影-创造与表达-暗.png differ diff --git a/src/assets/images/home/framework/访谈-聆听与对话-亮.png b/src/assets/images/home/framework/访谈-聆听与对话-亮.png new file mode 100644 index 0000000..ef6e18f Binary files /dev/null and b/src/assets/images/home/framework/访谈-聆听与对话-亮.png differ diff --git a/src/assets/images/home/framework/访谈-聆听与对话-暗.png b/src/assets/images/home/framework/访谈-聆听与对话-暗.png new file mode 100644 index 0000000..0a64a84 Binary files /dev/null and b/src/assets/images/home/framework/访谈-聆听与对话-暗.png differ diff --git a/src/assets/images/home/intro/手机.png b/src/assets/images/home/intro/手机.png new file mode 100644 index 0000000..5674f92 Binary files /dev/null and b/src/assets/images/home/intro/手机.png differ diff --git a/src/assets/images/home/intro/操作指南亮.png b/src/assets/images/home/intro/操作指南亮.png new file mode 100644 index 0000000..6e3adfb Binary files /dev/null and b/src/assets/images/home/intro/操作指南亮.png differ diff --git a/src/assets/images/home/intro/操作指南暗.png b/src/assets/images/home/intro/操作指南暗.png new file mode 100644 index 0000000..72c6249 Binary files /dev/null and b/src/assets/images/home/intro/操作指南暗.png differ diff --git a/src/assets/images/home/intro/背景底图.png b/src/assets/images/home/intro/背景底图.png new file mode 100644 index 0000000..847fc56 Binary files /dev/null and b/src/assets/images/home/intro/背景底图.png differ diff --git a/src/assets/images/home/intro/视频区域.png b/src/assets/images/home/intro/视频区域.png new file mode 100644 index 0000000..0177e25 Binary files /dev/null and b/src/assets/images/home/intro/视频区域.png differ diff --git a/src/assets/images/home/intro/视频底板.png b/src/assets/images/home/intro/视频底板.png new file mode 100644 index 0000000..a896cd4 Binary files /dev/null and b/src/assets/images/home/intro/视频底板.png differ diff --git a/src/assets/images/home/intro/课程简介亮.png b/src/assets/images/home/intro/课程简介亮.png new file mode 100644 index 0000000..cda5b11 Binary files /dev/null and b/src/assets/images/home/intro/课程简介亮.png differ diff --git a/src/assets/images/home/intro/课程简介暗.png b/src/assets/images/home/intro/课程简介暗.png new file mode 100644 index 0000000..752d2aa Binary files /dev/null and b/src/assets/images/home/intro/课程简介暗.png differ diff --git a/src/assets/images/home/stuy/学习进度底图.png b/src/assets/images/home/stuy/学习进度底图.png index b518f4e..9b9fcbf 100644 Binary files a/src/assets/images/home/stuy/学习进度底图.png and b/src/assets/images/home/stuy/学习进度底图.png differ diff --git a/src/assets/images/home/stuy/学习进度底图_.png b/src/assets/images/home/stuy/学习进度底图_.png new file mode 100644 index 0000000..b518f4e Binary files /dev/null and b/src/assets/images/home/stuy/学习进度底图_.png differ diff --git a/src/assets/images/home/新手导航/已完成.png b/src/assets/images/home/新手导航/已完成.png new file mode 100644 index 0000000..427c2b7 Binary files /dev/null and b/src/assets/images/home/新手导航/已完成.png differ diff --git a/src/assets/images/home/新手导航/未完成.png b/src/assets/images/home/新手导航/未完成.png new file mode 100644 index 0000000..62fb6f7 Binary files /dev/null and b/src/assets/images/home/新手导航/未完成.png differ diff --git a/src/assets/images/interview/img/育人故事配音.png b/src/assets/images/interview/img/育人故事配音.png new file mode 100644 index 0000000..427e421 Binary files /dev/null and b/src/assets/images/interview/img/育人故事配音.png differ diff --git a/src/assets/images/maps/dt.png b/src/assets/images/maps/dt.png deleted file mode 100644 index 39e1c3e..0000000 Binary files a/src/assets/images/maps/dt.png and /dev/null differ diff --git a/src/assets/images/skill/html.png b/src/assets/images/skill/html.png new file mode 100644 index 0000000..09f5415 Binary files /dev/null and b/src/assets/images/skill/html.png differ diff --git a/src/assets/images/skill/image.png b/src/assets/images/skill/image.png new file mode 100644 index 0000000..2dedc04 Binary files /dev/null and b/src/assets/images/skill/image.png differ diff --git a/src/assets/images/skill/video.png b/src/assets/images/skill/video.png new file mode 100644 index 0000000..e463d6c Binary files /dev/null and b/src/assets/images/skill/video.png differ diff --git a/src/assets/images/skill/暂未学习.png b/src/assets/images/skill/暂未学习.png new file mode 100644 index 0000000..e5adf51 Binary files /dev/null and b/src/assets/images/skill/暂未学习.png differ diff --git a/src/assets/images/vlog/img/上海植物园地图.jpg b/src/assets/images/vlog/img/上海植物园地图.jpg new file mode 100644 index 0000000..ef4b8e6 Binary files /dev/null and b/src/assets/images/vlog/img/上海植物园地图.jpg differ diff --git a/src/assets/images/vlog/img/上海植物园志愿者服务岗位清单.png b/src/assets/images/vlog/img/上海植物园志愿者服务岗位清单.png new file mode 100644 index 0000000..e08f285 Binary files /dev/null and b/src/assets/images/vlog/img/上海植物园志愿者服务岗位清单.png differ diff --git a/src/assets/images/vlog/practice/他人实战作品.png b/src/assets/images/vlog/practice/他人实战作品.png index b912571..cb41243 100644 Binary files a/src/assets/images/vlog/practice/他人实战作品.png and b/src/assets/images/vlog/practice/他人实战作品.png differ diff --git a/src/assets/images/vlog/practice/教师点评.png b/src/assets/images/vlog/practice/教师点评.png index 956822d..bf93c17 100644 Binary files a/src/assets/images/vlog/practice/教师点评.png and b/src/assets/images/vlog/practice/教师点评.png differ diff --git a/src/assets/images/work/vlog背景全屏待定.png b/src/assets/images/vlog/work/vlog背景全屏待定.png similarity index 100% rename from src/assets/images/work/vlog背景全屏待定.png rename to src/assets/images/vlog/work/vlog背景全屏待定.png diff --git a/src/assets/images/work/专业剖析小屏.png b/src/assets/images/vlog/work/专业剖析小屏.png similarity index 100% rename from src/assets/images/work/专业剖析小屏.png rename to src/assets/images/vlog/work/专业剖析小屏.png diff --git a/src/assets/images/vlog/work/思路提点全屏.png b/src/assets/images/vlog/work/思路提点全屏.png new file mode 100644 index 0000000..16bcec7 Binary files /dev/null and b/src/assets/images/vlog/work/思路提点全屏.png differ diff --git a/src/assets/images/work/思路提点小屏.png b/src/assets/images/vlog/work/思路提点小屏.png similarity index 100% rename from src/assets/images/work/思路提点小屏.png rename to src/assets/images/vlog/work/思路提点小屏.png diff --git a/src/assets/images/work/案例背景待定.png b/src/assets/images/vlog/work/案例背景待定.png similarity index 100% rename from src/assets/images/work/案例背景待定.png rename to src/assets/images/vlog/work/案例背景待定.png diff --git a/src/assets/images/vlog/进度数字/0.png b/src/assets/images/vlog/进度数字/0.png deleted file mode 100644 index 14f34ca..0000000 Binary files a/src/assets/images/vlog/进度数字/0.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/1.png b/src/assets/images/vlog/进度数字/1.png deleted file mode 100644 index 2a08e03..0000000 Binary files a/src/assets/images/vlog/进度数字/1.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/2.png b/src/assets/images/vlog/进度数字/2.png deleted file mode 100644 index d7e6f63..0000000 Binary files a/src/assets/images/vlog/进度数字/2.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/3.png b/src/assets/images/vlog/进度数字/3.png deleted file mode 100644 index f2e945f..0000000 Binary files a/src/assets/images/vlog/进度数字/3.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/4.png b/src/assets/images/vlog/进度数字/4.png deleted file mode 100644 index b05b68f..0000000 Binary files a/src/assets/images/vlog/进度数字/4.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/5.png b/src/assets/images/vlog/进度数字/5.png deleted file mode 100644 index 12bf8e2..0000000 Binary files a/src/assets/images/vlog/进度数字/5.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/6.png b/src/assets/images/vlog/进度数字/6.png deleted file mode 100644 index ac0bba4..0000000 Binary files a/src/assets/images/vlog/进度数字/6.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/7.png b/src/assets/images/vlog/进度数字/7.png deleted file mode 100644 index 5a18ac3..0000000 Binary files a/src/assets/images/vlog/进度数字/7.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/8.png b/src/assets/images/vlog/进度数字/8.png deleted file mode 100644 index bde83aa..0000000 Binary files a/src/assets/images/vlog/进度数字/8.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/9.png b/src/assets/images/vlog/进度数字/9.png deleted file mode 100644 index 565be27..0000000 Binary files a/src/assets/images/vlog/进度数字/9.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/percentage.png b/src/assets/images/vlog/进度数字/percentage.png deleted file mode 100644 index f8b5b8d..0000000 Binary files a/src/assets/images/vlog/进度数字/percentage.png and /dev/null differ diff --git a/src/assets/images/vlog/进度数字/进度.png b/src/assets/images/vlog/进度数字/进度.png deleted file mode 100644 index 02f1d16..0000000 Binary files a/src/assets/images/vlog/进度数字/进度.png and /dev/null differ diff --git a/src/assets/images/work/任务取景框-完成.png b/src/assets/images/work/任务取景框-完成.png new file mode 100644 index 0000000..427c2b7 Binary files /dev/null and b/src/assets/images/work/任务取景框-完成.png differ diff --git a/src/assets/images/work/任务取景框-未完成.png b/src/assets/images/work/任务取景框-未完成.png new file mode 100644 index 0000000..6752f63 Binary files /dev/null and b/src/assets/images/work/任务取景框-未完成.png differ diff --git a/src/assets/images/work/思路提点全屏.png b/src/assets/images/work/思路提点全屏.png deleted file mode 100644 index 55639c5..0000000 Binary files a/src/assets/images/work/思路提点全屏.png and /dev/null differ diff --git a/src/components/AnchoredProgressBar/AnchoredProgressBar.vue b/src/components/AnchoredProgressBar/AnchoredProgressBar.vue index 9068c23..b3476cf 100644 --- a/src/components/AnchoredProgressBar/AnchoredProgressBar.vue +++ b/src/components/AnchoredProgressBar/AnchoredProgressBar.vue @@ -1,159 +1,75 @@ diff --git a/src/components/AnchoredProgressBar/computeSegmentLayout.ts b/src/components/AnchoredProgressBar/computeSegmentLayout.ts new file mode 100644 index 0000000..d75c848 --- /dev/null +++ b/src/components/AnchoredProgressBar/computeSegmentLayout.ts @@ -0,0 +1,21 @@ +import type { ProgressSegment, ProgressSegmentLayout } from './types' + +const clamp = (value: number, lower: number, upper: number) => + Math.min(Math.max(value, lower), upper) + +export const computeSegmentLayout = ( + segments: ProgressSegment[], + min = 0, + max = 100, +): ProgressSegmentLayout[] => { + const range = max - min + const toPercent = (value: number) => + range <= 0 ? 0 : clamp(((value - min) / range) * 100, 0, 100) + + return segments.map((segment) => { + const left = toPercent(segment.start) + const right = toPercent(segment.end) + const width = Math.max(0, right - left) + return { ...segment, left, width, center: left + width / 2 } + }) +} diff --git a/src/components/AnchoredProgressBar/index.ts b/src/components/AnchoredProgressBar/index.ts index 574a981..297453f 100644 --- a/src/components/AnchoredProgressBar/index.ts +++ b/src/components/AnchoredProgressBar/index.ts @@ -1,2 +1,3 @@ export { default as AnchoredProgressBar } from './AnchoredProgressBar.vue' +export { computeSegmentLayout } from './computeSegmentLayout' export type * from './types' diff --git a/src/components/AnchoredProgressBar/types.ts b/src/components/AnchoredProgressBar/types.ts index 398c139..623bacf 100644 --- a/src/components/AnchoredProgressBar/types.ts +++ b/src/components/AnchoredProgressBar/types.ts @@ -1,38 +1,25 @@ -export type ProgressAnchor = { +export type ProgressSegment = { id: string - label: string - value: number - labelPosition?: 'top' | 'bottom' - children?: ProgressAnchor[] + label?: string + start: number + end: number + className?: string + style?: Record +} + +export type ProgressSegmentLayout = ProgressSegment & { + left: number + width: number + center: number } export type AnchoredProgressBarProps = { progress: number - anchors: ProgressAnchor[] min?: number max?: number - showPercent?: boolean - nodeImage?: string + segments?: ProgressSegment[] trackClass?: string fillClass?: string - markerClass?: string - labelClass?: string - childLabelClass?: string - percentClass?: string - labelTop?: string - childLabelTop?: string - barTop?: string - barHeight?: string - markerTop?: string - markerHeight?: string - markerWidth?: string - childMarkerTop?: string - childMarkerHeight?: string - childMarkerWidth?: string - nodeTop?: string - nodeHeight?: string - percentTop?: string - labelStyle?: Record - childLabelStyle?: Record - percentStyle?: Record + segmentClass?: string + showDividers?: boolean } diff --git a/src/components/Button/ImgButton.vue b/src/components/Button/ImgButton.vue index ef45cc0..7317fde 100644 --- a/src/components/Button/ImgButton.vue +++ b/src/components/Button/ImgButton.vue @@ -5,11 +5,11 @@ @@ -292,10 +376,32 @@ watch( overflow: hidden; cursor: grab; z-index: 1; + touch-action: none; +} +.sv-image-stage :deep(.loading-overlay) { + position: absolute; + z-index: 3; + background: transparent; +} +.sv-image-error { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + z-index: 3; + color: #fff; + font-size: 20px; + letter-spacing: 2px; } .sv-image { + opacity: 0; will-change: transform; - transition: transform 0.1s ease-out; + transition: + transform 0.1s ease-out, + opacity 0.2s ease; +} +.sv-image.loaded { + opacity: 1; } .sv-image.no-transition { transition: none !important; diff --git a/src/components/Loading/index.ts b/src/components/Loading/index.ts new file mode 100644 index 0000000..7dcd022 --- /dev/null +++ b/src/components/Loading/index.ts @@ -0,0 +1 @@ +export { default as LoadingOverlay } from './LoadingOverlay.vue' diff --git a/src/components/ProgressBar/ProgressBar.vue b/src/components/ProgressBar/ProgressBar.vue index 496ab16..a07cc62 100644 --- a/src/components/ProgressBar/ProgressBar.vue +++ b/src/components/ProgressBar/ProgressBar.vue @@ -8,9 +8,15 @@
-
+
-
+
@@ -67,13 +73,17 @@ ? 'text-white/60' : currentStep === index + 1 ? 'text-[#fbbf24]' - : 'text-white' + : 'text-white', ]" > {{ step.name }} - + @@ -101,7 +114,10 @@
-
+
- - - + + +
diff --git a/src/components/Video/index.ts b/src/components/Video/index.ts index 0a6f5d5..d646a60 100644 --- a/src/components/Video/index.ts +++ b/src/components/Video/index.ts @@ -5,57 +5,33 @@ import type { SlotsType, } from 'vue' import VideoSfc from './Video.vue' +import type { + PlayFailedPayload, + TimeUnit, + VideoAnchor, + VideoProps, + VideoSlotProps, +} from './types' -type TimeUnit = number - -interface VideoSource { - src: string - type?: string -} - -interface VideoAnchor { - id: string - time: number - enabled?: boolean - afterResume?: 'hide' | 'remove' | 'keep' - meta?: Record -} - -interface VideoProps { - src?: string - sources?: VideoSource[] - autoplay?: boolean - muted?: boolean - loop?: boolean - preload?: 'auto' | 'metadata' | 'none' - poster?: string - controls?: boolean - hideNativeControls?: boolean - autoHideControls?: boolean - autoHideDelay?: number - showCenterPlayIcon?: boolean - modelValue?: boolean - volume?: number - playbackRate?: number - fit?: 'contain' | 'cover' | 'fill' - anchors?: VideoAnchor[] +type VideoEventProps = { + onPlay?: () => void + onPause?: () => void onEnded?: () => void + onTimeupdate?: (currentTime: TimeUnit, duration: TimeUnit) => void + onError?: (error: unknown) => void + onLoadedmetadata?: (duration: TimeUnit) => void + onLoadeddata?: () => void + onCanplay?: () => void + onCanplaythrough?: () => void + onRatechange?: (rate: number) => void + onVolumechange?: (volume: number, muted: boolean) => void + onSeeking?: (currentTime: TimeUnit) => void + onSeeked?: (currentTime: TimeUnit) => void + onPlayfailed?: (payload: PlayFailedPayload) => void + 'onUpdate:modelValue'?: (playing: boolean) => void } -interface VideoSlotProps { - video: HTMLVideoElement | undefined - playing: boolean - currentTime: TimeUnit - duration: TimeUnit - progressPercent: number - bufferedPercent: number - volume: number - rate: number - togglePlay: () => Promise - seek: (time: number) => void - setVolume: (volume: number) => void - setRate: (rate: number) => void -} +type VideoComponentProps = VideoProps & VideoEventProps type VideoSlots = SlotsType<{ default?: () => any @@ -71,7 +47,7 @@ type VideoSlots = SlotsType<{ }> type VideoComponent = DefineComponent< - VideoProps, + VideoComponentProps, Record, Record, Record, @@ -86,14 +62,18 @@ type VideoComponent = DefineComponent< timeupdate: (currentTime: TimeUnit, duration: TimeUnit) => void error: (error: unknown) => void loadedmetadata: (duration: TimeUnit) => void + loadeddata: () => void + canplay: () => void + canplaythrough: () => void ratechange: (rate: number) => void volumechange: (volume: number, muted: boolean) => void seeking: (currentTime: TimeUnit) => void seeked: (currentTime: TimeUnit) => void + playfailed: (payload: PlayFailedPayload) => void }, string, PublicProps, - Readonly, + Readonly, Record, VideoSlots > @@ -102,4 +82,4 @@ const Video = VideoSfc as VideoComponent export { Video } export default Video -export type { VideoAnchor, VideoProps, VideoSlotProps } +export type { VideoAnchor, VideoProps, VideoSlotProps, PlayFailedPayload } diff --git a/src/components/Video/types.ts b/src/components/Video/types.ts new file mode 100644 index 0000000..67ac63e --- /dev/null +++ b/src/components/Video/types.ts @@ -0,0 +1,60 @@ +export type TimeUnit = number +export type PlayAttemptReason = + | 'toggle' + | 'model' + | 'anchor-resume' + | 'source-change' + +export interface PlayFailedPayload { + reason: PlayAttemptReason + error: unknown + src?: string +} + +export interface VideoSource { + src: string + type?: string +} + +export interface VideoAnchor { + id: string + time: number + enabled?: boolean + afterResume?: 'hide' | 'remove' | 'keep' + meta?: Record +} + +export interface VideoProps { + src?: string + sources?: VideoSource[] + autoplay?: boolean + muted?: boolean + loop?: boolean + preload?: 'auto' | 'metadata' | 'none' + poster?: string + controls?: boolean + hideNativeControls?: boolean + autoHideControls?: boolean + autoHideDelay?: number + showCenterPlayIcon?: boolean + modelValue?: boolean + volume?: number + playbackRate?: number + fit?: 'contain' | 'cover' | 'fill' + anchors?: VideoAnchor[] +} + +export interface VideoSlotProps { + video: HTMLVideoElement | undefined + playing: boolean + currentTime: TimeUnit + duration: TimeUnit + progressPercent: number + bufferedPercent: number + volume: number + rate: number + togglePlay: () => Promise + seek: (time: number) => void + setVolume: (volume: number) => void + setRate: (rate: number) => void +} diff --git a/src/components/index.ts b/src/components/index.ts index ec2015d..ace2db3 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -18,6 +18,12 @@ export * from './Switcher' // 图片预览(函数式) export * from './ImagePreview' +// 课程指南弹窗(函数式) +export * from './CourseGuidePopup' + +// 课程框架弹窗(函数式) +export * from './CourseFrameworkPopup' + // 弹窗组件 export * from './Modal' export * from './UnfinishedLearningPrompt' @@ -25,6 +31,9 @@ export * from './UnfinishedLearningPrompt' // 遮罩层组件 export * from './Overlay' +// Loading 加载组件 +export * from './Loading' + // 视频组件 export * from './Video' diff --git a/src/composables/useScale.ts b/src/composables/useScale.ts index 2e552fb..636e8ab 100644 --- a/src/composables/useScale.ts +++ b/src/composables/useScale.ts @@ -1,64 +1,108 @@ import { ref, onMounted, onUnmounted, computed } from 'vue' +const DESIGN_WIDTH = 1920 +const DESIGN_HEIGHT = 1080 + +const getViewportWidth = () => window.visualViewport?.width ?? window.innerWidth +const getViewportHeight = () => + window.visualViewport?.height ?? window.innerHeight +const getWindowWidth = () => + typeof window === 'undefined' ? DESIGN_WIDTH : Math.round(getViewportWidth()) +const getWindowHeight = () => + typeof window === 'undefined' + ? DESIGN_HEIGHT + : Math.round(getViewportHeight()) + +const windowWidth = ref(getWindowWidth()) +const windowHeight = ref(getWindowHeight()) + +const scale = computed(() => { + const scaleX = windowWidth.value / DESIGN_WIDTH + const scaleY = windowHeight.value / DESIGN_HEIGHT + + return Math.min(scaleX, scaleY) +}) + +const scaledWidth = computed(() => DESIGN_WIDTH * scale.value) +const scaledHeight = computed(() => DESIGN_HEIGHT * scale.value) +const offsetX = computed(() => (windowWidth.value - scaledWidth.value) / 2) +const offsetY = computed(() => (windowHeight.value - scaledHeight.value) / 2) + +const scaleStyle = computed(() => ({ + transform: `scale(${scale.value})`, + transformOrigin: 'top left', + width: `${DESIGN_WIDTH}px`, + height: `${DESIGN_HEIGHT}px`, + position: 'absolute' as const, + left: `${offsetX.value}px`, + top: `${offsetY.value}px`, +})) + +let mountedCount = 0 +let updateTimer: ReturnType | undefined + +const updateWindowSize = () => { + const nextWidth = getWindowWidth() + const nextHeight = getWindowHeight() + + if (windowWidth.value === nextWidth && windowHeight.value === nextHeight) + return + + windowWidth.value = nextWidth + windowHeight.value = nextHeight +} + +const scheduleWindowSizeUpdate = (delay = 100) => { + if (updateTimer) clearTimeout(updateTimer) + + updateTimer = setTimeout(() => { + updateTimer = undefined + updateWindowSize() + }, delay) +} + +const handleResize = () => scheduleWindowSizeUpdate() +const handleOrientationChange = () => scheduleWindowSizeUpdate(300) + +const startWindowSizeListener = () => { + if (typeof window === 'undefined') return + + if (mountedCount === 0) { + window.addEventListener('resize', handleResize) + window.addEventListener('orientationchange', handleOrientationChange) + window.visualViewport?.addEventListener('resize', handleResize) + } + + mountedCount += 1 + updateWindowSize() +} + +const stopWindowSizeListener = () => { + if (typeof window === 'undefined' || mountedCount === 0) return + + mountedCount -= 1 + + if (mountedCount > 0) return + + window.removeEventListener('resize', handleResize) + window.removeEventListener('orientationchange', handleOrientationChange) + window.visualViewport?.removeEventListener('resize', handleResize) + + if (updateTimer) { + clearTimeout(updateTimer) + updateTimer = undefined + } +} + /** * 等比例缩放Hook * 基于1920*1080设计稿进行等比例缩放 */ export function useScale() { - // 设计稿尺寸 - const DESIGN_WIDTH = 1920 - const DESIGN_HEIGHT = 1080 - - // 当前窗口尺寸 - const windowWidth = ref(window.innerWidth) - const windowHeight = ref(window.innerHeight) - - // 缩放比例 - const scale = computed(() => { - const scaleX = windowWidth.value / DESIGN_WIDTH - const scaleY = windowHeight.value / DESIGN_HEIGHT - - // 取较小的缩放比例,确保内容完全显示在视窗内 - return Math.min(scaleX, scaleY) - }) - - // 缩放后的实际尺寸 - const scaledWidth = computed(() => DESIGN_WIDTH * scale.value) - const scaledHeight = computed(() => DESIGN_HEIGHT * scale.value) - - // 居中偏移量 - const offsetX = computed(() => (windowWidth.value - scaledWidth.value) / 2) - const offsetY = computed(() => (windowHeight.value - scaledHeight.value) / 2) - - // 缩放样式对象 - const scaleStyle = computed(() => ({ - transform: `scale(${scale.value})`, - transformOrigin: 'top left', - width: `${DESIGN_WIDTH}px`, - height: `${DESIGN_HEIGHT}px`, - position: 'absolute' as const, - left: `${offsetX.value}px`, - top: `${offsetY.value}px` - })) - - // 更新窗口尺寸 - const updateWindowSize = () => { - windowWidth.value = window.innerWidth - windowHeight.value = window.innerHeight - } - - // 监听窗口大小变化 - onMounted(() => { - window.addEventListener('resize', updateWindowSize) - updateWindowSize() - }) - - onUnmounted(() => { - window.removeEventListener('resize', updateWindowSize) - }) + onMounted(startWindowSizeListener) + onUnmounted(stopWindowSizeListener) return { - // 响应式数据 windowWidth, windowHeight, scale, @@ -66,12 +110,8 @@ export function useScale() { scaledHeight, offsetX, offsetY, - - // 样式对象 scaleStyle, - - // 常量 DESIGN_WIDTH, - DESIGN_HEIGHT + DESIGN_HEIGHT, } } diff --git a/src/config/moduleStepMap.ts b/src/config/moduleStepMap.ts index 14efe7a..7c428fc 100644 --- a/src/config/moduleStepMap.ts +++ b/src/config/moduleStepMap.ts @@ -1,6 +1,38 @@ export type ModuleName = 'film' | 'vlog' | 'interview' export type StepIndex = 1 | 2 | 3 +export const basicElementsFoundationVideoKeys = [ + 'basic-composition:step1', + 'basic-composition:step2', + 'basic-light:step2', + 'basic-light:step3', + 'basic-light:step4', + 'basic-camera-movement:step2', + 'basic-camera-movement:step3', +] as const + +export const basicElementsShootingVideoKeys = [ + 'basic-shooting-skills:step2', + 'basic-shooting-skills:step3', + 'basic-shooting-skills:step4', + 'basic-shooting-skills:step5', + 'basic-shooting-skills:step6', + 'basic-shooting-skills:step7', +] as const + +export const basicElementsEditingVideoKeys = [ + 'basic-short-video-editing-skills:step2', + 'basic-short-video-editing-skills:step3', + 'basic-short-video-editing-skills:step4', + 'basic-short-video-editing-skills:step5', + 'basic-short-video-editing-skills:step6', + 'basic-short-video-editing-skills:step7', + 'basic-short-video-editing-skills:step8', + 'basic-short-video-editing-skills:step9', + 'basic-short-video-editing-skills:step10', + 'basic-short-video-editing-skills:step11', +] as const + /** * 模块步骤 -> 子页面 route.name 列表 * @@ -45,12 +77,21 @@ export const moduleStepMap: Record< } as const export const videoCompletionRouteNames = new Set([ + 'FilmMidtermShooting', 'FilmOnsiteShooting', 'FilmEditing', + 'FilmColor', + 'FilmColorPractice', + 'FilmExportPublish', + 'VlogMidtermShooting', 'VlogShooting', 'VlogEditing', + 'VlogColor', + 'VlogExportPublish', + 'InterviewShooting', 'InterviewOnsiteShooting', 'InterviewEditing', + 'InterviewExportPublish', ]) export const allTrackedRouteNames = new Set( diff --git a/src/config/studyProgress.ts b/src/config/studyProgress.ts new file mode 100644 index 0000000..7a6521a --- /dev/null +++ b/src/config/studyProgress.ts @@ -0,0 +1,107 @@ +import { + basicElementsEditingVideoKeys, + basicElementsFoundationVideoKeys, + basicElementsShootingVideoKeys, + moduleStepMap, +} from './moduleStepMap' + +export type ProductionModuleKey = 'vlog' | 'interview' | 'film' + +type StepKey = 1 | 2 | 3 + +export type ViewedPredicate = (name: string) => boolean +export type ViewedInput = ViewedPredicate | Record + +const toPredicate = (viewed: ViewedInput): ViewedPredicate => + typeof viewed === 'function' + ? viewed + : (name: string) => viewed[name] === true + +const countViewedRoutes = ( + routes: readonly string[], + isViewed: ViewedPredicate, +) => { + if (routes.length === 0) return 0 + return routes.filter((routeName) => isViewed(routeName)).length +} + +const getStepProgress = ( + routes: readonly string[], + isViewed: ViewedPredicate, +) => { + if (routes.length === 0) return 0 + return countViewedRoutes(routes, isViewed) / routes.length +} + +const WORK_COMPLETION_KEYS = ['case', 'profession', 'idea'] as const +const PRACTICE_ROUTE_MAP: Record = { + vlog: 'VlogPractice', + interview: 'InterviewPractice', + film: 'FilmPractice', +} + +const getWorkProgress = (workVideosWatched: Record) => + countViewedRoutes( + WORK_COMPLETION_KEYS, + (key) => workVideosWatched[key] === true, + ) / WORK_COMPLETION_KEYS.length + +export const getProductionModuleProgress = ( + moduleKey: ProductionModuleKey, + viewed: ViewedInput, + workVideosWatched: Record = {}, +) => { + const isViewed = toPredicate(viewed) + const stepRoutes = moduleStepMap[moduleKey] + const taskProgress = getWorkProgress(workVideosWatched) * 20 + const stepProgress = (step: StepKey) => + getStepProgress(stepRoutes[step], isViewed) + const step1Progress = stepProgress(1) * 22 + const step2Progress = stepProgress(2) * 22 + const step3Progress = stepProgress(3) * 21 + const progress = taskProgress + step1Progress + step2Progress + step3Progress + + if ( + progress >= 85 && + step1Progress === 22 && + step2Progress === 22 && + step3Progress === 21 && + isViewed(PRACTICE_ROUTE_MAP[moduleKey]) + ) { + return 100 + } + + return Math.round(progress) +} + +const BASIC_ELEMENTS_VIDEO_KEYS = [ + ...basicElementsFoundationVideoKeys, + ...basicElementsShootingVideoKeys, + ...basicElementsEditingVideoKeys, +] as const + +export const getBasicElementsProgress = ( + unlockProgress: number, + isUnlocked: boolean, + learningVideosWatched: Record = {}, +) => { + if (isUnlocked) return 100 + + const watchedProgress = + (countViewedRoutes( + BASIC_ELEMENTS_VIDEO_KEYS, + (key) => learningVideosWatched[key] === true, + ) / + BASIC_ELEMENTS_VIDEO_KEYS.length) * + 100 + + return Math.round( + Math.max(0, Math.min(100, Math.max(unlockProgress, watchedProgress))), + ) +} + +export const getOverallStudyProgress = (progresses: readonly number[]) => { + if (progresses.length === 0) return 0 + const total = progresses.reduce((sum, progress) => sum + progress, 0) + return Math.round(total / progresses.length) +} diff --git a/src/main.ts b/src/main.ts index 197f8bd..ad90ee1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,25 @@ import './style.css' import App from './App.vue' import router from './router' +// 在开发模式的移动端启用 vConsole(需先安装 vconsole 依赖) +if (import.meta.env.DEV && typeof window !== 'undefined') { + const ua = window.navigator.userAgent.toLowerCase() + const isMobile = /iphone|ipad|ipod|android|mobile/.test(ua) + + if (isMobile) { + // 动态加载,避免生产环境打包 & 未安装时报错 + import('vconsole') + .then((mod) => { + const VConsole = (mod as any).default || (mod as any) + + new VConsole() + }) + .catch(() => { + // vconsole 未安装或加载失败时静默忽略 + }) + } +} + const app = createApp(App) const pinia = createPinia() pinia.use(piniaPluginPersistedstate) diff --git a/src/router/index.ts b/src/router/index.ts index 24dfa81..b2c954f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -51,14 +51,6 @@ const routes: RouteRecordRaw[] = [ title: '技能树', }, }, - { - path: '/skill/video', - name: 'SkillVdieo', - component: () => import('@/views/skill/video.vue'), - meta: { - title: '技能树视频', - }, - }, NotFound, ] diff --git a/src/stores/modules/basic-elements.ts b/src/stores/modules/basic-elements.ts index 65be112..a152969 100644 --- a/src/stores/modules/basic-elements.ts +++ b/src/stores/modules/basic-elements.ts @@ -11,6 +11,8 @@ interface BasicElementsSavePayload { basicElementsUnlockProgress?: number learningVideosWatched?: Record guideShown?: Record + hasFinishedUnlockedGuide?: boolean + uploadedCourseOrders?: number[] } async function getYibanApi(): Promise { @@ -40,6 +42,8 @@ export const useBasicElementsStore = defineStore( const basicElementsUnlockProgress = ref(0) const learningVideosWatched = ref>({}) const guideShown = ref>({}) + const hasFinishedUnlockedGuide = ref(false) + const uploadedCourseOrders = ref([]) //Actions function saveBasicElementsStore(): void { @@ -50,6 +54,8 @@ export const useBasicElementsStore = defineStore( basicElementsUnlockProgress: basicElementsUnlockProgress.value, learningVideosWatched: learningVideosWatched.value, guideShown: guideShown.value, + hasFinishedUnlockedGuide: hasFinishedUnlockedGuide.value, + uploadedCourseOrders: uploadedCourseOrders.value, } getYibanApi() @@ -84,6 +90,8 @@ export const useBasicElementsStore = defineStore( data.basicElementsUnlockProgress ?? 0 learningVideosWatched.value = data.learningVideosWatched ?? {} guideShown.value = data.guideShown ?? {} + hasFinishedUnlockedGuide.value = data.hasFinishedUnlockedGuide ?? false + uploadedCourseOrders.value = data.uploadedCourseOrders ?? [] } catch (error) { console.error('Failed to load Basic Elements store', error) } @@ -115,11 +123,27 @@ export const useBasicElementsStore = defineStore( saveBasicElementsStore() } + function markUnlockedGuideFinished(): void { + hasFinishedUnlockedGuide.value = true + saveBasicElementsStore() + } + + function markCourseOrderUploaded(order: number): void { + if (uploadedCourseOrders.value.includes(order)) return + uploadedCourseOrders.value = [...uploadedCourseOrders.value, order].sort( + (left, right) => left - right, + ) + saveBasicElementsStore() + } + function markLearningVideoWatched(key: string): void { if (!key) return if (learningVideosWatched.value[key]) return learningVideosWatched.value[key] = true saveBasicElementsStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncBasicElementsCourseProgress(), + ) } function resetBasicElementsState(): void { @@ -129,6 +153,7 @@ export const useBasicElementsStore = defineStore( basicElementsUnlockProgress.value = 0 learningVideosWatched.value = {} guideShown.value = {} + hasFinishedUnlockedGuide.value = false saveBasicElementsStore() } @@ -139,6 +164,8 @@ export const useBasicElementsStore = defineStore( setIsBasicElementsUnlock, setBasicElementsUnlockProgress, markGuideShown, + markUnlockedGuideFinished, + markCourseOrderUploaded, markLearningVideoWatched, saveBasicElementsStore, loadBasicElementsStore, @@ -150,6 +177,8 @@ export const useBasicElementsStore = defineStore( basicElementsUnlockProgress, learningVideosWatched, guideShown, + hasFinishedUnlockedGuide, + uploadedCourseOrders, } }, { diff --git a/src/stores/modules/film.ts b/src/stores/modules/film.ts index 892c97d..aad4be5 100644 --- a/src/stores/modules/film.ts +++ b/src/stores/modules/film.ts @@ -1,6 +1,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { moduleStepMap } from '@/config/moduleStepMap' +import { getProductionModuleProgress } from '@/config/studyProgress' export const FILM_LESSON_ID = 'dianying_rwqjk' export const FILM_KEY = 4 @@ -52,6 +53,7 @@ export const FILM_ONSITE_ROUTE_NAMES = [ ] as const const DERIVED_FILM_ROUTE_NAMES = new Set([ + 'FilmMidtermShooting', 'FilmOnsiteShooting', 'FilmEditing', ]) @@ -102,6 +104,7 @@ export const useFilmStore = defineStore( const guideShown = ref>({}) const practiceScrollPage = ref(0) const workSelectedTab = ref('case') + const shouldCheckWorkCompletion = ref(false) const practiceNoteContent = ref('') const practiceSelfEvaluationScores = ref({}) const practiceWorkRatings = ref({}) @@ -178,6 +181,9 @@ export const useFilmStore = defineStore( Math.min(completedStep.value + 1, 4), ) saveFilmStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncFilmCourseProgress(), + ) } function markViewed(key: string): void { @@ -245,6 +251,9 @@ export const useFilmStore = defineStore( if (workVideosWatched.value[key]) return workVideosWatched.value[key] = true saveFilmStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncFilmCourseProgress(), + ) } function setIsGuideFilmEditingComplete(value: boolean): void { @@ -268,6 +277,16 @@ export const useFilmStore = defineStore( saveFilmStore() } + function requestWorkCompletionCheck(): void { + shouldCheckWorkCompletion.value = true + } + + function consumeWorkCompletionCheck(): boolean { + if (!shouldCheckWorkCompletion.value) return false + shouldCheckWorkCompletion.value = false + return true + } + function setPracticeNoteContent(value: string): void { practiceNoteContent.value = value saveFilmStore() @@ -299,6 +318,10 @@ export const useFilmStore = defineStore( const areEditingVideosWatched = (keys: readonly string[]) => keys.every((key) => editingVideosWatched.value[key] === true) + const allMidtermShootingViewed = computed(() => + areRoutesViewed(['FilmFilmingLocationA', 'FilmFilmingLocationB']), + ) + const allOnsiteScenesViewed = computed(() => areRoutesViewed(FILM_ONSITE_ROUTE_NAMES), ) @@ -308,6 +331,7 @@ export const useFilmStore = defineStore( ) function isRouteViewed(name: string): boolean { + if (name === 'FilmMidtermShooting') return allMidtermShootingViewed.value if (name === 'FilmOnsiteShooting') return allOnsiteScenesViewed.value if (name === 'FilmEditing') return allEditingVideosWatched.value return viewed.value[name] === true @@ -321,19 +345,22 @@ export const useFilmStore = defineStore( const isDone = (step: 1 | 2 | 3) => stepRouteNames[step].every((name) => isRouteViewed(name)) + if (isDone(1) && isDone(2) && isDone(3) && viewed.value.FilmPractice) + return 4 if (isDone(1) && isDone(2) && isDone(3)) return 3 if (isDone(1) && isDone(2)) return 2 if (isDone(1)) return 1 return 0 }) - // 按子页面粒度的学习进度百分比 - const progressPercent = computed(() => { - const allRoutes = (Object.values(stepRouteNames) as string[][]).flat() - if (allRoutes.length === 0) return 0 - const viewedCount = allRoutes.filter((name) => isRouteViewed(name)).length - return Math.round((viewedCount / allRoutes.length) * 100) - }) + // 按子页面粒度的学习进度百分比(含任务取景框与实战操作台) + const progressPercent = computed(() => + getProductionModuleProgress( + 'film', + isRouteViewed, + workVideosWatched.value, + ), + ) // Reset all Film-related persistent state function resetFilmState(): void { @@ -375,6 +402,8 @@ export const useFilmStore = defineStore( markGuideShown, setPracticeScrollPage, setWorkSelectedTab, + requestWorkCompletionCheck, + consumeWorkCompletionCheck, setPracticeNoteContent, setPracticeSelfEvaluationScores, setPracticeWorkRating, diff --git a/src/stores/modules/interview.ts b/src/stores/modules/interview.ts index 2d24d7c..6ca6dde 100644 --- a/src/stores/modules/interview.ts +++ b/src/stores/modules/interview.ts @@ -1,6 +1,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { moduleStepMap } from '@/config/moduleStepMap' +import { getProductionModuleProgress } from '@/config/studyProgress' export const INTERVIEW_LESSON_ID = 'fangtan_rwqjk' export const INTERVIEW_KEY = 3 @@ -90,6 +91,7 @@ export const useInterviewStore = defineStore( const guideShown = ref>({}) const practiceScrollPage = ref(0) const workSelectedTab = ref('case') + const shouldCheckWorkCompletion = ref(false) const practiceNoteContent = ref('') const practiceSelfEvaluationScores = ref({}) const practiceWorkRatings = ref({}) @@ -167,6 +169,9 @@ export const useInterviewStore = defineStore( Math.min(completedStep.value + 1, 4), ) saveInterviewStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncInterviewCourseProgress(), + ) } function areProfessionVideosWatched(keys: readonly string[]): boolean { @@ -211,24 +216,48 @@ export const useInterviewStore = defineStore( return practiceWorkRatings.value[workKey] ?? {} } + const areRoutesViewed = (routeNames: readonly string[]) => + routeNames.every((name) => viewed.value[name] === true) + + const allMidtermShootingViewed = computed(() => + areRoutesViewed([ + 'InterviewFilmingLocationA', + 'InterviewFilmingLocationB', + ]), + ) + + function isRouteViewed(name: string): boolean { + if (name === 'InterviewShooting') return allMidtermShootingViewed.value + if (name === 'InterviewOnsiteShooting') { + return areProfessionVideosWatched(INTERVIEW_ONSITE_VIDEO_KEYS) + } + if (name === 'InterviewEditing') { + return areProfessionVideosWatched(INTERVIEW_EDITING_VIDEO_KEYS) + } + return viewed.value[name] === true + } + const stepRouteNames = moduleStepMap.interview const completedStep = computed(() => { const isDone = (step: 1 | 2 | 3) => - stepRouteNames[step].every((name) => viewed.value[name] === true) + stepRouteNames[step].every((name) => isRouteViewed(name)) + if (isDone(1) && isDone(2) && isDone(3) && viewed.value.InterviewPractice) + return 4 if (isDone(1) && isDone(2) && isDone(3)) return 3 if (isDone(1) && isDone(2)) return 2 if (isDone(1)) return 1 return 0 }) - const progressPercent = computed(() => { - const allRoutes = (Object.values(stepRouteNames) as string[][]).flat() - if (allRoutes.length === 0) return 0 - const viewedCount = allRoutes.filter((name) => viewed.value[name]).length - return Math.round((viewedCount / allRoutes.length) * 100) - }) + const progressPercent = computed(() => + getProductionModuleProgress( + 'interview', + isRouteViewed, + workVideosWatched.value, + ), + ) function setIsGuideInterviewTask(value: boolean): void { isGuideInterviewTask.value = value @@ -278,6 +307,9 @@ export const useInterviewStore = defineStore( if (workVideosWatched.value[key]) return workVideosWatched.value[key] = true saveInterviewStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncInterviewCourseProgress(), + ) } function markOnsiteVideoWatched(key: string): void { @@ -316,6 +348,16 @@ export const useInterviewStore = defineStore( saveInterviewStore() } + function requestWorkCompletionCheck(): void { + shouldCheckWorkCompletion.value = true + } + + function consumeWorkCompletionCheck(): boolean { + if (!shouldCheckWorkCompletion.value) return false + shouldCheckWorkCompletion.value = false + return true + } + function resetInterviewState(): void { viewed.value = {} isGuideInterviewTask.value = true @@ -353,6 +395,8 @@ export const useInterviewStore = defineStore( markGuideShown, setPracticeScrollPage, setWorkSelectedTab, + requestWorkCompletionCheck, + consumeWorkCompletionCheck, setPracticeNoteContent, setPracticeSelfEvaluationScores, setPracticeWorkRating, @@ -360,6 +404,7 @@ export const useInterviewStore = defineStore( resetInterviewState, loadInterviewStore, saveInterviewStore, + isRouteViewed, //Actions completedStep, progressPercent, diff --git a/src/stores/modules/vlog.ts b/src/stores/modules/vlog.ts index 2c4f068..74737a7 100644 --- a/src/stores/modules/vlog.ts +++ b/src/stores/modules/vlog.ts @@ -1,6 +1,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { moduleStepMap } from '@/config/moduleStepMap' +import { getProductionModuleProgress } from '@/config/studyProgress' export const VLOG_LESSON_ID = 'vlog_rwqjk' export const VLOG_KEY = 2 @@ -91,6 +92,7 @@ export const useVlogStore = defineStore( const guideShown = ref>({}) const practiceScrollPage = ref(0) const workSelectedTab = ref('case') + const shouldCheckWorkCompletion = ref(false) const practiceNoteContent = ref('') const practiceSelfEvaluationScores = ref({}) const practiceWorkRatings = ref({}) @@ -164,6 +166,9 @@ export const useVlogStore = defineStore( Math.min(completedStep.value + 1, 4), ) saveVlogStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncVlogCourseProgress(), + ) } function areProfessionVideosWatched(keys: readonly string[]): boolean { @@ -223,6 +228,9 @@ export const useVlogStore = defineStore( if (workVideosWatched.value[key]) return workVideosWatched.value[key] = true saveVlogStore() + void import('@/utils/yibanLessonProgress').then((api) => + api.syncVlogCourseProgress(), + ) } function markOnsiteVideoWatched(key: string): void { @@ -266,6 +274,16 @@ export const useVlogStore = defineStore( saveVlogStore() } + function requestWorkCompletionCheck(): void { + shouldCheckWorkCompletion.value = true + } + + function consumeWorkCompletionCheck(): boolean { + if (!shouldCheckWorkCompletion.value) return false + shouldCheckWorkCompletion.value = false + return true + } + // 基于“子页面是否已查看”的大步骤完成度(1..3) // disabled 子项不计入(在这里直接不放进列表) function setPracticeNoteContent(value: string): void { @@ -293,25 +311,46 @@ export const useVlogStore = defineStore( return practiceWorkRatings.value[workKey] ?? {} } + const areRoutesViewed = (routeNames: readonly string[]) => + routeNames.every((name) => viewed.value[name] === true) + + const allMidtermShootingViewed = computed(() => + areRoutesViewed(['VlogFilmingLocationA', 'VlogFilmingLocationB']), + ) + + function isRouteViewed(name: string): boolean { + if (name === 'VlogMidtermShooting') return allMidtermShootingViewed.value + if (name === 'VlogShooting') { + return areProfessionVideosWatched(VLOG_ONSITE_VIDEO_KEYS) + } + if (name === 'VlogEditing') { + return areProfessionVideosWatched(VLOG_EDITING_VIDEO_KEYS) + } + return viewed.value[name] === true + } + const stepRouteNames = moduleStepMap.vlog const completedStep = computed(() => { const isDone = (step: 1 | 2 | 3) => - stepRouteNames[step].every((name) => viewed.value[name] === true) + stepRouteNames[step].every((name) => isRouteViewed(name)) + if (isDone(1) && isDone(2) && isDone(3) && viewed.value.VlogPractice) + return 4 if (isDone(1) && isDone(2) && isDone(3)) return 3 if (isDone(1) && isDone(2)) return 2 if (isDone(1)) return 1 return 0 }) - // 按子页面粒度的学习进度百分比 - const progressPercent = computed(() => { - const allRoutes = (Object.values(stepRouteNames) as string[][]).flat() - if (allRoutes.length === 0) return 0 - const viewedCount = allRoutes.filter((name) => viewed.value[name]).length - return Math.round((viewedCount / allRoutes.length) * 100) - }) + // 按子页面粒度的学习进度百分比(含任务取景框与实战操作台) + const progressPercent = computed(() => + getProductionModuleProgress( + 'vlog', + isRouteViewed, + workVideosWatched.value, + ), + ) // Reset all Vlog-related persistent state function resetVlogState(): void { @@ -355,10 +394,13 @@ export const useVlogStore = defineStore( markGuideShown, setPracticeScrollPage, setWorkSelectedTab, + requestWorkCompletionCheck, + consumeWorkCompletionCheck, setPracticeNoteContent, setPracticeSelfEvaluationScores, setPracticeWorkRating, getPracticeWorkRating, + isRouteViewed, //Actions completedStep, progressPercent, diff --git a/src/utils/imageProcessing.ts b/src/utils/imageProcessing.ts index e7e360e..d227e67 100644 --- a/src/utils/imageProcessing.ts +++ b/src/utils/imageProcessing.ts @@ -12,10 +12,15 @@ export interface FilterOptions { blur?: number sharpen?: number filter?: string + temperature?: number } // RGB 转 HSL -const rgbToHsl = (r: number, g: number, b: number): [number, number, number] => { +const rgbToHsl = ( + r: number, + g: number, + b: number, +): [number, number, number] => { r /= 255 g /= 255 b /= 255 @@ -37,7 +42,11 @@ const rgbToHsl = (r: number, g: number, b: number): [number, number, number] => } // HSL 转 RGB -const hslToRgb = (h: number, s: number, l: number): [number, number, number] => { +const hslToRgb = ( + h: number, + s: number, + l: number, +): [number, number, number] => { h = h / (2 * Math.PI) const hue2rgb = (p: number, q: number, t: number) => { @@ -70,6 +79,7 @@ const applyPresetFilter = (data: Uint8ClampedArray, filterStr: string) => { const brightnessMatch = filterStr.match(/brightness\(([\d.]+)\)/) const saturateMatch = filterStr.match(/saturate\(([\d.]+)\)/) const grayscaleMatch = filterStr.match(/grayscale\(([\d.]+)\)/) + const temperatureMatch = filterStr.match(/temperature\(([\d.-]+)\)/) const sepiaAmount = sepiaMatch ? parseFloat(sepiaMatch[1]) : 0 const hueRotate = hueMatch ? (parseFloat(hueMatch[1]) * Math.PI) / 180 : 0 @@ -77,6 +87,9 @@ const applyPresetFilter = (data: Uint8ClampedArray, filterStr: string) => { const brightnessFactor = brightnessMatch ? parseFloat(brightnessMatch[1]) : 1 const saturateFactor = saturateMatch ? parseFloat(saturateMatch[1]) : 1 const grayscaleAmount = grayscaleMatch ? parseFloat(grayscaleMatch[1]) : 0 + const temperatureAmount = temperatureMatch + ? parseFloat(temperatureMatch[1]) + : 0 for (let i = 0; i < data.length; i += 4) { let r = data[i] @@ -119,7 +132,14 @@ const applyPresetFilter = (data: Uint8ClampedArray, filterStr: string) => { b = gray + (b - gray) * saturateFactor } - // 6. Hue-rotate + // 6. Temperature + if (temperatureAmount !== 0) { + r += temperatureAmount + b -= temperatureAmount + g += temperatureAmount < 0 ? Math.abs(temperatureAmount) * 0.15 : 0 + } + + // 7. Hue-rotate if (hueRotate !== 0) { const [h, s, l] = rgbToHsl(r, g, b) const newH = (h + hueRotate) % (2 * Math.PI) @@ -216,7 +236,8 @@ const applySharpen = (imageData: ImageData, amount: number) => { for (let c = 0; c < 3; c++) { const val = buff[i + c] - const neighbors = buff[up + c] + buff[down + c] + buff[left + c] + buff[right + c] + const neighbors = + buff[up + c] + buff[down + c] + buff[left + c] + buff[right + c] const res = val + k * (4 * val - neighbors) data[i + c] = res } @@ -227,9 +248,9 @@ const applySharpen = (imageData: ImageData, amount: number) => { // 主处理函数 export const processImage = async ( imageData: ImageData, - opts: FilterOptions + opts: FilterOptions, ): Promise => { - return new Promise(resolve => { + return new Promise((resolve) => { // 使用 setTimeout 将计算推迟到下一个宏任务,避免阻塞 UI 渲染 setTimeout(() => { const data = imageData.data @@ -241,7 +262,8 @@ export const processImage = async ( hue = 0, blur = 0, sharpen = 0, - filter = 'none' + filter = 'none', + temperature = 0, } = opts // 1. 预设滤镜 @@ -253,10 +275,17 @@ export const processImage = async ( const brightnessFactor = (100 + brightness + exposure) / 100 const contrastFactor = (100 + contrast) / 100 const saturationFactor = (100 + saturation) / 100 - const hueRotation = (hue * 1.8 * Math.PI) / 180 + const tintAmount = hue * 0.35 // 如果有基础参数调整,遍历像素 - if (brightness !== 0 || contrast !== 0 || saturation !== 0 || exposure !== 0 || hue !== 0) { + if ( + brightness !== 0 || + contrast !== 0 || + saturation !== 0 || + exposure !== 0 || + hue !== 0 || + temperature !== 0 + ) { const tempBuffer = new Uint8ClampedArray(data) for (let i = 0; i < data.length; i += 4) { let r = tempBuffer[i] @@ -279,14 +308,16 @@ export const processImage = async ( g = gray + (g - gray) * saturationFactor b = gray + (b - gray) * saturationFactor - // Hue + if (temperature !== 0) { + r += temperature + b -= temperature + g += temperature < 0 ? Math.abs(temperature) * 0.15 : 0 + } + if (hue !== 0) { - const [h, s, l] = rgbToHsl(r, g, b) - const newH = (h + hueRotation) % (2 * Math.PI) - const [newR, newG, newB] = hslToRgb(newH, s, l) - r = newR - g = newG - b = newB + r += tintAmount > 0 ? tintAmount : 0 + b += tintAmount > 0 ? tintAmount : 0 + g += tintAmount < 0 ? Math.abs(tintAmount) : 0 } data[i] = Math.max(0, Math.min(255, r)) diff --git a/src/utils/index.ts b/src/utils/index.ts index 1a96dea..03b28ea 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,10 @@ // 图片资源映射 - 使用静态导入确保构建时包含所有图片 -const imageModules = import.meta.glob('../assets/images/**/*.{png,jpg,jpeg,gif,svg,webp}', { - eager: true -}) +const imageModules = import.meta.glob( + '../assets/images/**/*.{png,jpg,jpeg,gif,svg,webp}', + { + eager: true, + }, +) //访问静态图片 - 使用静态导入确保构建后路径正确 export const getStaticImage = (path: string) => { @@ -61,7 +64,7 @@ export const IMAGE_PATHS = { SELECTED_BTN3: 'home/action/selected-btn3', VIDEO1: 'home/action/video1', VIDEO2: 'home/action/video2', - VIDEO3: 'home/action/video3' + VIDEO3: 'home/action/video3', }, // vlog VLOG: { @@ -102,7 +105,7 @@ export const IMAGE_PATHS = { SAMPLEAN: 'vlog/制作变焦镜/左边导航栏/样例展示-暗', AI: 'vlog/制作变焦镜/左边导航栏/ai分镜工具', BIAOGEBG: 'vlog/制作变焦镜/表格背景', - JIAOBENQIETU: 'vlog/脚本切图/我的志愿者手记拍摄脚本' + JIAOBENQIETU: 'vlog/脚本切图/我的志愿者手记拍摄脚本', }, //Work WORK: { @@ -114,7 +117,7 @@ export const IMAGE_PATHS = { TASK_CAMERA: 'work/task-camera', TASK_BTN_OK: 'work/task-btn-ok', TASK_BTN: 'work/task-btn', - TASK_PERSON: 'work/person' + TASK_PERSON: 'work/person', }, //按钮 @@ -134,14 +137,14 @@ export const IMAGE_PATHS = { //操作指引 OPERATE_GUIDE: 'home/guide/cz-btn', //开始拍摄 - START_FILMING: 'home/guide/action-btn' + START_FILMING: 'home/guide/action-btn', }, //components COMPONENTS: { CLOSE: 'components/close', //返回 - BACK: 'components/back' + BACK: 'components/back', }, // ai工具组镜头按钮 AI_TOOLS_BUTTONS: { @@ -149,7 +152,7 @@ export const IMAGE_PATHS = { NEXT: 'home/ai工具组镜头/下一步', START: 'home/ai工具组镜头/开始', COMPLETE: 'home/ai工具组镜头/完成', - CANCEL: 'home/ai工具组镜头/取消' + CANCEL: 'home/ai工具组镜头/取消', }, // 新手导航相关 NEWBIE_GUIDE: { @@ -169,7 +172,7 @@ export const IMAGE_PATHS = { NODE_LOCKED: 'home/新手导航/节点-未解锁', CAMERA_MOVEMENT: 'home/新手导航/运镜', PROGRESS_LIGHT: 'home/新手导航/进度条-亮', - PROGRESS_DARK: 'home/新手导航/进度条-暗' + PROGRESS_DARK: 'home/新手导航/进度条-暗', }, // 模块区域相关 MODULE_AREA: { @@ -180,7 +183,7 @@ export const IMAGE_PATHS = { SELECTED_VLOG: 'home/模块区域/选中区域-vlog', SELECTED_THIRD: 'home/模块区域/选中区域-第三模块', SELECTED_SECOND: 'home/模块区域/选中区域-第二模块', - POPUP_VLOG: 'home/模块区域/选中弹出-vlog' + POPUP_VLOG: 'home/模块区域/选中弹出-vlog', }, // 节点数字 NODE_NUMBERS: Array.from({ length: 10 }, (_, i) => `home/节点数字/${i}`), @@ -189,29 +192,41 @@ export const IMAGE_PATHS = { PROGRESS_BAR: { TITLE: 'components/制作变焦镜-我的志愿者手记', UNLOCKED: 'components/制作变焦镜-未解锁', - COMPLETE_BUTTON: 'components/进入-实战操作', + COMPLETE_BUTTON: 'components/进入实战操作台-未学习', + COMPLETE_BUTTON_COMPLETED: 'components/进入实战操作台-已学习', COMPLETE_BUTTON_DISABLED: 'components/完成前置-即可开启实战', - BACKGROUND: 'components/制作变焦镜-底板' + BACKGROUND: 'components/制作变焦镜-底板', }, // 设备准备 EQUIPMENT: { BG: 'equipment/设备准备-背景', SELECT_THE_BUTTON: 'equipment/按钮选中-设备准备', - NAVIGATION_SIDEBAR: 'equipment/侧边导航栏' + NAVIGATION_SIDEBAR: 'equipment/侧边导航栏', }, SHOOTING: { - BG: 'shooting/实地取景入口背景' + BG: 'shooting/实地取景入口背景', }, FILMINGLOCATIONA: { - BG: 'shooting/取景地模糊背景' + BG: 'shooting/取景地模糊背景', }, // 后期剪辑 AFTEREDITING: { MIDTERM: 'afterditing/后期剪辑底板', EDITING: 'afterditing/按钮-剪辑', - ADJUSTMENTCOLOR: 'afterditing/按钮-调色' - } + ADJUSTMENTCOLOR: 'afterditing/按钮-调色', + }, + + // Loading 加载组件 + LOADING: { + BG: 'components/loading/背景图层', + LOGO: 'components/loading/logo', + DRAGON: 'components/loading/小龙人', + TEXT: 'components/loading/精彩内容即将呈现文字', + DOTS: 'components/loading/省略号', + PROGRESS_TRACK: 'components/loading/进度条底板', + PROGRESS_FILL: 'components/loading/进度条亮', + }, } as const // 获取 public 文件夹中的资源路径(用于 Word 文档等) @@ -222,6 +237,24 @@ export const getPublicPath = (path: string) => { return `${import.meta.env.BASE_URL}${cleanPath}` } +export const getVideoPoster = (videoPath?: string) => { + if (!videoPath) return '' + + const cleanPath = videoPath.split(/[?#]/)[0] + const fileName = cleanPath.split('/').filter(Boolean).pop() + if (!fileName) return '' + + const coverName = decodeURIComponent(fileName).replace(/\.[^.]+$/, '.jpg') + const imagePath = `../assets/images/cover/${coverName}` + const module = imageModules[imagePath] + + if (module && typeof module === 'object' && 'default' in module) { + return module.default as string + } + + return '' +} + // 获取标签颜色 export const getTagColor = (tagName: string) => { const colorMap: Record = { @@ -232,7 +265,7 @@ export const getTagColor = (tagName: string) => { 摄影基础: 'bg-[#2cb067] border-white text-white', 剪映基础操作: 'bg-[#945bc6] border-white text-white', AI与版权伦理知识: 'bg-[#6f8cf9] border-white text-white', - 思路提点与案例赏析: 'bg-[#32a4fb] border-white text-white' + 思路提点与案例赏析: 'bg-[#32a4fb] border-white text-white', } return colorMap[tagName] || 'bg-blue-400 border-white/60 text-white' } diff --git a/src/utils/yiban.ts b/src/utils/yiban.ts index ed9db5a..fcc14c5 100644 --- a/src/utils/yiban.ts +++ b/src/utils/yiban.ts @@ -1,5 +1,4 @@ import { useUserStore } from '@/stores' - // 声明外部依赖(由运行环境提供) declare const $: any declare const iscallyb: boolean @@ -9,11 +8,6 @@ declare function getFutureDa(days: number): string declare function getCurrentDa(str: string): void declare function formatDateFromMilliseconds(date: Date): string -// 本地或非易班环境下,外部全局变量可能未注入;统一用安全访问 -const isYibanRuntime = (): boolean => { - return typeof iscallyb !== 'undefined' && iscallyb === true -} - interface UserDefineParam { lessonId: string key?: string | number @@ -37,11 +31,11 @@ interface VideoAjax { addUserDefine(param: UserDefineParam): void getUserDefineInfoByUser( param: UserDefineParam, - callback: (data: VideoAjaxResponse | undefined) => void + callback: (data: VideoAjaxResponse | undefined) => void, ): void getUserDefineInfo( param: UserDefineParam, - callback: (data: VideoAjaxResponse | undefined) => void + callback: (data: VideoAjaxResponse | undefined) => void, ): void finishCourse(param: { order: number; co: string; process: number }): void } @@ -72,22 +66,17 @@ $.extend({ }, getUrlVar(name: string): string | undefined { return ($ as any).getUrlVars()[name] - } + }, }) const LESSON_PREFIX = 'dsp' -const videoajax: VideoAjax | null = typeof videoAjax !== 'undefined' ? videoAjax() : null -videoajax?.setHost('/') - -function normalizeLessonId(lessonId: string | number): string { - const raw = String(lessonId) - return raw.startsWith(`${LESSON_PREFIX}_`) ? raw : `${LESSON_PREFIX}_${raw}` -} +const videoajax: VideoAjax = videoAjax() +videoajax.setHost('/') // 设置我的自定义数据(所有的自定义数据都设置在第一个课时ID上,以key来区分不同的类型) function setLtDefine(key: string | number, value: string): void { - if (isYibanRuntime()) { + if (iscallyb) { setUserDefine(1, key, value) } else { sessionStorage.setItem(`${LESSON_PREFIX}_${key}`, value) @@ -98,7 +87,7 @@ let mycstr9 = '' // 获取定义数据(由于底层是异步接口,这里返回 Promise) async function getLtDefine(key: string | number): Promise { - if (isYibanRuntime()) { + if (iscallyb) { if (mycstr9 === '') { const all = await getUsersDefine(1, emykey) if (all === undefined || all === null || all === 'null' || all === '') { @@ -108,13 +97,20 @@ async function getLtDefine(key: string | number): Promise { mycstr9 = all as string // 这里是 emykey,对应上面 getUsersDefine 的 string 分支 } } - const str = mycstr9 === undefined || mycstr9 === null || mycstr9 === 'null' ? '' : mycstr9 + const str = + mycstr9 === undefined || mycstr9 === null || mycstr9 === 'null' + ? '' + : mycstr9 getCurrentDa(str) const objitem = await getUserDefine(1, key) - return objitem === undefined || objitem === null || objitem === 'null' ? '' : objitem + return objitem === undefined || objitem === null || objitem === 'null' + ? '' + : objitem } else { const itemvalue = sessionStorage.getItem(`${LESSON_PREFIX}_${key}`) - return itemvalue === undefined || itemvalue === null || itemvalue === 'null' ? '' : itemvalue + return itemvalue === undefined || itemvalue === null || itemvalue === 'null' + ? '' + : itemvalue } } @@ -140,17 +136,12 @@ async function getCourseNotepad(): Promise { export function setUserDefine( lessonId: string | number, key: string | number, - value: string + value: string, ): void { - if (!isYibanRuntime() || !videoajax) { - sessionStorage.setItem(`${normalizeLessonId(lessonId)}_${key}`, value) - return - } - const param: UserDefineParam = { - lessonId: normalizeLessonId(lessonId), + lessonId: `${LESSON_PREFIX}_${lessonId}`, key, - content: value + content: value, } videoajax.addUserDefine(param) } @@ -158,32 +149,35 @@ export function setUserDefine( // 内部:将回调形式封装为 Promise function getUserDefineAsyncInternal( lessonId: string | number, - key: string | number + key: string | number, ): Promise { - if (!isYibanRuntime() || !videoajax) { - const value = sessionStorage.getItem(`${normalizeLessonId(lessonId)}_${key}`) - return Promise.resolve(value === null || value === 'null' ? '' : value) - } - const param: UserDefineParam = { - lessonId: normalizeLessonId(lessonId), - order: key + lessonId: `${LESSON_PREFIX}_${lessonId}`, + order: key, } - const ajax = videoajax - return new Promise(resolve => { - let content = '' - ajax.getUserDefineInfoByUser(param, res => { - // 保留原有 console 行为 + return new Promise((resolve) => { + let settled = false + const done = (content: string) => { + if (settled) return + settled = true + resolve(content) + } + + const timeout = setTimeout(() => { + console.warn(`getUserDefine timeout: lessonId=${lessonId}, key=${key}`) + done('') + }, 5000) + + videoajax.getUserDefineInfoByUser(param, (res) => { + clearTimeout(timeout) console.log(res) + let content = '' if (res && res.code === 200) { const json = (res.data.value || []) as VideoAjaxResponseItem[] - const contents = json[0]?.content || '' - content = contents - } else { - content = '' + content = json[0]?.content || '' } - resolve(content) + done(content) }) }) } @@ -191,7 +185,7 @@ function getUserDefineAsyncInternal( // Promise 版本:获取用户自定义数据 export async function getUserDefine( lessonId: string | number, - key: string | number + key: string | number, ): Promise { return getUserDefineAsyncInternal(lessonId, key) } @@ -199,21 +193,29 @@ export async function getUserDefine( // 内部:获取【所有】用户自定义数据(Promise 封装) function getUsersDefineAsyncInternal( lessonId: string | number, - key: string | number + key: string | number, ): Promise { - if (!isYibanRuntime() || !videoajax) { - return Promise.resolve('') - } - const param: UserDefineParam = { - lessonId: normalizeLessonId(lessonId), - order: key + lessonId: `${LESSON_PREFIX}_${lessonId}`, + order: key, } - const ajax = videoajax - return new Promise(resolve => { - let content: string | VideoAjaxResponseItem[] = '' - ajax.getUserDefineInfo(param, data => { + return new Promise((resolve) => { + let settled = false + const done = (content: string | VideoAjaxResponseItem[]) => { + if (settled) return + settled = true + resolve(content) + } + + const timeout = setTimeout(() => { + console.warn(`getUsersDefine timeout: lessonId=${lessonId}, key=${key}`) + done('') + }, 5000) + + videoajax.getUserDefineInfo(param, (data) => { + clearTimeout(timeout) + let content: string | VideoAjaxResponseItem[] = '' if (data && data.code === 200) { const json = (data.data.value || []) as VideoAjaxResponseItem[] if (json.length > 1) { @@ -221,8 +223,8 @@ function getUsersDefineAsyncInternal( const mdate = new Date( Math.max.apply( null, - json.map(item => new Date(item.content || '').getTime()) - ) + json.map((item) => new Date(item.content || '').getTime()), + ), ) content = formatDateFromMilliseconds(mdate) } else { @@ -231,10 +233,8 @@ function getUsersDefineAsyncInternal( } else { content = json[0]?.content || '' } - } else { - content = '' } - resolve(content) + done(content) }) }) } @@ -242,27 +242,35 @@ function getUsersDefineAsyncInternal( // Promise 版本:获取【所有】用户自定义数据 export async function getUsersDefine( lessonId: string | number, - key: string | number + key: string | number, ): Promise { return getUsersDefineAsyncInternal(lessonId, key) } // 根据课时顺序号(所处课程的第几课时)更新对应易班平台上的每个环节进度(异步) -export async function setCourseTimeProgress(order: number): Promise { +export async function setCourseTimeProgress(order: number): Promise { const courseid = useUserStore().courseId - // var courseid = getCookie(lesson+"_course_id");//获取url链接的课程ID - // const courseid = await getLtDefine('10000') console.info(`courseid:${courseid} order:${order}`) - if ( - isYibanRuntime() && - videoajax && - courseid !== undefined && - courseid !== null && - courseid !== '' - ) { - if (order > 0) { - const param2 = { order, co: courseid, process: 100 } - videoajax.finishCourse(param2) - } + + if (!iscallyb) { + console.info( + `[yiban] skip finishCourse: not in yiban runtime, order=${order}`, + ) + return false } + + if (courseid === undefined || courseid === null || courseid === '') { + console.info(`[yiban] skip finishCourse: missing courseId, order=${order}`) + return false + } + + if (order <= 0) { + console.info(`[yiban] skip finishCourse: invalid order=${order}`) + return false + } + + const param2 = { order, co: courseid, process: 100 } + console.info('[yiban] finishCourse', param2) + videoajax.finishCourse(param2) + return true } diff --git a/src/utils/yibanLessonProgress.ts b/src/utils/yibanLessonProgress.ts new file mode 100644 index 0000000..8d0afbb --- /dev/null +++ b/src/utils/yibanLessonProgress.ts @@ -0,0 +1,218 @@ +import { + basicElementsEditingVideoKeys, + basicElementsFoundationVideoKeys, + basicElementsShootingVideoKeys, +} from '@/config/moduleStepMap' +import { + useBasicElementsStore, + useFilmStore, + useInterviewStore, + useVlogStore, +} from '@/stores' +import { setCourseTimeProgress } from '@/utils/yiban' + +const YIBAN_COURSE_ORDERS = { + basicFoundation: 1, + basicShooting: 2, + basicEditing: 3, + vlogTask: 4, + vlogStage: 5, + vlogPractice: 6, + interviewTask: 7, + interviewStage: 8, + interviewPractice: 9, + filmTask: 10, + filmStage: 11, + filmPractice: 12, +} as const + +const isAllCompleted = ( + record: Record, + keys: readonly string[], +): boolean => keys.every((key) => record[key] === true) + +const getUploadedCourseOrders = (): number[] => { + const basicElementsStore = useBasicElementsStore() + return [...basicElementsStore.uploadedCourseOrders] +} + +const hasUploadedCourseOrder = (order: number): boolean => + getUploadedCourseOrders().includes(order) + +export async function markYibanLessonCompleted(order: number): Promise { + if (hasUploadedCourseOrder(order)) return + + const uploaded = await setCourseTimeProgress(order) + if (!uploaded) return + + useBasicElementsStore().markCourseOrderUploaded(order) +} + +export async function syncAllCompletedYibanLessons(): Promise { + const basicElementsStore = useBasicElementsStore() + const vlogStore = useVlogStore() + const interviewStore = useInterviewStore() + const filmStore = useFilmStore() + + if ( + isAllCompleted( + basicElementsStore.learningVideosWatched, + basicElementsFoundationVideoKeys, + ) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.basicFoundation) + } + + if ( + isAllCompleted( + basicElementsStore.learningVideosWatched, + basicElementsShootingVideoKeys, + ) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.basicShooting) + } + + if ( + isAllCompleted( + basicElementsStore.learningVideosWatched, + basicElementsEditingVideoKeys, + ) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.basicEditing) + } + + if ( + isAllCompleted(vlogStore.workVideosWatched, ['case', 'profession', 'idea']) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.vlogTask) + } + + if (vlogStore.completedStep === 3) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.vlogStage) + } + + if (vlogStore.completedStep >= 4) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.vlogPractice) + } + + if ( + isAllCompleted(interviewStore.workVideosWatched, [ + 'case', + 'profession', + 'idea', + ]) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.interviewTask) + } + + if (interviewStore.completedStep === 3) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.interviewStage) + } + + if (interviewStore.completedStep >= 4) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.interviewPractice) + } + + if ( + isAllCompleted(filmStore.workVideosWatched, ['case', 'profession', 'idea']) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.filmTask) + } + + if (filmStore.completedStep === 3) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.filmStage) + } + + if (filmStore.completedStep >= 4) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.filmPractice) + } +} + +export async function syncBasicElementsCourseProgress(): Promise { + const basicElementsStore = useBasicElementsStore() + + if ( + isAllCompleted( + basicElementsStore.learningVideosWatched, + basicElementsFoundationVideoKeys, + ) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.basicFoundation) + } + + if ( + isAllCompleted( + basicElementsStore.learningVideosWatched, + basicElementsShootingVideoKeys, + ) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.basicShooting) + } + + if ( + isAllCompleted( + basicElementsStore.learningVideosWatched, + basicElementsEditingVideoKeys, + ) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.basicEditing) + } +} + +export async function syncVlogCourseProgress(): Promise { + const vlogStore = useVlogStore() + + if ( + isAllCompleted(vlogStore.workVideosWatched, ['case', 'profession', 'idea']) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.vlogTask) + } + + if (vlogStore.completedStep === 3) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.vlogStage) + } + + if (vlogStore.completedStep >= 4) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.vlogPractice) + } +} + +export async function syncInterviewCourseProgress(): Promise { + const interviewStore = useInterviewStore() + + if ( + isAllCompleted(interviewStore.workVideosWatched, [ + 'case', + 'profession', + 'idea', + ]) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.interviewTask) + } + + if (interviewStore.completedStep === 3) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.interviewStage) + } + + if (interviewStore.completedStep >= 4) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.interviewPractice) + } +} + +export async function syncFilmCourseProgress(): Promise { + const filmStore = useFilmStore() + + if ( + isAllCompleted(filmStore.workVideosWatched, ['case', 'profession', 'idea']) + ) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.filmTask) + } + + if (filmStore.completedStep === 3) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.filmStage) + } + + if (filmStore.completedStep >= 4) { + await markYibanLessonCompleted(YIBAN_COURSE_ORDERS.filmPractice) + } +} diff --git a/src/views/basic-elements/basic-shooting-skills/index.vue b/src/views/basic-elements/basic-shooting-skills/index.vue index e0a3e1a..a796132 100644 --- a/src/views/basic-elements/basic-shooting-skills/index.vue +++ b/src/views/basic-elements/basic-shooting-skills/index.vue @@ -9,7 +9,12 @@ import { Video } from '@/components' import QuestionQuiz, { type Question, } from '@/components/QuestionQuiz/QuestionQuiz.vue' -import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' +import { + getPublicPath, + getStaticImage, + IMAGE_PATHS, + getVideoPoster, +} from '@/utils' import { useBasicElementsStore } from '@/stores/modules/basic-elements' const router = useRouter() @@ -62,7 +67,9 @@ const rawSteps = [ id: 'step6', label: '摄影基础', content: '神奇的微距摄影', - videoUrl: getPublicPath('video/new/shooting-skills/xsdh_sqdwjsy_batch.mp4'), + videoUrl: getPublicPath( + 'video/new/shooting-skills/xinshou_shenqideweijusheying.mp4', + ), type: 'post' as const, }, { @@ -222,10 +229,7 @@ const handleVideoChange = () => { status.value = true return } - nextTick(() => { - // 使用受控播放来触发下一个视频自动播放 - playing.value = true - }) + playing.value = true } const handleClose = () => { @@ -257,9 +261,7 @@ const handleStepClick = (index: number) => { showQuiz.value = false currentVideoIndex.value = index currentNavigationStep.value = index - nextTick(() => { - playing.value = true - }) + playing.value = true } const handleStepNavigationClose = () => { @@ -330,6 +332,7 @@ onBeforeRouteLeave((to) => {
diff --git a/src/views/basic-elements/composition/index.vue b/src/views/basic-elements/composition/index.vue index 8fb6a2d..5f6ffb3 100644 --- a/src/views/basic-elements/composition/index.vue +++ b/src/views/basic-elements/composition/index.vue @@ -5,7 +5,12 @@ import ImgButton from '@/components/Button/ImgButton.vue' import StepNavigation from '@/components/Guide/StepNavigation.vue' import UnfinishedLearningPrompt from '@/components/UnfinishedLearningPrompt' import { Video } from '@/components' -import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' +import { + getPublicPath, + getStaticImage, + IMAGE_PATHS, + getVideoPoster, +} from '@/utils' import { GuideSteps, GuideStepsItem } from '@/components/Guide' import { useBasicElementsStore } from '@/stores/modules/basic-elements' const router = useRouter() @@ -87,10 +92,7 @@ const handleVideoChange = () => { status.value = true return } - nextTick(() => { - // 使用受控播放来触发下一个视频自动播放 - playing.value = true - }) + playing.value = true } const handleClose = () => { @@ -110,11 +112,7 @@ const onEnded = () => { const handleStepClick = (index: number) => { currentVideoIndex.value = index currentNavigationStep.value = index - // 先重置播放状态,确保 v-model 触发 watcher - playing.value = false - nextTick(() => { - playing.value = true - }) + playing.value = true } const handleStepNavigationClose = () => { @@ -179,6 +177,7 @@ onMounted(() => {})