diff --git a/.gitignore b/.gitignore index 4340584..2bda502 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ dist-ssr # 忽略所有 .mp4 文件 *.mp4 public/video/* +public/audio/* # Editor directories and files .vscode/* diff --git a/.prettierrc b/.prettierrc index 5c25189..ebb7562 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,11 +2,14 @@ "semi": false, "singleQuote": true, "tabWidth": 2, + "useTabs": false, "trailingComma": "none", "printWidth": 100, "endOfLine": "lf", "arrowParens": "avoid", "bracketSpacing": true, "bracketSameLine": false, - "vueIndentScriptAndStyle": false + "htmlWhitespaceSensitivity": "ignore", + "vueIndentScriptAndStyle": false, + "singleAttributePerLine": false } diff --git a/.prettierrc.json b/.prettierrc.json index b61c42f..ebb7562 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,15 +1,15 @@ { - "printWidth": 100, - "tabWidth": 2, - "useTabs": true, "semi": false, "singleQuote": true, + "tabWidth": 2, + "useTabs": false, "trailingComma": "none", + "printWidth": 100, + "endOfLine": "lf", + "arrowParens": "avoid", "bracketSpacing": true, - "bracketSameLine": true, - "arrowParens": "always", + "bracketSameLine": false, "htmlWhitespaceSensitivity": "ignore", "vueIndentScriptAndStyle": false, - "endOfLine": "auto", "singleAttributePerLine": false } diff --git a/eslint.config.ts b/eslint.config.ts index fb2fb92..082a3ee 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -2,11 +2,37 @@ import globals from 'globals' import tseslint from 'typescript-eslint' import pluginVue from 'eslint-plugin-vue' import { defineConfig } from 'eslint/config' +import { fileURLToPath } from 'node:url' + +// 直接使用 tsconfig 的绝对路径,避免中文路径在 URL/路径互转时被编码导致读取失败 +const tsconfigEslintPath = fileURLToPath(new URL('./tsconfig.eslint.json', import.meta.url)) export default defineConfig([ - { files: ['**/*.{js,mjs,cjs,ts,mts,cts,vue}'], languageOptions: { globals: globals.browser } }, + // 全局忽略:自身配置文件、产物、第三方静态资源等不参与 lint + { + ignores: [ + 'eslint.config.ts', + 'eslint.config.*', + 'dist/**', + 'node_modules/**', + 'public/**', + 'vite.config.*', + 'postcss.config.*', + 'tailwind.config.*' + ] + }, + { + files: ['**/*.{js,mjs,cjs,ts,mts,cts,tsx,vue}'], + languageOptions: { globals: globals.browser } + }, tseslint.configs.recommended, { + languageOptions: { + parserOptions: { + // 让 typescript-eslint 使用专用 tsconfig(显式 include .vue) + project: [tsconfigEslintPath] + } + }, rules: { '@typescript-eslint/no-explicit-any': 'off' } @@ -14,7 +40,19 @@ export default defineConfig([ pluginVue.configs['flat/essential'], { files: ['**/*.vue'], - languageOptions: { parserOptions: { parser: tseslint.parser } }, + languageOptions: { + parserOptions: { + parser: { + ts: tseslint.parser, + tsx: tseslint.parser + }, + project: [tsconfigEslintPath], + extraFileExtensions: ['.vue'], + ecmaFeatures: { + jsx: true + } + } + }, rules: { 'vue/multi-word-component-names': 'off', 'vue/no-undef-components': [ diff --git a/index.html b/index.html index 1f36676..067721c 100644 --- a/index.html +++ b/index.html @@ -8,10 +8,12 @@ content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" /> + 短视频达人
+ diff --git a/package.json b/package.json index 9e3ba91..60311dd 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,10 @@ "@commitlint/config-conventional": "^19.8.1", "@types/node": "^24.3.3", "@vitejs/plugin-vue": "^6.0.1", + "@vitejs/plugin-vue-jsx": "^5.1.5", "@vue/tsconfig": "^0.7.0", - "autoprefixer": "^10.4.21", - "baseline-browser-mapping": "^2.8.28", + "autoprefixer": "^10.5.0", + "baseline-browser-mapping": "^2.10.23", "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92acea9..43f6d93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,15 +48,18 @@ importers: '@vitejs/plugin-vue': specifier: ^6.0.1 version: 6.0.1(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))(vue@3.5.21(typescript@5.8.3)) + '@vitejs/plugin-vue-jsx': + specifier: ^5.1.5 + version: 5.1.5(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))(vue@3.5.21(typescript@5.8.3)) '@vue/tsconfig': specifier: ^0.7.0 version: 0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3)) autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) + specifier: ^10.5.0 + version: 10.5.0(postcss@8.5.6) baseline-browser-mapping: - specifier: ^2.8.28 - version: 2.8.28 + specifier: ^2.10.23 + version: 2.10.23 eslint: specifier: ^9.35.0 version: 9.35.0(jiti@2.5.1) @@ -113,6 +116,72 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -121,15 +190,62 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.4': resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@commitlint/cli@19.8.1': resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==} engines: {node: '>=v18'} @@ -548,6 +664,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -587,6 +706,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.29': resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rollup/rollup-android-arm-eabi@4.50.1': resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} cpu: [arm] @@ -789,6 +911,13 @@ packages: resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-vue-jsx@5.1.5': + resolution: {integrity: sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.0.0 + '@vitejs/plugin-vue@6.0.1': resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -805,18 +934,46 @@ packages: '@volar/typescript@2.4.23': resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + '@vue/babel-helper-vue-transform-on@2.0.1': + resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==} + + '@vue/babel-plugin-jsx@2.0.1': + resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@2.0.1': + resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@vue/compiler-core@3.5.21': resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==} + '@vue/compiler-core@3.5.33': + resolution: {integrity: sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==} + '@vue/compiler-dom@3.5.21': resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==} + '@vue/compiler-dom@3.5.33': + resolution: {integrity: sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==} + '@vue/compiler-sfc@3.5.21': resolution: {integrity: sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==} + '@vue/compiler-sfc@3.5.33': + resolution: {integrity: sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==} + '@vue/compiler-ssr@3.5.21': resolution: {integrity: sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==} + '@vue/compiler-ssr@3.5.33': + resolution: {integrity: sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==} + '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} @@ -857,6 +1014,9 @@ packages: '@vue/shared@3.5.21': resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==} + '@vue/shared@3.5.33': + resolution: {integrity: sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==} + '@vue/tsconfig@0.7.0': resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==} peerDependencies: @@ -943,8 +1103,8 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -960,8 +1120,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.28: - resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==} + baseline-browser-mapping@2.10.23: + resolution: {integrity: sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==} + engines: {node: '>=6.0.0'} hasBin: true binary-extensions@2.3.0: @@ -984,8 +1145,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.0: - resolution: {integrity: sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1000,8 +1161,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001741: - resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} + caniuse-lite@1.0.30001791: + resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1071,6 +1232,9 @@ packages: engines: {node: '>=16'} hasBin: true + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} @@ -1129,6 +1293,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -1146,8 +1319,8 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} didyoumean@1.2.2: @@ -1167,8 +1340,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.218: - resolution: {integrity: sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==} + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} emoji-regex@10.5.0: resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} @@ -1187,6 +1360,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -1284,6 +1461,10 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -1363,8 +1544,8 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1377,6 +1558,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-browser-rtc@1.1.0: resolution: {integrity: sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==} @@ -1573,6 +1758,11 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1588,6 +1778,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} @@ -1759,9 +1954,15 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -1906,17 +2107,13 @@ packages: engines: {node: '>= 4.4.x'} hasBin: true - node-releases@2.0.21: - resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -2096,6 +2293,10 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -2215,11 +2416,20 @@ packages: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -2405,8 +2615,8 @@ packages: unist-util-stringify-position@3.0.3: resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2570,6 +2780,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.8.1: resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} engines: {node: '>= 14.6'} @@ -2605,19 +2818,184 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.4 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.4 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.28.4': dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@commitlint/cli@19.8.1(@types/node@24.3.3)(typescript@5.8.3)': dependencies: '@commitlint/format': 19.8.1 @@ -2941,6 +3319,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -2977,6 +3360,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.29': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rollup/rollup-android-arm-eabi@4.50.1': optional: true @@ -3159,6 +3544,18 @@ snapshots: '@typescript-eslint/types': 8.43.0 eslint-visitor-keys: 4.2.1 + '@vitejs/plugin-vue-jsx@5.1.5(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))(vue@3.5.21(typescript@5.8.3))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.17 + '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0) + 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) + vue: 3.5.21(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-vue@6.0.1(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))(vue@3.5.21(typescript@5.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 @@ -3177,6 +3574,35 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 + '@vue/babel-helper-vue-transform-on@2.0.1': {} + + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.0)': + dependencies: + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.28.4 + '@vue/babel-helper-vue-transform-on': 2.0.1 + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0) + '@vue/shared': 3.5.33 + optionalDependencies: + '@babel/core': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.0)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/parser': 7.28.4 + '@vue/compiler-sfc': 3.5.33 + transitivePeerDependencies: + - supports-color + '@vue/compiler-core@3.5.21': dependencies: '@babel/parser': 7.28.4 @@ -3185,11 +3611,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.33': + dependencies: + '@babel/parser': 7.29.2 + '@vue/shared': 3.5.33 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.21': dependencies: '@vue/compiler-core': 3.5.21 '@vue/shared': 3.5.21 + '@vue/compiler-dom@3.5.33': + dependencies: + '@vue/compiler-core': 3.5.33 + '@vue/shared': 3.5.33 + '@vue/compiler-sfc@3.5.21': dependencies: '@babel/parser': 7.28.4 @@ -3202,11 +3641,28 @@ snapshots: postcss: 8.5.6 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.5.33': + dependencies: + '@babel/parser': 7.29.2 + '@vue/compiler-core': 3.5.33 + '@vue/compiler-dom': 3.5.33 + '@vue/compiler-ssr': 3.5.33 + '@vue/shared': 3.5.33 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.12 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.5.21': dependencies: '@vue/compiler-dom': 3.5.21 '@vue/shared': 3.5.21 + '@vue/compiler-ssr@3.5.33': + dependencies: + '@vue/compiler-dom': 3.5.33 + '@vue/shared': 3.5.33 + '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 @@ -3269,6 +3725,8 @@ snapshots: '@vue/shared@3.5.21': {} + '@vue/shared@3.5.33': {} + '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.21(typescript@5.8.3))': optionalDependencies: typescript: 5.8.3 @@ -3338,12 +3796,11 @@ snapshots: array-ify@1.0.0: {} - autoprefixer@10.4.21(postcss@8.5.6): + autoprefixer@10.5.0(postcss@8.5.6): dependencies: - browserslist: 4.26.0 - caniuse-lite: 1.0.30001741 - fraction.js: 4.3.7 - normalize-range: 0.1.2 + browserslist: 4.28.2 + caniuse-lite: 1.0.30001791 + fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -3354,7 +3811,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.28: {} + baseline-browser-mapping@2.10.23: {} binary-extensions@2.3.0: {} @@ -3375,13 +3832,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.26.0: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.8.28 - caniuse-lite: 1.0.30001741 - electron-to-chromium: 1.5.218 - node-releases: 2.0.21 - update-browserslist-db: 1.1.3(browserslist@4.26.0) + baseline-browser-mapping: 2.10.23 + caniuse-lite: 1.0.30001791 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer@6.0.3: dependencies: @@ -3392,7 +3849,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001741: {} + caniuse-lite@1.0.30001791: {} chalk@4.1.2: dependencies: @@ -3466,6 +3923,8 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + convert-source-map@2.0.0: {} + copy-anything@2.0.6: dependencies: is-what: 3.14.1 @@ -3514,6 +3973,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -3526,7 +3989,7 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.0.4: + detect-libc@2.1.2: optional: true didyoumean@1.2.2: {} @@ -3541,7 +4004,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.218: {} + electron-to-chromium@1.5.344: {} emoji-regex@10.5.0: {} @@ -3558,6 +4021,8 @@ snapshots: entities@4.5.0: {} + entities@7.0.1: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -3717,6 +4182,10 @@ snapshots: dependencies: estraverse: 5.3.0 + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -3788,7 +4257,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fraction.js@4.3.7: {} + fraction.js@5.3.4: {} fs.realpath@1.0.0: {} @@ -3797,6 +4266,8 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-browser-rtc@1.1.0: {} get-caller-file@2.0.5: {} @@ -3956,6 +4427,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} @@ -3966,6 +4439,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + jsonparse@1.3.1: {} jszip@3.10.1: @@ -4044,7 +4519,7 @@ snapshots: lightningcss@1.30.1: dependencies: - detect-libc: 2.0.4 + detect-libc: 2.1.2 optionalDependencies: lightningcss-darwin-arm64: 1.30.1 lightningcss-darwin-x64: 1.30.1 @@ -4128,10 +4603,18 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -4349,12 +4832,10 @@ snapshots: sax: 1.4.1 optional: true - node-releases@2.0.21: {} + node-releases@2.0.38: {} normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -4500,6 +4981,12 @@ snapshots: postcss-value-parser@4.2.0: {} + postcss@8.5.12: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -4635,8 +5122,12 @@ snapshots: semver@5.7.2: optional: true + semver@6.3.1: {} + semver@7.7.2: {} + semver@7.7.4: {} + setimmediate@1.0.5: {} shebang-command@2.0.0: @@ -4866,9 +5357,9 @@ snapshots: dependencies: '@types/unist': 2.0.11 - update-browserslist-db@1.1.3(browserslist@4.26.0): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.26.0 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -4911,13 +5402,13 @@ snapshots: vue-eslint-parser@10.2.0(eslint@9.35.0(jiti@2.5.1)): dependencies: - debug: 4.4.1 + debug: 4.4.3 eslint: 9.35.0(jiti@2.5.1) eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - esquery: 1.6.0 - semver: 7.7.2 + esquery: 1.7.0 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -4999,6 +5490,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yaml@2.8.1: {} yargs-parser@21.1.1: {} diff --git a/public/videoajax/jquery-3.7.1.min.js b/public/videoajax/jquery-3.7.1.min.js new file mode 100644 index 0000000..c1f3c2a --- /dev/null +++ b/public/videoajax/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0>> 6)) + + fromCharCode(0x80 | (cc & 0x3f))) + : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + + fromCharCode(0x80 | ( cc & 0x3f))); + } else { + var cc = 0x10000 + + (c.charCodeAt(0) - 0xD800) * 0x400 + + (c.charCodeAt(1) - 0xDC00); + return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) + + fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) + + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + + fromCharCode(0x80 | ( cc & 0x3f))); + } + }; + var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; + var utob = function(u) { + return u.replace(re_utob, cb_utob); + }; + var cb_encode = function(ccc) { + var padlen = [0, 2, 1][ccc.length % 3], + ord = ccc.charCodeAt(0) << 16 + | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) + | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)), + chars = [ + b64chars.charAt( ord >>> 18), + b64chars.charAt((ord >>> 12) & 63), + padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), + padlen >= 1 ? '=' : b64chars.charAt(ord & 63) + ]; + return chars.join(''); + }; + var btoa = global.btoa ? function(b) { + return global.btoa(b); + } : function(b) { + return b.replace(/[\s\S]{1,3}/g, cb_encode); + }; + var _encode = buffer + ? function (u) { return (new buffer(u)).toString('base64') } + : function (u) { return btoa(utob(u)) } + ; + var encode = function(u, urisafe) { + return !urisafe + ? _encode(u) + : _encode(u).replace(/[+\/]/g, function(m0) { + return m0 == '+' ? '-' : '_'; + }).replace(/=/g, ''); + }; + var encodeURI = function(u) { return encode(u, true) }; + // decoder stuff + var re_btou = new RegExp([ + '[\xC0-\xDF][\x80-\xBF]', + '[\xE0-\xEF][\x80-\xBF]{2}', + '[\xF0-\xF7][\x80-\xBF]{3}' + ].join('|'), 'g'); + var cb_btou = function(cccc) { + switch(cccc.length) { + case 4: + var cp = ((0x07 & cccc.charCodeAt(0)) << 18) + | ((0x3f & cccc.charCodeAt(1)) << 12) + | ((0x3f & cccc.charCodeAt(2)) << 6) + | (0x3f & cccc.charCodeAt(3)), + offset = cp - 0x10000; + return (fromCharCode((offset >>> 10) + 0xD800) + + fromCharCode((offset & 0x3FF) + 0xDC00)); + case 3: + return fromCharCode( + ((0x0f & cccc.charCodeAt(0)) << 12) + | ((0x3f & cccc.charCodeAt(1)) << 6) + | (0x3f & cccc.charCodeAt(2)) + ); + default: + return fromCharCode( + ((0x1f & cccc.charCodeAt(0)) << 6) + | (0x3f & cccc.charCodeAt(1)) + ); + } + }; + var btou = function(b) { + return b.replace(re_btou, cb_btou); + }; + var cb_decode = function(cccc) { + var len = cccc.length, + padlen = len % 4, + n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) + | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) + | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) + | (len > 3 ? b64tab[cccc.charAt(3)] : 0), + chars = [ + fromCharCode( n >>> 16), + fromCharCode((n >>> 8) & 0xff), + fromCharCode( n & 0xff) + ]; + chars.length -= [0, 0, 2, 1][padlen]; + return chars.join(''); + }; + var atob = global.atob ? function(a) { + return global.atob(a); + } : function(a){ + return a.replace(/[\s\S]{1,4}/g, cb_decode); + }; + var _decode = buffer + ? function(a) { return (new buffer(a, 'base64')).toString() } + : function(a) { return btou(atob(a)) }; + var decode = function(a){ + return _decode( + a.replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' }) + .replace(/[^A-Za-z0-9\+\/]/g, '') + ); + }; + var noConflict = function() { + var Base64 = global.Base64; + global.Base64 = _Base64; + return Base64; + }; + // export Base64 + global.Base64 = { + VERSION: version, + atob: atob, + btoa: btoa, + fromBase64: decode, + toBase64: encode, + utob: utob, + encode: encode, + encodeURI: encodeURI, + btou: btou, + decode: decode, + noConflict: noConflict + }; + // if ES5 is available, make Base64.extendString() available + if (typeof Object.defineProperty === 'function') { + var noEnum = function(v){ + return {value:v,enumerable:false,writable:true,configurable:true}; + }; + global.Base64.extendString = function () { + Object.defineProperty( + String.prototype, 'fromBase64', noEnum(function () { + return decode(this) + })); + Object.defineProperty( + String.prototype, 'toBase64', noEnum(function (urisafe) { + return encode(this, urisafe) + })); + Object.defineProperty( + String.prototype, 'toBase64URI', noEnum(function () { + return encode(this, true) + })); + }; + } + // that's it! +})(this); + +if (this['Meteor']) { + Base64 = global.Base64; // for normal export in Meteor.js +} + +; (function (root) { + // body... + + var videoajax = function (argument) { + // body... + var host = '/'; + + this._setHost=function(hoststr) { + host = hoststr; + }; + this._getHost = function(){ + return host; + } + }; + + $.extend(videoajax.prototype,{ + /** + * 设置主域名 + * @param hosts {String} default: '/' 主域名字符串 + */ + setHost :function(hosts){ + this._setHost(hosts); + }, + //user_id,course_id,lesson_id,break_point + /** + * 播放退出时间点 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * user_id {Number} 用户ID + * course_id {Number} 课程ID + * lesson_id {Number} 课时ID + * break_point {Number} 断点 + *@param callback {Function} 回调方法 + */ + quitBreakPoint:function(data,callback){ + var paramfiled = ["user_id","course_id","lesson_id","break_point"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=addBreakPoint',{ + userViewPoint:dataStr + },function(data){ + isFunction(callback) ? callback(data.code == 200,data) : ''; + } + ); + }, + /** + * user_id:123,course_id:23,lesson_id:3 + *播放历史记录 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * user_id {Number} 用户ID + * course_id {Number} 课程ID + * lesson_id {Number} 课时ID + *@param callback {Function} 回调方法 + */ + getBreakPoint:function(data,callback){ + var paramfiled = ["user_id","course_id","lesson_id"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=userbreakpoint',{ + userCourseInfo:dataStr + },function(data){ + isFunction(callback) ? callback(data.code == 200,data) : ''; + // isFunction(callback) ? callback(data) : ''; + } + ); + }, + /** + *用户答题答案 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * user_id {Number} 用户ID + * course_id {Number} 课程ID + * lesson_id {Number} 课时ID + * question_order {Number} 问题序号 + * answer {String} 问题 + *@param callback {Function} 回调方法 + */ + getUserAnswer:function(data,callback){ + var paramfiled = ["user_id","lesson_id","course_id","question_order","answer"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=addUserAnswer',{ + userAnswerInfo:dataStr + },function(data){ + isFunction(callback) ? callback(data.code == 200,data) : ''; + } + ); + + }, + /** + *播放结束 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * order {Number} 排序ID + * co {Number} 课时ID + *@param callback {Function} 回调方法 + */ + finishCourse:function(data,callback){ + var paramfiled = ["order","co","process"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=user&c=learnrecord&a=playcount',data,function(data){ + isFunction(callback) ? callback(data.code == 200,data) : ''; + // isFunction(callback) ? callback(data) : ''; + } + ); + }, + /** + *课程进度获取 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * order {Number} 排序ID + * co {Number} 课时ID + *@param callback {Function} 回调方法 + * 注回调使用的参数,值类似为: {"status":true,"data":{"process":"91"}} + * 或: {"status":false,"data":'参数丢失'} + * 其中status为true为调用正常, 如果有值则data返回json形式的进度, 没有值则返回[] + * status为false时, data保存的是错误提示 + */ + getfinishCourse:function(data,callback){ + var paramfiled = ["order","co"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=user&c=learnrecord&a=getuserlessionprocess',data,function(data){ + isFunction(callback) ? callback(data) : ''; + // isFunction(callback) ? callback(data) : ''; + } + ); + }, + /** + *解码加密的breakpoint + * @pparam basecode {String} 要解码的字符串 + */ + fromBaseCode : function(basecode){ + return getBreakPoint(basecode); + }, + /** + *通过key值和课时值获取自定的数据 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * lessonId {Number} 课时ID + * order {Number} 排序ID + *@param callback {Function} 回调方法 + */ + getUserDefineInfo :function(data,callback){ + var paramfiled = ["lessonId","order"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=getUserDefineInfo',{ + defineDate:dataStr + },function(data){ + isFunction(callback) ? callback(data) : ''; + }); + }, + /** + *通过key值和课时值及用户ID获取自定的数据 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * lessonId {Number} 课时ID + * order {Number} 排序ID + *@param callback {Function} 回调方法 + */ + getUserDefineInfoByUser :function(data,callback){ + var paramfiled = ["lessonId","order"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=getUserDefineInfoByUser',{ + defineUserDate:dataStr + },function(data){ + isFunction(callback) ? callback(data) : ''; + }); + }, + /** + *增加自定数据 + * @param data {dataObject} 向后台传递的数据对象,应该包含 + * lessonId {Number} 课时ID + * key {string} 键值ID + * content {string} 字内容 + *@param callback {Function} 回调方法 + */ + addUserDefine:function(data,callback){ + var paramfiled = ["lessonId","key","content"]; + var dataStr = verfiyData(paramfiled,data); + getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=addUserDefine',{ + addDefineDate:dataStr + },function(data){ + isFunction(callback) ? callback(data) : ''; + }); + } + }) + + var getDate = function(url,data,callback){ + $.get(url,data,function(data){ + callback(data); + },'json') + }; + + var isFunction = function(fn){ + if(typeof fn === 'function')return true; + return false; + } + var verfiyData = function(field,data){ + var arr=[]; + console.log(field); + console.log('
'); + console.log(data); + for(var i in field){ + + if(!data[field[i]]){ + throw new Error('data is error'); + } + arr.push(field[i]+':'+data[field[i]]); + } + if(root.JSON){ + return JSON.stringify(data); + }else{ + return '{'+arr.join(',')+'}'; + } + + } + + var getBreakPoint = function (base64point) { + var newbc = base64point.split('').reverse().join(''); + return Base64.fromBase64(newbc); + } + + root.videoAjax = function(){ + return new videoajax(); + } +}(this)); \ No newline at end of file diff --git a/public/videoajax/videoajax.min.js b/public/videoajax/videoajax.min.js new file mode 100644 index 0000000..59a9620 --- /dev/null +++ b/public/videoajax/videoajax.min.js @@ -0,0 +1 @@ +(function(global){'use strict';var _Base64=global.Base64;var version="2.1.5";var buffer;if(typeof module!=='undefined'&&module.exports){buffer=require('buffer').Buffer}var b64chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i>>6))+fromCharCode(0x80|(cc&0x3f))):(fromCharCode(0xe0|((cc>>>12)&0x0f))+fromCharCode(0x80|((cc>>>6)&0x3f))+fromCharCode(0x80|(cc&0x3f)))}else{var cc=0x10000+(c.charCodeAt(0)-0xD800)*0x400+(c.charCodeAt(1)-0xDC00);return(fromCharCode(0xf0|((cc>>>18)&0x07))+fromCharCode(0x80|((cc>>>12)&0x3f))+fromCharCode(0x80|((cc>>>6)&0x3f))+fromCharCode(0x80|(cc&0x3f)))}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|((ccc.length>1?ccc.charCodeAt(1):0)<<8)|((ccc.length>2?ccc.charCodeAt(2):0)),chars=[b64chars.charAt(ord>>>18),b64chars.charAt((ord>>>12)&63),padlen>=2?'=':b64chars.charAt((ord>>>6)&63),padlen>=1?'=':b64chars.charAt(ord&63)];return chars.join('')};var btoa=global.btoa?function(b){return global.btoa(b)}:function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?function(u){return(new buffer(u)).toString('base64')}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(u):_encode(u).replace(/[+\/]/g,function(m0){return m0=='+'?'-':'_'}).replace(/=/g,'')};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(['[\xC0-\xDF][\x80-\xBF]','[\xE0-\xEF][\x80-\xBF]{2}','[\xF0-\xF7][\x80-\xBF]{3}'].join('|'),'g');var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=((0x07&cccc.charCodeAt(0))<<18)|((0x3f&cccc.charCodeAt(1))<<12)|((0x3f&cccc.charCodeAt(2))<<6)|(0x3f&cccc.charCodeAt(3)),offset=cp-0x10000;return(fromCharCode((offset>>>10)+0xD800)+fromCharCode((offset&0x3FF)+0xDC00));case 3:return fromCharCode(((0x0f&cccc.charCodeAt(0))<<12)|((0x3f&cccc.charCodeAt(1))<<6)|(0x3f&cccc.charCodeAt(2)));default:return fromCharCode(((0x1f&cccc.charCodeAt(0))<<6)|(0x3f&cccc.charCodeAt(1)))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode((n>>>8)&0xff),fromCharCode(n&0xff)];chars.length-=[0,0,2,1][padlen];return chars.join('')};var atob=global.atob?function(a){return global.atob(a)}:function(a){return a.replace(/[\s\S]{1,4}/g,cb_decode)};var _decode=buffer?function(a){return(new buffer(a,'base64')).toString()}:function(a){return btou(atob(a))};var decode=function(a){return _decode(a.replace(/[-_]/g,function(m0){return m0=='-'?'+':'/'}).replace(/[^A-Za-z0-9\+\/]/g,''))};var noConflict=function(){var Base64=global.Base64;global.Base64=_Base64;return Base64};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode,noConflict:noConflict};if(typeof Object.defineProperty==='function'){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,'fromBase64',noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,'toBase64',noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,'toBase64URI',noEnum(function(){return encode(this,true)}))}}})(this);if(this['Meteor']){Base64=global.Base64};(function(root){var videoajax=function(argument){var host='/';this._setHost=function(hoststr){host=hoststr};this._getHost=function(){return host}};$.extend(videoajax.prototype,{setHost:function(hosts){this._setHost(hosts)},quitBreakPoint:function(data,callback){var paramfiled=["user_id","course_id","lesson_id","break_point"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=addBreakPoint',{userViewPoint:dataStr},function(data){isFunction(callback)?callback(data.code==200,data):''})},getBreakPoint:function(data,callback){var paramfiled=["user_id","course_id","lesson_id"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=userbreakpoint',{userCourseInfo:dataStr},function(data){isFunction(callback)?callback(data.code==200,data):''})},getUserAnswer:function(data,callback){var paramfiled=["user_id","lesson_id","course_id","question_order","answer"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=addUserAnswer',{userAnswerInfo:dataStr},function(data){isFunction(callback)?callback(data.code==200,data):''})},finishCourse:function(data,callback){var paramfiled=["order","co","process"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=user&c=learnrecord&a=playcount',data,function(data){isFunction(callback)?callback(data.code==200,data):''})},getfinishCourse:function(data,callback){var paramfiled=["order","co"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=user&c=learnrecord&a=getuserlessionprocess',data,function(data){isFunction(callback)?callback(data):''})},fromBaseCode:function(basecode){return getBreakPoint(basecode)},getUserDefineInfo:function(data,callback){var paramfiled=["lessonId","order"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=getUserDefineInfo',{defineDate:dataStr},function(data){isFunction(callback)?callback(data):''})},getUserDefineInfoByUser:function(data,callback){var paramfiled=["lessonId","order"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=getUserDefineInfoByUser',{defineUserDate:dataStr},function(data){isFunction(callback)?callback(data):''})},addUserDefine:function(data,callback){var paramfiled=["lessonId","key","content"];var dataStr=verfiyData(paramfiled,data);getDate(this._getHost()+'index.php?m=zzplatform&c=userview&a=addUserDefine',{addDefineDate:dataStr},function(data){isFunction(callback)?callback(data):''})}})var getDate=function(url,data,callback){$.get(url,data,function(data){callback(data)},'json')};var isFunction=function(fn){if(typeof fn==='function')return true;return false}var verfiyData=function(field,data){var arr=[];console.log(field);console.log('
');console.log(data);for(var i in field){if(!data[field[i]]){throw new Error('data is error');}arr.push(field[i]+':'+data[field[i]])}if(root.JSON){return JSON.stringify(data)}else{return'{'+arr.join(',')+'}'}}var getBreakPoint=function(base64point){var newbc=base64point.split('').reverse().join('');return Base64.fromBase64(newbc)}root.videoAjax=function(){return new videoajax()}}(this)); \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 691352d..cacd248 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,10 +12,42 @@ diff --git a/src/components/Guide/types.ts b/src/components/Guide/types.ts index e5d4cf9..fd0056b 100644 --- a/src/components/Guide/types.ts +++ b/src/components/Guide/types.ts @@ -1,11 +1,24 @@ import type { Component } from 'vue' +export type AudioMode = 'global' | 'channel' +export type HighlightMode = 'border' | 'gradient' + // 引导步骤类型定义 export interface GuideStep { id: string title?: string content: string | Component nextText?: string + audioSrc?: string + audioMode?: AudioMode + audioChannelId?: string + audioAutoplay?: boolean + highlightSelector?: string | null + highlightPadding?: number + highlightRadius?: number + highlightMode?: HighlightMode + /** 垂直偏移(仅Y轴),支持 number(px) 或 '10%'/'120px' */ + positionOffsetY?: number | string } // 引导组件Props类型 @@ -23,8 +36,13 @@ export interface GuideStepsProps { autoStart?: boolean keyboardNavigation?: boolean autoPlayInterval?: number + highlightSelector?: string | null + highlightPadding?: number + highlightRadius?: number + highlightMode?: HighlightMode /** 垂直偏移(仅Y轴),支持 number(px) 或 '10%'/'120px' */ positionOffsetY?: number | string + visible?: boolean } // 引导组件事件类型 @@ -45,6 +63,10 @@ export interface GuideStepsItemProps { isLastStep?: boolean currentStep?: number totalSteps?: number + audioSrc?: string + audioMode?: AudioMode + audioChannelId?: string + audioAutoplay?: boolean } // GuideStepsItem Emits类型 diff --git a/src/components/ProgressBar/ProgressBar.vue b/src/components/ProgressBar/ProgressBar.vue index fd47d66..c13bdb6 100644 --- a/src/components/ProgressBar/ProgressBar.vue +++ b/src/components/ProgressBar/ProgressBar.vue @@ -51,14 +51,27 @@ :src="currentStep === index ? steps[index].nameSelect : steps[index].nameselectNot" alt="" /> --> -
+
{{ step.name }} + {{ step.name }} + @@ -174,7 +187,15 @@ const handleComplete = () => { } } +const isStepClickable = (index: number) => index <= props.completedStep + 1 + const handleStepClick = (index: number) => { + // 自由切换规则: + // - 已完成的步骤(index <= completedStep)可自由切换 + // - 下一步(index === completedStep + 1)允许进入学习 + // - 其他情况(跳关)忽略 + if (!isStepClickable(index)) return + currentStep.value = index emit('stepChange', index) } diff --git a/src/components/QuestionQuiz/QuestionQuiz.vue b/src/components/QuestionQuiz/QuestionQuiz.vue index d4e8cea..1d3652c 100644 --- a/src/components/QuestionQuiz/QuestionQuiz.vue +++ b/src/components/QuestionQuiz/QuestionQuiz.vue @@ -37,6 +37,7 @@ const currentIndex = ref(0) const selectedOptionId = ref(null) // 单选题选中的选项 const selectedOptionIds = ref([]) // 多选题选中的选项数组 const showErrorExplanation = ref(false) // 是否显示错误解析 +const showCorrectFeedback = ref(false) const total = computed(() => props.questions.length) const currentQuestion = computed(() => props.questions[currentIndex.value]) @@ -63,6 +64,7 @@ watch(currentIndex, () => { selectedOptionId.value = null selectedOptionIds.value = [] showErrorExplanation.value = false + showCorrectFeedback.value = false }) // 通过文件后缀判断媒体类型 @@ -79,6 +81,8 @@ const isImage = (src: string): boolean => { } const handleOptionClick = (optionId: string) => { + if (showCorrectFeedback.value) return + if (isMultiple.value) { // 多选题:切换选中状态 const index = selectedOptionIds.value.indexOf(optionId) @@ -93,13 +97,16 @@ const handleOptionClick = (optionId: string) => { } // 选择新选项时隐藏错误解析 showErrorExplanation.value = false + showCorrectFeedback.value = false } // 交互规则:选对进入下一步,选错显示解析;最后一题选对触发 complete 事件 // 如果 correctOptionId 为空字符串,表示引导题,任意选择都可进入下一题 -const handleSubmit = () => { +const handleSubmit = async () => { const q = currentQuestion.value + if (showCorrectFeedback.value) return + if (isMultiple.value) { // 多选题:必须选择至少一个选项 if (selectedOptionIds.value.length === 0) return @@ -138,12 +145,15 @@ const handleSubmit = () => { // 答对时重置错误解析状态 showErrorExplanation.value = false + showCorrectFeedback.value = true + await new Promise(resolve => setTimeout(resolve, 800)) if (currentIndex.value < total.value - 1) { currentIndex.value += 1 selectedOptionId.value = null selectedOptionIds.value = [] } else { + showCorrectFeedback.value = false emit('complete') } } @@ -158,9 +168,9 @@ const handleSubmit = () => {
第 - {{ - currentQuestion.id - }} + + {{ currentQuestion.id }} + 页 / 共 {{ total }} 页
@@ -222,6 +232,16 @@ const handleSubmit = () => {
+ + +
+ 回答正确 +
+
+
{
@@ -594,6 +677,7 @@ const videoStyle = computed(() => ({ transition: opacity 0.2s var(--sv-ease); will-change: opacity; } + .sv-fade-fast-enter-from, .sv-fade-fast-leave-to { opacity: 0; @@ -607,6 +691,7 @@ const videoStyle = computed(() => ({ transform 0.25s var(--sv-ease); will-change: transform, opacity; } + .sv-slide-up-enter-from, .sv-slide-up-leave-to { opacity: 0; diff --git a/src/components/Video/index.ts b/src/components/Video/index.ts index 218cfb5..a11bb6c 100644 --- a/src/components/Video/index.ts +++ b/src/components/Video/index.ts @@ -1,2 +1,95 @@ -export { default as Video } from './Video.vue' -export type {} from './Video.vue' +import type { ComponentOptionsMixin, DefineComponent, PublicProps, SlotsType } from 'vue' +import VideoSfc from './Video.vue' + +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[] +} + +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 VideoSlots = SlotsType<{ + default?: () => any + anchor?: (props: { anchor: VideoAnchor; resume: () => void; unmute: () => void }) => any + overlay?: (props: VideoSlotProps) => any + controls?: (props: VideoSlotProps) => any + 'controls-left'?: (props: VideoSlotProps) => any + 'controls-right'?: (props: VideoSlotProps) => any +}> + +type VideoComponent = DefineComponent< + VideoProps, + Record, + Record, + Record, + Record, + ComponentOptionsMixin, + ComponentOptionsMixin, + { + 'update:modelValue': (playing: boolean) => void + play: () => void + pause: () => void + ended: () => void + timeupdate: (currentTime: TimeUnit, duration: TimeUnit) => void + error: (error: unknown) => void + loadedmetadata: (duration: TimeUnit) => void + ratechange: (rate: number) => void + volumechange: (volume: number, muted: boolean) => void + seeking: (currentTime: TimeUnit) => void + seeked: (currentTime: TimeUnit) => void + }, + string, + PublicProps, + Readonly, + Record, + VideoSlots +> + +const Video = VideoSfc as VideoComponent + +export { Video } +export default Video +export type { VideoAnchor, VideoProps, VideoSlotProps } diff --git a/src/composables/useAudioManager.ts b/src/composables/useAudioManager.ts new file mode 100644 index 0000000..9ebc88e --- /dev/null +++ b/src/composables/useAudioManager.ts @@ -0,0 +1,145 @@ +import { computed, onBeforeUnmount, ref, type MaybeRefOrGetter, toValue } from 'vue' + +type AudioMode = 'global' | 'channel' + +interface UseAudioManagerOptions { + mode?: MaybeRefOrGetter + channelId?: MaybeRefOrGetter +} + +interface AudioState { + currentSrc: string | null + isPlaying: boolean +} + +const audioPool = new Map() +const statePool = new Map() +const playTokens = new Map() +const listeners = new Set<() => void>() + +function resolveKey(options?: UseAudioManagerOptions): string { + if ((toValue(options?.mode) ?? 'global') === 'global') { + return '__GLOBAL__' + } + + return `channel:${toValue(options?.channelId) ?? '__DEFAULT__'}` +} + +function getState(key: string): AudioState { + return ( + statePool.get(key) ?? { + currentSrc: null, + isPlaying: false + } + ) +} + +function emit(key: string, state: AudioState) { + statePool.set(key, state) + listeners.forEach(listener => listener()) +} + +function nextToken(key: string) { + const token = (playTokens.get(key) ?? 0) + 1 + playTokens.set(key, token) + return token +} + +function isLatestToken(key: string, token: number) { + return playTokens.get(key) === token +} + +function getOrCreateAudio(key: string): HTMLAudioElement { + let audio = audioPool.get(key) + + if (!audio) { + audio = new Audio() + audioPool.set(key, audio) + + audio.addEventListener('ended', () => { + const state = getState(key) + + emit(key, { + currentSrc: state.currentSrc, + isPlaying: false + }) + }) + } + + return audio +} + +export function useAudioManager(options?: UseAudioManagerOptions) { + const key = computed(() => resolveKey(options)) + const state = ref(getState(key.value)) + + const syncState = () => { + state.value = getState(key.value) + } + + listeners.add(syncState) + + onBeforeUnmount(() => { + listeners.delete(syncState) + }) + + const play = async (src: string) => { + const currentKey = key.value + const audio = getOrCreateAudio(currentKey) + const token = nextToken(currentKey) + + audio.pause() + + if (getState(currentKey).currentSrc !== src) { + audio.src = src + } + + audio.currentTime = 0 + + emit(currentKey, { + currentSrc: src, + isPlaying: false + }) + + try { + await audio.play() + + if (!isLatestToken(currentKey, token)) return + + emit(currentKey, { + currentSrc: src, + isPlaying: true + }) + } catch { + if (!isLatestToken(currentKey, token)) return + + emit(currentKey, { + currentSrc: src, + isPlaying: false + }) + } + } + + const stop = () => { + const currentKey = key.value + const audio = audioPool.get(currentKey) + + nextToken(currentKey) + + if (audio) { + audio.pause() + audio.currentTime = 0 + } + + emit(currentKey, { + currentSrc: getState(currentKey).currentSrc, + isPlaying: false + }) + } + + return { + state, + play, + stop + } +} diff --git a/src/config/moduleStepMap.ts b/src/config/moduleStepMap.ts new file mode 100644 index 0000000..b9abd17 --- /dev/null +++ b/src/config/moduleStepMap.ts @@ -0,0 +1,41 @@ +export type ModuleName = 'film' | 'vlog' | 'interview' +export type StepIndex = 1 | 2 | 3 + +/** + * 模块步骤 -> 子页面 route.name 列表 + * + * 说明: + * - 用于“停留≥阈值后自动计为已查看(viewed)”以及计算 completedStep。 + * - 按需求:菜单中 disabled=true 的子项不计入完成条件,因此这里不包含 disabled 子项。 + */ +export const moduleStepMap: Record> = { + film: { + 1: ['FilmResource', 'FilmCreative', 'FilmScriptWriting', 'FilmStoryboard'], + 2: ['FilmMidtermEquipment', 'FilmMidtermShooting', 'FilmOnsiteShooting'], + 3: ['FilmEditing', 'FilmColorPractice', 'FilmSoundEffect', 'FilmExportPublish'] + }, + vlog: { + 1: ['VlogResource', 'VlogCreative', 'VlogScriptWriting', 'VlogStoryboard'], + 2: ['VlogMidtermEquipment', 'VlogMidtermShooting', 'VlogShooting'], + 3: ['VlogEditing', 'VlogColor', 'VlogExportPublish'] + }, + interview: { + 1: ['InterviewResource', 'InterviewCreative', 'InterviewScriptWriting', 'InterviewStoryboard'], + 2: ['InterviewEquipment', 'InterviewShooting', 'InterviewOnsiteShooting'], + 3: ['InterviewEditing', 'InterviewColorPractice', 'InterviewAudio', 'InterviewExportPublish'] + } +} as const + +export const allTrackedRouteNames = new Set( + Object.values(moduleStepMap).flatMap(stepMap => Object.values(stepMap).flatMap(names => names)) +) + +export function getModuleNameByRouteName(routeName: string): ModuleName | null { + for (const moduleName of Object.keys(moduleStepMap) as ModuleName[]) { + const stepMap = moduleStepMap[moduleName] + for (const step of [1, 2, 3] as const) { + if (stepMap[step].includes(routeName)) return moduleName + } + } + return null +} diff --git a/src/router/index.ts b/src/router/index.ts index 5cf6695..7a271e9 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,8 @@ import { createRouter, createWebHashHistory } from 'vue-router' import type { RouteRecordRaw } from 'vue-router' import { BasicElements, Film, Interview, Vlog } from './modules' +import { allTrackedRouteNames, getModuleNameByRouteName } from '@/config/moduleStepMap' +import { useFilmStore, useInterviewStore, useVlogStore } from '@/stores' const NotFound = { path: '/:pathMatch(.*)*', @@ -15,7 +17,7 @@ const routes: RouteRecordRaw[] = [ { path: '/', name: 'Home', - component: () => import('@/views/home/index.vue'), + component: () => import('@/views/home/index.tsx'), meta: { title: '首页' } @@ -61,13 +63,46 @@ const router = createRouter({ routes }) +const VIEW_THRESHOLD_MS = 3000 +let currentTrackedRouteName: string | null = null +let currentEnterAt = 0 + // 路由守卫 -router.beforeEach((to, _from, next) => { +router.beforeEach((to, from, next) => { // 设置页面标题 if (to.meta?.title) { document.title = `${to.meta.title} - 短视频达人` } + + // 结算离开:from 是上一个页面 + const fromName = typeof from.name === 'string' ? from.name : null + if ( + fromName && + currentTrackedRouteName === fromName && + allTrackedRouteNames.has(fromName) && + currentEnterAt > 0 + ) { + const stayedMs = Date.now() - currentEnterAt + if (stayedMs >= VIEW_THRESHOLD_MS) { + const moduleName = getModuleNameByRouteName(fromName) + if (moduleName === 'film') useFilmStore().markViewed(fromName) + if (moduleName === 'vlog') useVlogStore().markViewed(fromName) + if (moduleName === 'interview') useInterviewStore().markViewed(fromName) + } + } next() }) +router.afterEach(to => { + // 记录进入:to 是当前页面 + const toName = typeof to.name === 'string' ? to.name : null + if (toName && allTrackedRouteNames.has(toName)) { + currentTrackedRouteName = toName + currentEnterAt = Date.now() + } else { + currentTrackedRouteName = null + currentEnterAt = 0 + } +}) + export default router diff --git a/src/router/modules/interview.ts b/src/router/modules/interview.ts index 2a0c3ec..8458234 100644 --- a/src/router/modules/interview.ts +++ b/src/router/modules/interview.ts @@ -29,6 +29,22 @@ const Interview: RouteRecordRaw[] = [ } }, //前期准备 + { + path: '/interview/resource', + name: 'InterviewResource', + component: () => import('@/views/interview/resource/index.vue'), + meta: { + title: '资源筹备' + } + }, + { + path: '/interview/creative', + name: 'InterviewCreative', + component: () => import('@/views/interview/creative/index.vue'), + meta: { + title: '创意构想' + } + }, { path: '/interview/script-writing', name: 'InterviewScriptWriting', @@ -37,6 +53,14 @@ const Interview: RouteRecordRaw[] = [ title: '脚本撰写' } }, + { + path: '/interview/storyboard', + name: 'InterviewStoryboard', + component: () => import('@/views/interview/storyboard/index.vue'), + meta: { + title: '分镜制作' + } + }, //中期拍摄 { path: '/interview/equipment', @@ -54,6 +78,14 @@ const Interview: RouteRecordRaw[] = [ title: '实地取景' } }, + { + path: '/interview/on-site-shooting', + name: 'InterviewOnsiteShooting', + component: () => import('@/views/interview/midterm-shooting/on-site/index.vue'), + meta: { + title: '现场拍摄' + } + }, { path: '/interview/filmingLocation-a', name: 'InterviewFilmingLocationA', @@ -89,6 +121,14 @@ const Interview: RouteRecordRaw[] = [ // title: '调色' // } // }, + { + path: '/interview/color-practice', + name: 'InterviewColorPractice', + component: () => import('@/views/interview/after-editing/color-practice.vue'), + meta: { + title: '后期剪辑-调色实践' + } + }, { path: '/interview/color', name: 'InterviewColor', @@ -97,6 +137,22 @@ const Interview: RouteRecordRaw[] = [ title: '调色' } }, + { + path: '/interview/audio', + name: 'InterviewAudio', + component: () => import('@/views/interview/after-editing/audio/index.vue'), + meta: { + title: '音频' + } + }, + { + path: '/interview/export-publish', + name: 'InterviewExportPublish', + component: () => import('@/views/interview/after-editing/export-publish/index.vue'), + meta: { + title: '导出发布' + } + }, // 实战操作台 { path: '/interview/practice', diff --git a/src/router/modules/vlog.ts b/src/router/modules/vlog.ts index 1401a38..1bd7b71 100644 --- a/src/router/modules/vlog.ts +++ b/src/router/modules/vlog.ts @@ -28,7 +28,23 @@ const Vlog: RouteRecordRaw[] = [ title: '发现与记录-视频' } }, - //前期准备-脚本撰写 + //前期准备 + { + path: '/vlog/resource', + name: 'VlogResource', + component: () => import('@/views/vlog/resource/index.vue'), + meta: { + title: '资源筹备' + } + }, + { + path: '/vlog/creative', + name: 'VlogCreative', + component: () => import('@/views/vlog/creative/index.vue'), + meta: { + title: '创意构想' + } + }, { path: '/vlog/script-writing', name: 'VlogScriptWriting', @@ -37,6 +53,14 @@ const Vlog: RouteRecordRaw[] = [ title: '脚本撰写' } }, + { + path: '/vlog/storyboard', + name: 'VlogStoryboard', + component: () => import('@/views/vlog/storyboard/index.vue'), + meta: { + title: '分镜制作' + } + }, //中期拍摄 { path: '/vlog/equipment', @@ -62,54 +86,54 @@ const Vlog: RouteRecordRaw[] = [ title: '现场拍摄' } }, - { - path: '/vlog/on-site-shooting/time-lapse', - name: 'VlogOnsiteTimeLapse', - component: () => import('@/views/vlog/midterm-shooting/on-site/time-lapse.vue'), - meta: { - title: '现场拍摄-延时拍摄' - } - }, - { - path: '/vlog/on-site-shooting/slider', - name: 'VlogOnsiteSlider', - component: () => import('@/views/vlog/midterm-shooting/on-site/slider.vue'), - meta: { - title: '现场拍摄-滑轨拍摄' - } - }, - { - path: '/vlog/on-site-shooting/slow-shutter-day', - name: 'VlogOnsiteSlowShutterDay', - component: () => import('@/views/vlog/midterm-shooting/on-site/slow-shutter-day.vue'), - meta: { - title: '现场拍摄-慢门拍摄(白天)' - } - }, - { - path: '/vlog/on-site-shooting/slow-shutter-night', - name: 'VlogOnsiteSlowShutterNight', - component: () => import('@/views/vlog/midterm-shooting/on-site/slow-shutter-night.vue'), - meta: { - title: '现场拍摄-慢门拍摄(夜晚)' - } - }, - { - path: '/vlog/on-site-shooting/low-angle', - name: 'VlogOnsiteLowAngle', - component: () => import('@/views/vlog/midterm-shooting/on-site/low-angle.vue'), - meta: { - title: '现场拍摄-低角度拍摄' - } - }, - { - path: '/vlog/on-site-shooting/random', - name: 'VlogOnsiteRandom', - component: () => import('@/views/vlog/midterm-shooting/on-site/random.vue'), - meta: { - title: '现场拍摄-创作随机性' - } - }, + // { + // path: '/vlog/on-site-shooting/time-lapse', + // name: 'VlogOnsiteTimeLapse', + // component: () => import('@/views/vlog/midterm-shooting/on-site/time-lapse.vue'), + // meta: { + // title: '现场拍摄-延时拍摄' + // } + // }, + // { + // path: '/vlog/on-site-shooting/slider', + // name: 'VlogOnsiteSlider', + // component: () => import('@/views/vlog/midterm-shooting/on-site/slider.vue'), + // meta: { + // title: '现场拍摄-滑轨拍摄' + // } + // }, + // { + // path: '/vlog/on-site-shooting/slow-shutter-day', + // name: 'VlogOnsiteSlowShutterDay', + // component: () => import('@/views/vlog/midterm-shooting/on-site/slow-shutter-day.vue'), + // meta: { + // title: '现场拍摄-慢门拍摄(白天)' + // } + // }, + // { + // path: '/vlog/on-site-shooting/slow-shutter-night', + // name: 'VlogOnsiteSlowShutterNight', + // component: () => import('@/views/vlog/midterm-shooting/on-site/slow-shutter-night.vue'), + // meta: { + // title: '现场拍摄-慢门拍摄(夜晚)' + // } + // }, + // { + // path: '/vlog/on-site-shooting/low-angle', + // name: 'VlogOnsiteLowAngle', + // component: () => import('@/views/vlog/midterm-shooting/on-site/low-angle.vue'), + // meta: { + // title: '现场拍摄-低角度拍摄' + // } + // }, + // { + // path: '/vlog/on-site-shooting/random', + // name: 'VlogOnsiteRandom', + // component: () => import('@/views/vlog/midterm-shooting/on-site/random.vue'), + // meta: { + // title: '现场拍摄-创作随机性' + // } + // }, { path: '/vlog/filmingLocation-a', name: 'VlogFilmingLocationA', @@ -151,6 +175,14 @@ const Vlog: RouteRecordRaw[] = [ title: '后期剪辑-调色' } }, + { + path: '/vlog/export-publish', + name: 'VlogExportPublish', + component: () => import('@/views/vlog/after-editing/export-publish/index.vue'), + meta: { + title: '后期剪辑-导出发布' + } + }, // 实战操作台 { path: '/vlog/practice', diff --git a/src/stores/modules/basic-elements.ts b/src/stores/modules/basic-elements.ts index 05a82db..a0419e2 100644 --- a/src/stores/modules/basic-elements.ts +++ b/src/stores/modules/basic-elements.ts @@ -1,6 +1,25 @@ import { defineStore } from 'pinia' import { ref } from 'vue' +export const BASIC_ELEMENTS_LESSON_ID = 'xsdh_sdjcys' +export const BASIC_ELEMENTS_KEY = 1 + +interface BasicElementsSavePayload { + isGuideNewUser?: boolean + isGuideBasicElementsTask?: boolean + isBasicElementsUnlock?: boolean + basicElementsUnlockProgress?: number +} + +async function getYibanApi(): Promise { + try { + return await import('@/utils/yiban') + } catch (error) { + console.error('Failed to load Yiban API', error) + return null + } +} + /** * Basic Elements store - manages Basic Elements information */ @@ -9,6 +28,8 @@ export const useBasicElementsStore = defineStore( () => { //State + //action-新手导航(是否引导新手导航) + const isGuideNewUser = ref(true) //basic-elements-任务取景框引导 const isGuideBasicElementsTask = ref(true) //basic-elements-是否解锁完成前置(观看视频) @@ -17,24 +38,78 @@ export const useBasicElementsStore = defineStore( const basicElementsUnlockProgress = ref(0) //Actions + function saveBasicElementsStore(): void { + const payload: BasicElementsSavePayload = { + isGuideNewUser: isGuideNewUser.value, + isGuideBasicElementsTask: isGuideBasicElementsTask.value, + isBasicElementsUnlock: isBasicElementsUnlock.value, + basicElementsUnlockProgress: basicElementsUnlockProgress.value + } + + getYibanApi() + .then(api => + api?.setUserDefine(BASIC_ELEMENTS_LESSON_ID, BASIC_ELEMENTS_KEY, JSON.stringify(payload)) + ) + .catch(error => console.error('Failed to save Basic Elements store', error)) + } + + async function loadBasicElementsStore(): Promise { + try { + const api = await getYibanApi() + if (!api) return + + const raw = await api.getUserDefine(BASIC_ELEMENTS_LESSON_ID, BASIC_ELEMENTS_KEY) + if (!raw) return + + const data = JSON.parse(raw) as BasicElementsSavePayload + isGuideNewUser.value = data.isGuideNewUser ?? true + isGuideBasicElementsTask.value = data.isGuideBasicElementsTask ?? true + isBasicElementsUnlock.value = data.isBasicElementsUnlock ?? false + basicElementsUnlockProgress.value = data.basicElementsUnlockProgress ?? 0 + } catch (error) { + console.error('Failed to load Basic Elements store', error) + } + } + + function setIsGuideNewUser(value: boolean): void { + isGuideNewUser.value = value + saveBasicElementsStore() + } + function setIsGuideBasicElementsTask(value: boolean): void { isGuideBasicElementsTask.value = value + saveBasicElementsStore() } function setIsBasicElementsUnlock(value: boolean): void { isBasicElementsUnlock.value = value + saveBasicElementsStore() } function setBasicElementsUnlockProgress(value: number): void { basicElementsUnlockProgress.value = value + saveBasicElementsStore() + } + + function resetBasicElementsState(): void { + isGuideNewUser.value = true + isGuideBasicElementsTask.value = true + isBasicElementsUnlock.value = false + basicElementsUnlockProgress.value = 0 + saveBasicElementsStore() } return { //State + setIsGuideNewUser, setIsGuideBasicElementsTask, setIsBasicElementsUnlock, setBasicElementsUnlockProgress, + saveBasicElementsStore, + loadBasicElementsStore, + resetBasicElementsState, //Actions + isGuideNewUser, isGuideBasicElementsTask, isBasicElementsUnlock, basicElementsUnlockProgress diff --git a/src/stores/modules/film.ts b/src/stores/modules/film.ts index 3431d3b..46c38ce 100644 --- a/src/stores/modules/film.ts +++ b/src/stores/modules/film.ts @@ -1,5 +1,26 @@ import { defineStore } from 'pinia' -import { ref } from 'vue' +import { computed, ref } from 'vue' +import { moduleStepMap } from '@/config/moduleStepMap' + +export const FILM_LESSON_ID = 'film_rwqjk' +export const FILM_KEY = 4 + +interface FilmSavePayload { + viewed?: Record + isGuideFilmTask?: boolean + hasShownGuideFilmTask?: boolean + isFilmUnlock?: boolean + FilmUnlockProgress?: number +} + +async function getYibanApi(): Promise { + try { + return await import('@/utils/yiban') + } catch (error) { + console.error('Failed to load Yiban API', error) + return null + } +} /** * Film store - manages Film information @@ -9,6 +30,12 @@ export const useFilmStore = defineStore( () => { //State + /** + * 已查看(停留≥阈值)记录 + * key 推荐用 route.name + */ + const viewed = ref>({}) + //Film-任务取景框引导 const isGuideFilmTask = ref(true) const hasShownGuideFilmTask = ref(false) @@ -18,38 +45,116 @@ export const useFilmStore = defineStore( const FilmUnlockProgress = ref(0) //Actions + function saveFilmStore(): void { + const payload: FilmSavePayload = { + viewed: viewed.value, + isGuideFilmTask: isGuideFilmTask.value, + hasShownGuideFilmTask: hasShownGuideFilmTask.value, + isFilmUnlock: isFilmUnlock.value, + FilmUnlockProgress: FilmUnlockProgress.value + } + + getYibanApi() + .then(api => api?.setUserDefine(FILM_LESSON_ID, FILM_KEY, JSON.stringify(payload))) + .catch(error => console.error('Failed to save Film store', error)) + } + + async function loadFilmStore(): Promise { + try { + const api = await getYibanApi() + if (!api) return + + const raw = await api.getUserDefine(FILM_LESSON_ID, FILM_KEY) + if (!raw) return + + const data = JSON.parse(raw) as FilmSavePayload + viewed.value = data.viewed ?? {} + isGuideFilmTask.value = data.isGuideFilmTask ?? true + hasShownGuideFilmTask.value = data.hasShownGuideFilmTask ?? false + isFilmUnlock.value = data.isFilmUnlock ?? false + FilmUnlockProgress.value = data.FilmUnlockProgress ?? 0 + } catch (error) { + console.error('Failed to load Film store', error) + } + } + + function markViewed(key: string): void { + if (!key) return + viewed.value[key] = true + FilmUnlockProgress.value = Math.max( + FilmUnlockProgress.value, + Math.min(completedStep.value + 1, 4) + ) + saveFilmStore() + } + function setIsGuideFilmTask(value: boolean): void { isGuideFilmTask.value = value + saveFilmStore() } function setIsFilmUnlock(value: boolean): void { isFilmUnlock.value = value + saveFilmStore() } function setFilmUnlockProgress(value: number): void { - FilmUnlockProgress.value = value + const maxUnlockedStep = Math.min(completedStep.value + 1, 4) + FilmUnlockProgress.value = Math.min(value, maxUnlockedStep) + saveFilmStore() } function setHasShownGuideFilmTask(value: boolean): void { hasShownGuideFilmTask.value = value + saveFilmStore() } + // 基于“子页面是否已查看”的大步骤完成度(1..3) + // disabled 子项不计入(在这里直接不放进列表) + const stepRouteNames = moduleStepMap.film + + const completedStep = computed(() => { + const isDone = (step: 1 | 2 | 3) => + stepRouteNames[step].every(name => viewed.value[name] === true) + + 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) + }) + // Reset all Film-related persistent state function resetFilmState(): void { + viewed.value = {} isGuideFilmTask.value = true hasShownGuideFilmTask.value = false isFilmUnlock.value = false FilmUnlockProgress.value = 0 + saveFilmStore() } return { //State + markViewed, setIsGuideFilmTask, setIsFilmUnlock, setFilmUnlockProgress, setHasShownGuideFilmTask, + saveFilmStore, + loadFilmStore, resetFilmState, //Actions + completedStep, + progressPercent, + viewed, isGuideFilmTask, isFilmUnlock, FilmUnlockProgress, diff --git a/src/stores/modules/interview.ts b/src/stores/modules/interview.ts index 5b3010e..0680b05 100644 --- a/src/stores/modules/interview.ts +++ b/src/stores/modules/interview.ts @@ -1,5 +1,25 @@ import { defineStore } from 'pinia' -import { ref } from 'vue' +import { computed, ref } from 'vue' +import { moduleStepMap } from '@/config/moduleStepMap' + +export const INTERVIEW_LESSON_ID = 'fangtan_rwqjk' +export const INTERVIEW_KEY = 3 + +interface InterviewSavePayload { + viewed?: Record + isGuideInterviewTask?: boolean + isInterviewUnlock?: boolean + InterviewUnlockProgress?: number +} + +async function getYibanApi(): Promise { + try { + return await import('@/utils/yiban') + } catch (error) { + console.error('Failed to load Yiban API', error) + return null + } +} /** * I store - manages Interview information @@ -9,6 +29,12 @@ export const useInterviewStore = defineStore( () => { //State + /** + * 已查看(停留≥阈值)记录 + * key 推荐用 route.name + */ + const viewed = ref>({}) + //Interview-任务取景框引导 const isGuideInterviewTask = ref(true) //Interview-是否解锁完成前置(观看视频) @@ -17,24 +43,107 @@ export const useInterviewStore = defineStore( const InterviewUnlockProgress = ref(0) //Actions + function saveInterviewStore(): void { + const payload: InterviewSavePayload = { + viewed: viewed.value, + isGuideInterviewTask: isGuideInterviewTask.value, + isInterviewUnlock: isInterviewUnlock.value, + InterviewUnlockProgress: InterviewUnlockProgress.value + } + + getYibanApi() + .then(api => + api?.setUserDefine(INTERVIEW_LESSON_ID, INTERVIEW_KEY, JSON.stringify(payload)) + ) + .catch(error => console.error('Failed to save Interview store', error)) + } + + async function loadInterviewStore(): Promise { + try { + const api = await getYibanApi() + if (!api) return + + const raw = await api.getUserDefine(INTERVIEW_LESSON_ID, INTERVIEW_KEY) + if (!raw) return + + const data = JSON.parse(raw) as InterviewSavePayload + viewed.value = data.viewed ?? {} + isGuideInterviewTask.value = data.isGuideInterviewTask ?? true + isInterviewUnlock.value = data.isInterviewUnlock ?? false + InterviewUnlockProgress.value = data.InterviewUnlockProgress ?? 0 + } catch (error) { + console.error('Failed to load Interview store', error) + } + } + + function markViewed(key: string): void { + if (!key) return + viewed.value[key] = true + InterviewUnlockProgress.value = Math.max( + InterviewUnlockProgress.value, + Math.min(completedStep.value + 1, 4) + ) + saveInterviewStore() + } + + // 基于“子页面是否已查看”的大步骤完成度(1..3) + // disabled 子项不计入(在这里直接不放进列表) + const stepRouteNames = moduleStepMap.interview + + const completedStep = computed(() => { + const isDone = (step: 1 | 2 | 3) => + stepRouteNames[step].every(name => viewed.value[name] === true) + + 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) + }) + function setIsGuideInterviewTask(value: boolean): void { isGuideInterviewTask.value = value + saveInterviewStore() } function setIsInterviewUnlock(value: boolean): void { isInterviewUnlock.value = value + saveInterviewStore() } function setInterviewUnlockProgress(value: number): void { - InterviewUnlockProgress.value = value + const maxUnlockedStep = Math.min(completedStep.value + 1, 4) + InterviewUnlockProgress.value = Math.min(value, maxUnlockedStep) + saveInterviewStore() + } + + function resetInterviewState(): void { + viewed.value = {} + isGuideInterviewTask.value = true + isInterviewUnlock.value = false + InterviewUnlockProgress.value = 0 + saveInterviewStore() } return { //State + markViewed, setIsGuideInterviewTask, setIsInterviewUnlock, setInterviewUnlockProgress, + resetInterviewState, + loadInterviewStore, + saveInterviewStore, //Actions + completedStep, + progressPercent, + viewed, isGuideInterviewTask, isInterviewUnlock, InterviewUnlockProgress diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts index 84f4da8..d411d08 100644 --- a/src/stores/modules/user.ts +++ b/src/stores/modules/user.ts @@ -7,22 +7,25 @@ import { ref } from 'vue' export const useUserStore = defineStore( 'user', () => { - //action-新手导航(是否引导新手导航) - const isGuideNewUser = ref(true) + const courseId = ref(null) + const uid = ref(null) - function setIsGuideNewUser(value: boolean): void { - isGuideNewUser.value = value + function setCourseInfo(payload: { courseId: string | null; uid: string | null }): void { + courseId.value = payload.courseId + uid.value = payload.uid } // Reset all User-related persistent state function resetUserState(): void { - isGuideNewUser.value = true + courseId.value = null + uid.value = null } return { - setIsGuideNewUser, + setCourseInfo, resetUserState, - isGuideNewUser + courseId, + uid } }, { diff --git a/src/stores/modules/vlog.ts b/src/stores/modules/vlog.ts index fd5c7a1..1236cc7 100644 --- a/src/stores/modules/vlog.ts +++ b/src/stores/modules/vlog.ts @@ -1,5 +1,30 @@ import { defineStore } from 'pinia' -import { ref } from 'vue' +import { computed, ref } from 'vue' +import { moduleStepMap } from '@/config/moduleStepMap' + +export const VLOG_LESSON_ID = 'vlog_rwqjk' +export const VLOG_KEY = 2 + +interface VlogSavePayload { + viewed?: Record + isGuideVlogTask?: boolean + isGuideVlogReturn?: boolean + isGuideVlogStage1?: boolean + isGuideVlogStage2?: boolean + isGuideVlogStage3?: boolean + isVlogUnlock?: boolean + vlogUnlockProgress?: number + vlogMenuVisible?: boolean +} + +async function getYibanApi(): Promise { + try { + return await import('@/utils/yiban') + } catch (error) { + console.error('Failed to load Yiban API', error) + return null + } +} /** * Vlog store - manages Vlog information @@ -9,43 +34,185 @@ export const useVlogStore = defineStore( () => { //State + /** + * 已查看(停留≥阈值)记录 + * key 推荐用 route.name + */ + const viewed = ref>({}) + //vlog-任务取景框引导 const isGuideVlogTask = ref(true) + //vlog-返回 VlogWork 时的一次性引导 + const isGuideVlogReturn = ref(true) + //vlog-前期准备 菜单一次性引导 + const isGuideVlogStage1 = ref(true) + //vlog-中期拍摄 菜单一次性引导 + const isGuideVlogStage2 = ref(true) + //vlog-后期剪辑 菜单一次性引导 + const isGuideVlogStage3 = ref(true) //vlog-是否解锁完成前置(观看视频) const isVlogUnlock = ref(false) + + //vlog-菜单(Overlay)展开状态 + const vlogMenuVisible = ref(false) + //vlog-发现与记录解锁进度 const vlogUnlockProgress = ref(0) //Actions + function saveVlogStore(): void { + const payload: VlogSavePayload = { + viewed: viewed.value, + isGuideVlogTask: isGuideVlogTask.value, + isGuideVlogReturn: isGuideVlogReturn.value, + isGuideVlogStage1: isGuideVlogStage1.value, + isGuideVlogStage2: isGuideVlogStage2.value, + isGuideVlogStage3: isGuideVlogStage3.value, + isVlogUnlock: isVlogUnlock.value, + vlogUnlockProgress: vlogUnlockProgress.value, + vlogMenuVisible: vlogMenuVisible.value + } + + getYibanApi() + .then(api => api?.setUserDefine(VLOG_LESSON_ID, VLOG_KEY, JSON.stringify(payload))) + .catch(error => console.error('Failed to save Vlog store', error)) + } + + async function loadVlogStore(): Promise { + try { + const api = await getYibanApi() + if (!api) return + + const raw = await api.getUserDefine(VLOG_LESSON_ID, VLOG_KEY) + if (!raw) return + + const data = JSON.parse(raw) as VlogSavePayload + viewed.value = data.viewed ?? {} + isGuideVlogTask.value = data.isGuideVlogTask ?? true + isGuideVlogReturn.value = data.isGuideVlogReturn ?? true + isGuideVlogStage1.value = data.isGuideVlogStage1 ?? true + isGuideVlogStage2.value = data.isGuideVlogStage2 ?? true + isGuideVlogStage3.value = data.isGuideVlogStage3 ?? true + isVlogUnlock.value = data.isVlogUnlock ?? false + vlogUnlockProgress.value = data.vlogUnlockProgress ?? 0 + vlogMenuVisible.value = data.vlogMenuVisible ?? false + } catch (error) { + console.error('Failed to load Vlog store', error) + } + } + + function markViewed(key: string): void { + if (!key) return + viewed.value[key] = true + vlogUnlockProgress.value = Math.max( + vlogUnlockProgress.value, + Math.min(completedStep.value + 1, 4) + ) + saveVlogStore() + } + function setIsGuideVlogTask(value: boolean): void { isGuideVlogTask.value = value + saveVlogStore() + } + + function setIsGuideVlogReturn(value: boolean): void { + isGuideVlogReturn.value = value + saveVlogStore() + } + + function setIsGuideVlogStage1(value: boolean): void { + isGuideVlogStage1.value = value + saveVlogStore() + } + + function setIsGuideVlogStage2(value: boolean): void { + isGuideVlogStage2.value = value + saveVlogStore() + } + + function setIsGuideVlogStage3(value: boolean): void { + isGuideVlogStage3.value = value + saveVlogStore() + } + + function setVlogMenuVisible(value: boolean): void { + vlogMenuVisible.value = value + saveVlogStore() } function setIsVlogUnlock(value: boolean): void { isVlogUnlock.value = value + saveVlogStore() } function setVlogUnlockProgress(value: number): void { - vlogUnlockProgress.value = value + const maxUnlockedStep = Math.min(completedStep.value + 1, 4) + vlogUnlockProgress.value = Math.min(value, maxUnlockedStep) + saveVlogStore() } + // 基于“子页面是否已查看”的大步骤完成度(1..3) + // disabled 子项不计入(在这里直接不放进列表) + const stepRouteNames = moduleStepMap.vlog + + const completedStep = computed(() => { + const isDone = (step: 1 | 2 | 3) => + stepRouteNames[step].every(name => viewed.value[name] === true) + + 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) + }) + // Reset all Vlog-related persistent state function resetVlogState(): void { + viewed.value = {} isGuideVlogTask.value = true + isGuideVlogReturn.value = true + isGuideVlogStage1.value = true + isGuideVlogStage2.value = true + isGuideVlogStage3.value = true isVlogUnlock.value = false - vlogUnlockProgress.value = 0 + vlogMenuVisible.value = false + saveVlogStore() } return { //State + markViewed, setIsGuideVlogTask, + setIsGuideVlogReturn, + setIsGuideVlogStage1, + setIsGuideVlogStage2, + setIsGuideVlogStage3, setIsVlogUnlock, - setVlogUnlockProgress, + setVlogMenuVisible, + saveVlogStore, + loadVlogStore, resetVlogState, + setVlogUnlockProgress, //Actions + completedStep, + progressPercent, + viewed, isGuideVlogTask, + isGuideVlogReturn, + isGuideVlogStage1, + isGuideVlogStage2, + isGuideVlogStage3, isVlogUnlock, - vlogUnlockProgress + vlogUnlockProgress, + vlogMenuVisible } }, { diff --git a/src/style.css b/src/style.css index 292fca6..194d9de 100644 --- a/src/style.css +++ b/src/style.css @@ -56,3 +56,12 @@ body { ::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } + +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus { + /* 让背景色变化的过渡时间变得无限长,从而锁定透明状态 */ + transition: background-color 99999s ease-in-out 0s; + /* 记得设置文字颜色,否则可能会变白看不见 */ + -webkit-text-fill-color: #fff !important; +} diff --git a/src/utils/yiban.ts b/src/utils/yiban.ts new file mode 100644 index 0000000..caee277 --- /dev/null +++ b/src/utils/yiban.ts @@ -0,0 +1,226 @@ +import { useUserStore } from '@/stores' + +declare const $: { + extend(value: Record): void + toJSON(value: unknown): string + parseJSON(value: string): unknown + getUrlVars?: () => string[] & Record + getUrlVar?: (name: string) => string | undefined +} +declare const iscallyb: boolean +declare const emykey: string +declare function videoAjax(): VideoAjax +declare function getFutureDa(days: number): string +declare function getCurrentDa(str: string): void +declare function formatDateFromMilliseconds(date: Date): string + +interface UserDefineParam { + lessonId: string + key?: string | number + order?: string | number + content?: string +} + +interface VideoAjaxResponseItem { + content?: string +} + +interface VideoAjaxResponse { + code: number + data: { + value?: VideoAjaxResponseItem[] | string + } +} + +interface VideoAjax { + setHost(host: string): void + addUserDefine(param: UserDefineParam): void + getUserDefineInfoByUser( + param: UserDefineParam, + callback: (data: VideoAjaxResponse | undefined) => void + ): void + getUserDefineInfo( + param: UserDefineParam, + callback: (data: VideoAjaxResponse | undefined) => void + ): void + finishCourse(param: { order: number; co: string; process: number }): void +} + +$.extend({ + getUrlVars(): string[] & Record { + const vars = [] as unknown as string[] & Record + const queryIndex = window.location.href.indexOf('?') + if (queryIndex === -1) return vars + + const hashes = window.location.href + .slice(queryIndex + 1) + .split('&') + .filter(Boolean) + + for (const item of hashes) { + const [keyRaw = '', valueRaw = ''] = item.split('=') + const key = decodeURIComponent(keyRaw) + const value = decodeURIComponent(valueRaw) + vars.push(key) + vars[key] = value + } + + return vars + }, + getUrlVar(name: string): string | undefined { + return $.getUrlVars?.()[name] + } +}) + +const LESSON_PREFIX = 'dsp' + +const videoajax: VideoAjax = videoAjax() +videoajax.setHost('/') + +function normalizeLessonId(lessonId: string | number): string { + const raw = String(lessonId) + return raw.startsWith(`${LESSON_PREFIX}_`) ? raw : `${LESSON_PREFIX}_${raw}` +} + +function setLtDefine(key: string | number, value: string): void { + if (iscallyb) { + setUserDefine(1, key, value) + } else { + sessionStorage.setItem(`${LESSON_PREFIX}_${key}`, value) + } +} + +let mycstr9 = '' + +async function getLtDefine(key: string | number): Promise { + if (iscallyb) { + if (mycstr9 === '') { + const all = await getUsersDefine(1, emykey) + if (all === undefined || all === null || all === 'null' || all === '') { + mycstr9 = getFutureDa(10) + setLtDefine(emykey, mycstr9) + } else { + mycstr9 = all as string + } + } + + 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 + } + + const itemvalue = sessionStorage.getItem(`${LESSON_PREFIX}_${key}`) + return itemvalue === undefined || itemvalue === null || itemvalue === 'null' ? '' : itemvalue +} + +export function setCourseNotepad(collect: string[]): void { + setLtDefine(10002, $.toJSON(collect)) +} + +export async function getCourseNotepad(): Promise { + const raw = await getLtDefine(10002) + if (raw === undefined || raw === null || raw === '') { + return ['', '', '', '', '', '', '', '', ''] + } + + return $.parseJSON(raw) as string[] +} + +export function setUserDefine( + lessonId: string | number, + key: string | number, + value: string +): void { + const param: UserDefineParam = { + lessonId: normalizeLessonId(lessonId), + key, + content: value + } + videoajax.addUserDefine(param) +} + +function getUserDefineAsyncInternal( + lessonId: string | number, + key: string | number +): Promise { + const param: UserDefineParam = { + lessonId: normalizeLessonId(lessonId), + order: key + } + + return new Promise(resolve => { + let content = '' + videoajax.getUserDefineInfoByUser(param, res => { + console.log(res) + if (res && res.code === 200) { + const json = (res.data.value || []) as VideoAjaxResponseItem[] + content = json[0]?.content || '' + } else { + content = '' + } + resolve(content) + }) + }) +} + +export async function getUserDefine( + lessonId: string | number, + key: string | number +): Promise { + return getUserDefineAsyncInternal(lessonId, key) +} + +function getUsersDefineAsyncInternal( + lessonId: string | number, + key: string | number +): Promise { + const param: UserDefineParam = { + lessonId: normalizeLessonId(lessonId), + order: key + } + + return new Promise(resolve => { + let content: string | VideoAjaxResponseItem[] = '' + videoajax.getUserDefineInfo(param, data => { + if (data && data.code === 200) { + const json = (data.data.value || []) as VideoAjaxResponseItem[] + if (json.length > 1) { + if (key === emykey) { + const mdate = new Date( + Math.max.apply( + null, + json.map(item => new Date(item.content || '').getTime()) + ) + ) + content = formatDateFromMilliseconds(mdate) + } else { + content = json + } + } else { + content = json[0]?.content || '' + } + } else { + content = '' + } + resolve(content) + }) + }) +} + +export async function getUsersDefine( + lessonId: string | number, + key: string | number +): Promise { + return getUsersDefineAsyncInternal(lessonId, key) +} + +export async function setCourseTimeProgress(order: number): Promise { + const courseid = useUserStore().courseId + console.info(`courseid:${courseid} order:${order}`) + if (iscallyb && courseid !== undefined && courseid !== null && courseid !== '') { + if (order > 0) { + videoajax.finishCourse({ order, co: courseid, process: 100 }) + } + } +} diff --git a/src/views/basic-elements/basic-shooting-skills/index.vue b/src/views/basic-elements/basic-shooting-skills/index.vue index 845c69b..7c43f2d 100644 --- a/src/views/basic-elements/basic-shooting-skills/index.vue +++ b/src/views/basic-elements/basic-shooting-skills/index.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import ImgButton from '@/components/Button/ImgButton.vue' import { StepNavigation, GuideSteps, GuideStepsItem } from '@/components/Guide' import { Video, QuestionQuiz, type Question } from '@/components' -import { getStaticImage, IMAGE_PATHS } from '@/utils' +import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' // 视频导入 // 手机摄像头介绍 import video1 from '/video/new/xinshou_shoujishexiangtoujieshao.mp4' @@ -146,7 +146,7 @@ const getQuestionsForVideo = (index: number): Question[] => { return [ { id: 3, - title: '互动题 4', + title: '', description: '(单选)你觉得下面哪句话,更接近“新手拍视频的正确思路”?', options: [ { @@ -223,6 +223,12 @@ const handleStepNavigationClose = () => { showStepNavigation.value = false } +const handleIntroGuideComplete = () => { + nextTick(() => { + playing.value = true + }) +} + // 答题完成后的处理 const handleQuestionsComplete = () => { showQuiz.value = false @@ -291,12 +297,16 @@ const handleQuestionsComplete = () => { @close="handleStepNavigationClose" /> - - 欢迎来到基础拍摄训练区! - 接下来,我会带你依次认识手机摄像头、看懂景别、用好慢动作与延时技法。 - 还会教你如何拍出更稳的微距和更亮的夜景。 + + + 欢迎来到基础拍摄训练区! + + + 我会带你依次认识手机摄像头、看懂景别、用好慢动作与延时。 + + + 还会教你拍出更稳的微距和更亮的夜景。 +
diff --git a/src/views/basic-elements/camera-movement/index.vue b/src/views/basic-elements/camera-movement/index.vue index 3fc7c9c..c3234f6 100644 --- a/src/views/basic-elements/camera-movement/index.vue +++ b/src/views/basic-elements/camera-movement/index.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import ImgButton from '@/components/Button/ImgButton.vue' import { StepNavigation } from '@/components/Guide' import { Video } from '@/components' -import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils' +import { getBackgroundImage, getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' // 运镜初识 - 暂时用运镜的基本方式代替 import video1 from '/video/new/xinshou_yunjingdejibenfangshi.mp4' import video2 from '/video/new/xinshou_zhengquedeshoujipaishe.mp4' @@ -20,6 +20,23 @@ const props = defineProps<{ const playing = ref(false) const status = ref(false) +const showCameraMovementGuide = ref(false) +const cameraMovementGuideShown = ref(false) + +const handleIntroGuideComplete = () => { + nextTick(() => { + playing.value = true + }) +} + +const handleCameraMovementGuideComplete = () => { + showCameraMovementGuide.value = false + cameraMovementGuideShown.value = true + nextTick(() => { + playing.value = true + }) +} + // 步骤导航数据 const navigationSteps = ref([ // { @@ -60,6 +77,14 @@ const handleVideoChange = () => { status.value = true return } + + // 切到“运镜的基本方式”(index 1)时,先暂停并展示引导对话 + if (currentVideoIndex.value === 1 && !cameraMovementGuideShown.value) { + playing.value = false + showCameraMovementGuide.value = true + return + } + nextTick(() => { // 使用受控播放来触发下一个视频自动播放 playing.value = true @@ -145,9 +170,26 @@ const handleStepNavigationClose = () => { @close="handleStepNavigationClose" /> - - 想拍出好的视频效果,画面稳定性是最基本的要求。 - 接下来,我们来学习正确的持机方式和拍摄姿势。 + + + 想拍出好的视频效果,画面稳定性是最基本的要求。 + + + 接下来,我们来学习正确的持机方式和拍摄姿势。 + + + + + + 当我们将画面稳定之后,就来到了镜头语言。 + + + 同一个场景,用不同运镜,表达就不一样。跟着我,去了解5种基础运镜吧~ +
diff --git a/src/views/basic-elements/composition/index.vue b/src/views/basic-elements/composition/index.vue index f0aee4e..3fb4a2d 100644 --- a/src/views/basic-elements/composition/index.vue +++ b/src/views/basic-elements/composition/index.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import ImgButton from '@/components/Button/ImgButton.vue' import { StepNavigation } from '@/components/Guide' import { Video } from '@/components' -import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils' +import { getBackgroundImage, getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' import video1 from '/video/new/xsdh_rhxzgt_batch.mp4' import video2 from '/video/new/xsdh_jzgtf_batch.mp4' import { GuideSteps, GuideStepsItem } from '@/components/Guide' @@ -75,6 +75,8 @@ const onEnded = () => { const handleStepClick = (index: number) => { currentVideoIndex.value = index currentNavigationStep.value = index + // 先重置播放状态,确保 v-model 触发 watcher + playing.value = false nextTick(() => { playing.value = true }) @@ -84,6 +86,12 @@ const handleStepNavigationClose = () => { showStepNavigation.value = false } +const handleIntroGuideComplete = () => { + nextTick(() => { + playing.value = true + }) +} + onMounted(() => {}) @@ -145,15 +153,23 @@ onMounted(() => {}) :highlight-selector="guideStepIndex === 3 ? '.step-navigation-btn' : ''" position-offset-y="85%" @step-change="(_: any, index: number) => (guideStepIndex = index)" + @complete="handleIntroGuideComplete" > - 你有没有过这样的感觉?画面里什么都有,却不知道重点在哪里? - 很多时候,不是内容不够好,而是需要有构图思维。 - 所谓构图,就是在画面中对元素进行选择、取舍和安排 - + + 你有没有过这样的感觉?画面里什么都有,却不知道重点在哪里? + + + 很多时候,不是内容不够好,而是需要有构图思维。 + + + 所谓构图,就是在画面中对元素进行选择、取舍和安排 + +
一起来看看吧~
- 点击
diff --git a/src/views/basic-elements/short-video-editing-skills/index.vue b/src/views/basic-elements/short-video-editing-skills/index.vue index 53a4037..c262719 100644 --- a/src/views/basic-elements/short-video-editing-skills/index.vue +++ b/src/views/basic-elements/short-video-editing-skills/index.vue @@ -2,10 +2,9 @@ import { computed, nextTick, ref } from 'vue' import { useRouter } from 'vue-router' import ImgButton from '@/components/Button/ImgButton.vue' -import { StepNavigation } from '@/components/Guide' +import { StepNavigation, GuideSteps, GuideStepsItem } from '@/components/Guide' import { Video } from '@/components' -import { getStaticImage, IMAGE_PATHS } from '@/utils' -import video from '/video/new/xsdh_CapCut_Tutorial.mp4' +import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' const router = useRouter() @@ -18,55 +17,71 @@ const rawSteps = [ { id: 'step2', label: '剪映基础操作', - content: '认识剪映剪辑界面', + content: '初识剪映专业版界面和功能', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step3', label: '剪映基础操作', - content: '素材导入及调整', + content: '时间线的用法详解', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step4', label: '剪映基础操作', - content: '比例及背景设置', + content: '转场特效贴纸功能详解', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step5', label: '剪映基础操作', - content: '内容裁剪及组合', + content: '如何给视频快速上字幕', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step6', label: '剪映基础操作', - content: '音频添加及调整', + content: '音频的功能应用和处理', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step7', label: '剪映基础操作', - content: '添加字幕和贴纸', + content: '关键帧功能用法详解', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step8', label: '剪映基础操作', - content: '添加特效和滤镜', + content: '蒙版功能用法详解', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step9', label: '剪映基础操作', - content: '添加转场和动画', + content: '混合模式功能用法详解', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const }, { id: 'step10', label: '剪映基础操作', - content: '封面和视频导出', + content: '曲线变速功能用法详解', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), + type: 'other' as const + }, + { + id: 'step11', + label: '剪映基础操作', + content: '如何给画面调色', + videoUrl: getPublicPath('video/new/xsdh_CapCut_Tutorial.mp4'), type: 'other' as const } ] @@ -78,13 +93,13 @@ const showStepNavigation = ref(false) const currentNavigationStep = ref(0) // 对应视频索引,从0开始 // 视频列表 -const videoList = [video, video, video, video, video, video, video, video, video] const currentVideoIndex = ref(0) //按顺序播放 -const currentVideo = computed(() => videoList[currentVideoIndex.value]) +const currentVideo = computed(() => rawSteps[currentVideoIndex.value].videoUrl) + const handleVideoChange = () => { - if (currentVideoIndex.value < videoList.length - 1) { + if (currentVideoIndex.value < rawSteps.length - 1) { currentVideoIndex.value++ currentNavigationStep.value = currentVideoIndex.value } else { @@ -119,6 +134,12 @@ const handleStepClick = (index: number) => { const handleStepNavigationClose = () => { showStepNavigation.value = false } + +const handleIntroGuideComplete = () => { + nextTick(() => { + playing.value = true + }) +} diff --git a/src/views/film/after-editing/color-practice.vue b/src/views/film/after-editing/color-practice.vue index 8d36413..041ef0d 100644 --- a/src/views/film/after-editing/color-practice.vue +++ b/src/views/film/after-editing/color-practice.vue @@ -21,10 +21,10 @@ const back = () => { @click="back" /> - + - +
diff --git a/src/views/film/after-editing/export-publish/index.vue b/src/views/film/after-editing/export-publish/index.vue index 03f65d8..0e19e42 100644 --- a/src/views/film/after-editing/export-publish/index.vue +++ b/src/views/film/after-editing/export-publish/index.vue @@ -1,15 +1,12 @@ diff --git a/src/views/film/after-editing/sound-effect/index.vue b/src/views/film/after-editing/sound-effect/index.vue index ad9599d..2a95ca3 100644 --- a/src/views/film/after-editing/sound-effect/index.vue +++ b/src/views/film/after-editing/sound-effect/index.vue @@ -21,10 +21,10 @@ const back = () => { @click="back" /> - + - +
diff --git a/src/views/film/components/menu.vue b/src/views/film/components/menu.vue index 98d45b1..19229f3 100644 --- a/src/views/film/components/menu.vue +++ b/src/views/film/components/menu.vue @@ -3,6 +3,7 @@ import { getStaticImage } from '@/utils' import { computed, ref } from 'vue' import { useRouter } from 'vue-router' import { getBackgroundImage } from '@/utils' +import { useFilmStore } from '@/stores' defineOptions({ name: 'FilmMenu' }) /** @@ -71,12 +72,12 @@ const menuItems = ref([ parentValue: 2, disabled: false }, - { - label: '素材管理', - routeName: 'Film素材管理', - parentValue: 3, - disabled: true - }, + // { + // label: '素材管理', + // routeName: 'Film素材管理', + // parentValue: 3, + // disabled: true + // }, { label: '剪辑', routeName: 'FilmEditing', @@ -109,6 +110,11 @@ const props = defineProps<{ }>() const router = useRouter() +const film = useFilmStore() + +const isViewed = (routeName: string) => { + return film.viewed?.[routeName] === true +} const currentParentValue = computed(() => { return props.currentStep @@ -131,9 +137,9 @@ const handleItemClick = (item: { routeName: string; disabled: boolean }) => {
- {{ - category[currentParentValue - 1].label - }} + + {{ category[currentParentValue - 1].label }} +
@@ -157,7 +163,18 @@ const handleItemClick = (item: { routeName: string; disabled: boolean }) => { class="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 group-hover:opacity-100 transition-opacity duration-300" :style="{ backgroundImage: getBackgroundImage('components/menu-hover.png') }" >
- {{ item.label }} + + {{ item.label }} + +
diff --git a/src/views/film/creative/index.vue b/src/views/film/creative/index.vue index e0fb3be..5f78c31 100644 --- a/src/views/film/creative/index.vue +++ b/src/views/film/creative/index.vue @@ -32,10 +32,10 @@ const handleOpenImages = () => { @click="back" /> - + - +
diff --git a/src/views/film/index.vue b/src/views/film/index.vue index d703359..1b0c1c8 100644 --- a/src/views/film/index.vue +++ b/src/views/film/index.vue @@ -17,9 +17,7 @@ const status = ref(true) const currentStep = computed(() => film.FilmUnlockProgress) -const progress = computed(() => { - return Math.round((currentStep.value / 4) * 100) -}) +const progress = computed(() => film.progressPercent) const guideIndex = ref(0) const showTaskGuide = computed( @@ -130,7 +128,7 @@ watch( highlight-selector=".right-section" position-offset-y="50%" > - 点击这里进入实战操作台 + 点击这里进入实战操作台 @@ -167,7 +165,7 @@ watch(
@@ -177,6 +175,7 @@ watch( @@ -191,11 +190,9 @@ watch( >
- 请点击进入任务取景框开始学习! + 请点击 + + 进入任务取景框开始学习!
diff --git a/src/views/film/midterm-shooting/equipment/index.vue b/src/views/film/midterm-shooting/equipment/index.vue index c20d9fc..5a7cb15 100644 --- a/src/views/film/midterm-shooting/equipment/index.vue +++ b/src/views/film/midterm-shooting/equipment/index.vue @@ -1,6 +1,6 @@ diff --git a/src/views/film/midterm-shooting/shooting/index.vue b/src/views/film/midterm-shooting/shooting/index.vue index 98f638f..f5bedfd 100644 --- a/src/views/film/midterm-shooting/shooting/index.vue +++ b/src/views/film/midterm-shooting/shooting/index.vue @@ -41,15 +41,6 @@ const steps = ref([ } ]) -const currentIdx = ref(0) -const handleStepChange = (_: any, index: number) => { - currentIdx.value = index -} - -const currentHighlight = computed(() => { - return steps.value[currentIdx.value]?.highlightSelector -}) - const guideSteps = ref([ { id: '1', @@ -67,14 +58,6 @@ const guideSteps = ref([ highlightSelector: '.btn-item-3' } ]) -const currentIndex = ref(0) -const handleGuideStepChange = (_: any, index: number) => { - currentIndex.value = index -} -const currentHighlightSelector = computed(() => { - return guideSteps.value[currentIndex.value ?? 0]?.highlightSelector -}) - const showModal = ref(false) const showModal2 = ref(false) @@ -134,7 +117,6 @@ const handleClickB = () => { router.push({ name: 'FilmFilmingLocationB' }) } const back = () => { - film.setFilmUnlockProgress(3) // 跳转设备准备 router.back() } @@ -167,11 +149,9 @@ const back = () => { @@ -219,12 +199,9 @@ const back = () => { :visible="true" :steps="guideSteps" :keyboard-navigation="true" - :highlight-selector="currentHighlightSelector" :highlight-padding="12" :highlight-radius="16" position-offset-y="85%" - @step-change="handleGuideStepChange" - @complete="currentIndex = 0" /> diff --git a/src/views/film/resource/index.vue b/src/views/film/resource/index.vue index dedb475..7ef95ba 100644 --- a/src/views/film/resource/index.vue +++ b/src/views/film/resource/index.vue @@ -3,7 +3,6 @@ import { IMAGE_PATHS, getStaticImage } from '@/utils' import { ImgButton } from '@/components/Button' import { useRouter } from 'vue-router' import { GuideSteps, GuideStepsItem } from '@/components/Guide' -import { ref } from 'vue' const router = useRouter() @@ -20,10 +19,10 @@ const back = () => { @click="back" /> - + - +
diff --git a/src/views/film/script-writing/index.vue b/src/views/film/script-writing/index.vue index a3a8982..fa97edc 100644 --- a/src/views/film/script-writing/index.vue +++ b/src/views/film/script-writing/index.vue @@ -33,10 +33,10 @@ const handleOpenImages = () => { @click="back" /> - + - +
diff --git a/src/views/film/script-writing/sample.vue b/src/views/film/script-writing/sample.vue index 1d7314c..588bc97 100644 --- a/src/views/film/script-writing/sample.vue +++ b/src/views/film/script-writing/sample.vue @@ -1,34 +1,30 @@ diff --git a/src/views/home/index.tsx b/src/views/home/index.tsx new file mode 100644 index 0000000..67df467 --- /dev/null +++ b/src/views/home/index.tsx @@ -0,0 +1,113 @@ +import { defineComponent, ref } from 'vue' +import { getBackgroundImage, getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils' +import { ImgButton } from '@/components/Button' +import { GuideSteps, ImagePreview } from '@/components' +import { useScale } from '@/composables/useScale' + +const CourseFrameGuide = () => ( +
+ 点击 + + 查看课程结构,快速了解每个模块的学习内容。 +
+) + +const OperateGuide = () => ( +
+ + 包含课程介绍和操作指南,观看指引视频,轻松上手课程。(资源建设中...) +
+) + +const StartFilmingGuide = () => ( +
+ 点击 + + 正式进入课程,开启你的短视频拍摄之旅。 +
+) + +export default defineComponent({ + name: 'HomeView', + setup() { + const guideSteps = ref([ + { + id: 'step-1', + title: '', + content: '欢迎来到《短视频达人养成记》网络课程的学习!', + highlightSelector: null, + audioSrc: getPublicPath('audio/py_0_001.MP3'), + positionOffsetY: '80%' + }, + { + id: 'step-2', + title: '', + content: CourseFrameGuide, + highlightSelector: '.kecheng-wrapper', + audioSrc: getPublicPath('audio/py_0_002.MP3'), + positionOffsetY: '40%' + }, + { + id: 'step-3', + title: '', + content: OperateGuide, + highlightSelector: '.zhiying-wrapper', + audioSrc: getPublicPath('audio/py_0_003.MP3'), + positionOffsetY: '40%' + }, + { + id: 'step-4', + title: '', + content: StartFilmingGuide, + highlightSelector: '.start-filming-wrapper', + audioSrc: getPublicPath('audio/py_0_004.MP3'), + positionOffsetY: '40%' + } + ]) + const showGuide = ref(true) + const { scale, offsetX, offsetY } = useScale() + + const handleComplete = () => { + console.log('handleComplete') + } + + const openMindMap = () => { + ImagePreview({ + images: [getStaticImage('maps/dt')], + maskClosable: true, + getScaleMetrics: () => ({ scale: scale.value, offsetX: offsetX.value, offsetY: offsetY.value }) + }) + } + + return () => ( +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ ) + } +}) diff --git a/src/views/home/index.vue b/src/views/home/index.vue deleted file mode 100644 index 8f3b55d..0000000 --- a/src/views/home/index.vue +++ /dev/null @@ -1,327 +0,0 @@ - - - - - diff --git a/src/views/interview/after-editing/audio/index.vue b/src/views/interview/after-editing/audio/index.vue new file mode 100644 index 0000000..29cb37a --- /dev/null +++ b/src/views/interview/after-editing/audio/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/interview/after-editing/color-practice.vue b/src/views/interview/after-editing/color-practice.vue new file mode 100644 index 0000000..6c99640 --- /dev/null +++ b/src/views/interview/after-editing/color-practice.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/views/interview/after-editing/color.vue b/src/views/interview/after-editing/color.vue index b4a15d8..bf82000 100644 --- a/src/views/interview/after-editing/color.vue +++ b/src/views/interview/after-editing/color.vue @@ -5,7 +5,6 @@ import { ImgButton } from '@/components/Button' import { Overlay } from '@/components/Overlay' import { ref } from 'vue' import ImageFilter from '@/components/ImageFilter/ImageFilter.vue' -import { useInterviewStore } from '@/stores' const selected = ref(1) const btnState = ref(false) @@ -25,11 +24,11 @@ const adjustmentList = ref([ const colorBarList = ref([ { id: 1, name: '原图', filter: 'none' }, - { id: 2, name: '暖阳', filter: 'sepia(0.3) contrast(1.1) brightness(1.1)' }, + { id: 2, name: '暖阳', filter: 'saturate(2.5) contrast(2.1)' }, { id: 3, name: '冷调', filter: 'hue-rotate(180deg) contrast(0.9)' }, { id: 4, name: '复古', filter: 'sepia(0.6) contrast(1.2) brightness(0.9)' }, { id: 5, name: '黑白', filter: 'grayscale(1)' }, - { id: 6, name: '鲜艳', filter: 'saturate(1.5) contrast(1.1)' } + { id: 6, name: '鲜艳', filter: 'sepia(0.1) saturate(2.5) contrast(2.1) brightness(1)' } ]) // 获取指定参数的值 @@ -117,10 +116,8 @@ const handleTryOther = () => { } const handleFinish = () => { - useInterviewStore().setInterviewUnlockProgress(4) - router.push({ - name: 'Interview' + name: 'Film' }) } @@ -157,7 +154,7 @@ const changeColor = (item: { id: number; name: string }) => { class="relative w-[1273px] h-[732px] bg-no-repeat bg-cover mt-10 p-2 pb-[26px] pr-[26px]" :style="{ backgroundImage: getBackgroundImage('after-editing/color/图片框.png') }" > - + { + +

+ + + 推荐参数:亮度 90、对比度 100、饱和度 100、曝光 - 90、色调 0、锐化 100、模糊 0 + +

+
diff --git a/src/views/interview/after-editing/editing.vue b/src/views/interview/after-editing/editing.vue index 266edb4..66c2deb 100644 --- a/src/views/interview/after-editing/editing.vue +++ b/src/views/interview/after-editing/editing.vue @@ -1,69 +1,63 @@ - - diff --git a/src/views/interview/after-editing/export-publish/index.vue b/src/views/interview/after-editing/export-publish/index.vue new file mode 100644 index 0000000..1a7997f --- /dev/null +++ b/src/views/interview/after-editing/export-publish/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/interview/components/menu.vue b/src/views/interview/components/menu.vue index 764680d..ed95d41 100644 --- a/src/views/interview/components/menu.vue +++ b/src/views/interview/components/menu.vue @@ -3,7 +3,8 @@ import { getStaticImage } from '@/utils' import { computed, ref } from 'vue' import { useRouter } from 'vue-router' import { getBackgroundImage } from '@/utils' -defineOptions({ name: 'VlogMenu' }) +import { useInterviewStore } from '@/stores' +defineOptions({ name: 'InterviewMenu' }) /** * 菜单项 (前期准备、中期拍摄、后期剪辑) @@ -33,13 +34,13 @@ const menuItems = ref([ label: '资源筹备', routeName: 'InterviewResource', parentValue: 1, - disabled: true + disabled: false }, { label: '创意构想', routeName: 'InterviewCreative', parentValue: 1, - disabled: true + disabled: false }, { label: '脚本撰写', @@ -51,32 +52,32 @@ const menuItems = ref([ label: '分镜制作', routeName: 'InterviewStoryboard', parentValue: 1, - disabled: true + disabled: false }, { label: '设备准备', - routeName: 'InterviewMidtermEquipment', + routeName: 'InterviewEquipment', parentValue: 2, disabled: false }, { label: '实地取景', - routeName: 'InterviewMidtermShooting', + routeName: 'InterviewShooting', parentValue: 2, disabled: false }, { label: '现场拍摄', - routeName: 'Interview现场拍摄', + routeName: 'InterviewOnsiteShooting', parentValue: 2, - disabled: true - }, - { - label: '素材管理', - routeName: 'Interview素材管理', - parentValue: 3, - disabled: true + disabled: false }, + // { + // label: '素材管理', + // routeName: 'Interview素材管理', + // parentValue: 3, + // disabled: true + // }, { label: '剪辑', routeName: 'InterviewEditing', @@ -85,15 +86,21 @@ const menuItems = ref([ }, { label: '调色', - routeName: 'InterviewColor', + routeName: 'InterviewColorPractice', + parentValue: 3, + disabled: false + }, + { + label: '音频', + routeName: 'InterviewAudio', parentValue: 3, disabled: false }, { label: '导出发布', - routeName: 'Interview导出发布', + routeName: 'InterviewExportPublish', parentValue: 3, - disabled: true + disabled: false } ]) @@ -102,12 +109,19 @@ const props = defineProps<{ }>() const router = useRouter() +const interview = useInterviewStore() + +const isViewed = (routeName: string) => { + return interview.viewed?.[routeName] === true +} const currentParentValue = computed(() => { return props.currentStep }) // 当前选中的父级菜单项 -const currentItemValue = ref('') // 当前选中的子级菜单项 +const currentItemValue = computed(() => { + return router.currentRoute.value.name?.toString() ?? '' +}) const getMenuItems = computed(() => { return menuItems.value.filter(item => item.parentValue === currentParentValue.value) @@ -115,7 +129,6 @@ const getMenuItems = computed(() => { const handleItemClick = (item: { routeName: string; disabled: boolean }) => { if (item.disabled) return - currentItemValue.value = item.routeName router.push({ name: item.routeName }) } @@ -124,9 +137,9 @@ const handleItemClick = (item: { routeName: string; disabled: boolean }) => {
- {{ - category[currentParentValue - 1].label - }} + + {{ category[currentParentValue - 1].label }} +
@@ -150,7 +163,14 @@ const handleItemClick = (item: { routeName: string; disabled: boolean }) => { class="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 group-hover:opacity-100 transition-opacity duration-300" :style="{ backgroundImage: getBackgroundImage('components/menu-hover.png') }" >
- {{ item.label }} + + {{ item.label }} + +
diff --git a/src/views/interview/creative/index.vue b/src/views/interview/creative/index.vue new file mode 100644 index 0000000..bcb55da --- /dev/null +++ b/src/views/interview/creative/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/interview/index.vue b/src/views/interview/index.vue index 01a8ea4..c61ee95 100644 --- a/src/views/interview/index.vue +++ b/src/views/interview/index.vue @@ -1,8 +1,9 @@ - diff --git a/src/views/interview/resource/index.vue b/src/views/interview/resource/index.vue new file mode 100644 index 0000000..51db578 --- /dev/null +++ b/src/views/interview/resource/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/interview/script-writing/doubao.vue b/src/views/interview/script-writing/doubao.vue deleted file mode 100644 index e69de29..0000000 diff --git a/src/views/interview/script-writing/index.vue b/src/views/interview/script-writing/index.vue index 29851f1..a39197b 100644 --- a/src/views/interview/script-writing/index.vue +++ b/src/views/interview/script-writing/index.vue @@ -1,88 +1,44 @@ + diff --git a/src/views/interview/script-writing/sample.vue b/src/views/interview/script-writing/sample.vue deleted file mode 100644 index 4770839..0000000 --- a/src/views/interview/script-writing/sample.vue +++ /dev/null @@ -1,175 +0,0 @@ - - - diff --git a/src/views/interview/script-writing/writing.vue b/src/views/interview/script-writing/writing.vue deleted file mode 100644 index 99524ee..0000000 --- a/src/views/interview/script-writing/writing.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/src/views/interview/storyboard/index.vue b/src/views/interview/storyboard/index.vue new file mode 100644 index 0000000..53dd879 --- /dev/null +++ b/src/views/interview/storyboard/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/interview/video/index.vue b/src/views/interview/video/index.vue index 5ec7250..3e73603 100644 --- a/src/views/interview/video/index.vue +++ b/src/views/interview/video/index.vue @@ -99,6 +99,13 @@ const handleVideoChange = () => { } const handleClose = () => { + if (key.value === 'idea') { + interview.setInterviewUnlockProgress(1) + router.push({ + name: 'Interview' + }) + return + } router.back() } @@ -119,11 +126,6 @@ const handleComplete = () => { router.push({ name: 'InterviewWork' }) - } else { - if (interview.InterviewUnlockProgress == 0) interview.setInterviewUnlockProgress(1) - router.push({ - name: 'Interview' - }) } } @@ -150,7 +152,6 @@ onMounted(() => {
diff --git a/src/views/vlog/creative/index.vue b/src/views/vlog/creative/index.vue new file mode 100644 index 0000000..5c03d98 --- /dev/null +++ b/src/views/vlog/creative/index.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/views/vlog/index.vue b/src/views/vlog/index.vue index 7662926..645cf5e 100644 --- a/src/views/vlog/index.vue +++ b/src/views/vlog/index.vue @@ -1,19 +1,135 @@ diff --git a/src/views/vlog/midterm-shooting/equipment/index.vue b/src/views/vlog/midterm-shooting/equipment/index.vue index c19efa7..afb69b2 100644 --- a/src/views/vlog/midterm-shooting/equipment/index.vue +++ b/src/views/vlog/midterm-shooting/equipment/index.vue @@ -1,7 +1,7 @@ diff --git a/src/views/vlog/midterm-shooting/on-site/low-angle.vue b/src/views/vlog/midterm-shooting/on-site/low-angle.vue deleted file mode 100644 index 0ac19d0..0000000 --- a/src/views/vlog/midterm-shooting/on-site/low-angle.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/src/views/vlog/midterm-shooting/on-site/random.vue b/src/views/vlog/midterm-shooting/on-site/random.vue deleted file mode 100644 index 629e2fa..0000000 --- a/src/views/vlog/midterm-shooting/on-site/random.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/src/views/vlog/midterm-shooting/on-site/slider.vue b/src/views/vlog/midterm-shooting/on-site/slider.vue deleted file mode 100644 index fc9231c..0000000 --- a/src/views/vlog/midterm-shooting/on-site/slider.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/src/views/vlog/midterm-shooting/on-site/slow-shutter-day.vue b/src/views/vlog/midterm-shooting/on-site/slow-shutter-day.vue deleted file mode 100644 index 626184a..0000000 --- a/src/views/vlog/midterm-shooting/on-site/slow-shutter-day.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/src/views/vlog/midterm-shooting/on-site/slow-shutter-night.vue b/src/views/vlog/midterm-shooting/on-site/slow-shutter-night.vue deleted file mode 100644 index af48399..0000000 --- a/src/views/vlog/midterm-shooting/on-site/slow-shutter-night.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - diff --git a/src/views/vlog/midterm-shooting/on-site/time-lapse.vue b/src/views/vlog/midterm-shooting/on-site/time-lapse.vue deleted file mode 100644 index 8d2c5e0..0000000 --- a/src/views/vlog/midterm-shooting/on-site/time-lapse.vue +++ /dev/null @@ -1,157 +0,0 @@ - - - diff --git a/src/views/vlog/midterm-shooting/shooting/filmingLocation-b/index.vue b/src/views/vlog/midterm-shooting/shooting/filmingLocation-b/index.vue index ea8f711..df648cc 100644 --- a/src/views/vlog/midterm-shooting/shooting/filmingLocation-b/index.vue +++ b/src/views/vlog/midterm-shooting/shooting/filmingLocation-b/index.vue @@ -9,7 +9,6 @@ import StartOverlay from '../components/StartOverlay.vue' import LoadingOverlay from '@/components/Loading/LoadingOverlay.vue' import { GuideSteps, GuideStepsItem } from '@/components/Guide' import { useRouter } from 'vue-router' -import { useVlogStore } from '@/stores' const router = useRouter() const start = ref(false) @@ -114,7 +113,6 @@ const captureScene = async (): Promise => { */ const handleComplete = (): void => { showGuide.value = false - useVlogStore().setVlogUnlockProgress(3) router.push({ name: 'Vlog' }) @@ -283,7 +281,7 @@ const handleComplete = (): void => { - 实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~ + 实地取景已全部完成,现在跟着我走进现场开始正式的拍摄吧~
diff --git a/src/views/vlog/midterm-shooting/shooting/index.vue b/src/views/vlog/midterm-shooting/shooting/index.vue index 01ddc5f..780df97 100644 --- a/src/views/vlog/midterm-shooting/shooting/index.vue +++ b/src/views/vlog/midterm-shooting/shooting/index.vue @@ -4,7 +4,7 @@ import { useRouter } from 'vue-router' import { GuideSteps, GuideStepsItem } from '@/components/Guide' import { Overlay } from '@/components/Overlay' import { ImgButton } from '@/components/Button' -import { getBackgroundImage, IMAGE_PATHS } from '@/utils' +import { getBackgroundImage, getPublicPath, IMAGE_PATHS } from '@/utils' import shootingtitle from '@/assets/images/shooting/标题-实地取景.png' import videoSrc from '/video/xinshou_jiuzhonggoutufa.mp4' import KnowledgeCard from '@/components/KnowledgeCard/KnowledgeCard.vue' @@ -102,18 +102,24 @@ const back = () => {
- +
- 在走进拍摄场景之前,我们先来查看一下构图指南。 + + 在走进拍摄场景之前,我们先来回顾一下三种常见的构图方法。 + diff --git a/src/views/vlog/resource/index.vue b/src/views/vlog/resource/index.vue new file mode 100644 index 0000000..aad2930 --- /dev/null +++ b/src/views/vlog/resource/index.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/views/vlog/script-writing/sample.vue b/src/views/vlog/script-writing/sample.vue index ce11b4b..5c8ad4e 100644 --- a/src/views/vlog/script-writing/sample.vue +++ b/src/views/vlog/script-writing/sample.vue @@ -1,110 +1,63 @@ - diff --git a/src/views/vlog/script-writing/writing.vue b/src/views/vlog/script-writing/writing.vue index ffa4993..667660a 100644 --- a/src/views/vlog/script-writing/writing.vue +++ b/src/views/vlog/script-writing/writing.vue @@ -13,23 +13,14 @@ class="titletext" />
- - - - +