浏览代码

英文视频

jwyu 1 年之前
父节点
当前提交
b2e95b3455

+ 4 - 2
package.json

@@ -15,13 +15,15 @@
     "axios": "^1.6.7",
     "crypto-js": "^4.2.0",
     "element-plus": "2.4.4",
+    "froala-editor": "^4.1.4",
+    "highcharts": "11.2.0",
     "jquery": "^3.7.1",
     "js-md5": "^0.8.3",
-    "highcharts": "11.2.0",
-    "moment": "^2.30.1",
     "lodash": "^4.17.21",
+    "moment": "^2.30.1",
     "pinia": "^2.1.7",
     "vue": "^3.4.19",
+    "vue-froala-wysiwyg": "^4.1.4",
     "vue-router": "^4.3.0",
     "vue3-tree-org": "^4.2.2"
   },

+ 100 - 49
pnpm-lock.yaml

@@ -1,9 +1,5 @@
 lockfileVersion: '6.0'
 
-settings:
-  autoInstallPeers: true
-  excludeLinksFromLockfile: false
-
 dependencies:
   '@element-plus/icons-vue':
     specifier: ^2.3.1
@@ -17,27 +13,33 @@ dependencies:
   element-plus:
     specifier: 2.4.4
     version: 2.4.4(vue@3.4.20)
+  froala-editor:
+    specifier: ^4.1.4
+    version: 4.1.4
+  highcharts:
+    specifier: 11.2.0
+    version: 11.2.0
   jquery:
     specifier: ^3.7.1
     version: 3.7.1
   js-md5:
     specifier: ^0.8.3
     version: 0.8.3
-  highcharts:
-    specifier: 11.2.0
-    version: 11.2.0
-  moment:
-    specifier: ^2.30.1
-    version: 2.30.1
   lodash:
     specifier: ^4.17.21
     version: 4.17.21
+  moment:
+    specifier: ^2.30.1
+    version: 2.30.1
   pinia:
     specifier: ^2.1.7
     version: 2.1.7(vue@3.4.20)
   vue:
     specifier: ^3.4.19
     version: 3.4.20
+  vue-froala-wysiwyg:
+    specifier: ^4.1.4
+    version: 4.1.4
   vue-router:
     specifier: ^4.3.0
     version: 4.3.0(vue@3.4.20)
@@ -133,6 +135,14 @@ packages:
     dependencies:
       '@babel/types': 7.23.9
 
+  /@babel/runtime-corejs2@7.24.0:
+    resolution: {integrity: sha512-RZVGq1it0GA1K8rb+z7v7NzecP6VYCMedN7yHsCCIQUMmRXFCPJD8GISdf6uIGj7NDDihg7ieQEzpdpQbUL75Q==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      core-js: 2.6.12
+      regenerator-runtime: 0.14.1
+    dev: false
+
   /@babel/template@7.23.9:
     resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==}
     engines: {node: '>=6.9.0'}
@@ -182,7 +192,7 @@ packages:
     dev: false
 
   /@esbuild/aix-ppc64@0.19.12:
-    resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==, tarball: https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz}
+    resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
     engines: {node: '>=12'}
     cpu: [ppc64]
     os: [aix]
@@ -191,7 +201,7 @@ packages:
     optional: true
 
   /@esbuild/android-arm64@0.19.12:
-    resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==, tarball: https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz}
+    resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [android]
@@ -200,7 +210,7 @@ packages:
     optional: true
 
   /@esbuild/android-arm@0.19.12:
-    resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz}
+    resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [android]
@@ -209,7 +219,7 @@ packages:
     optional: true
 
   /@esbuild/android-x64@0.19.12:
-    resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==, tarball: https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [android]
@@ -218,7 +228,7 @@ packages:
     optional: true
 
   /@esbuild/darwin-arm64@0.19.12:
-    resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==, tarball: https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz}
+    resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [darwin]
@@ -227,7 +237,7 @@ packages:
     optional: true
 
   /@esbuild/darwin-x64@0.19.12:
-    resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==, tarball: https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [darwin]
@@ -236,7 +246,7 @@ packages:
     optional: true
 
   /@esbuild/freebsd-arm64@0.19.12:
-    resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz}
+    resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [freebsd]
@@ -245,7 +255,7 @@ packages:
     optional: true
 
   /@esbuild/freebsd-x64@0.19.12:
-    resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [freebsd]
@@ -254,7 +264,7 @@ packages:
     optional: true
 
   /@esbuild/linux-arm64@0.19.12:
-    resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz}
+    resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [linux]
@@ -263,7 +273,7 @@ packages:
     optional: true
 
   /@esbuild/linux-arm@0.19.12:
-    resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz}
+    resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [linux]
@@ -272,7 +282,7 @@ packages:
     optional: true
 
   /@esbuild/linux-ia32@0.19.12:
-    resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==, tarball: https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz}
+    resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [linux]
@@ -281,7 +291,7 @@ packages:
     optional: true
 
   /@esbuild/linux-loong64@0.19.12:
-    resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz}
+    resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
     engines: {node: '>=12'}
     cpu: [loong64]
     os: [linux]
@@ -290,7 +300,7 @@ packages:
     optional: true
 
   /@esbuild/linux-mips64el@0.19.12:
-    resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==, tarball: https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz}
+    resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
     engines: {node: '>=12'}
     cpu: [mips64el]
     os: [linux]
@@ -299,7 +309,7 @@ packages:
     optional: true
 
   /@esbuild/linux-ppc64@0.19.12:
-    resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==, tarball: https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz}
+    resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
     engines: {node: '>=12'}
     cpu: [ppc64]
     os: [linux]
@@ -308,7 +318,7 @@ packages:
     optional: true
 
   /@esbuild/linux-riscv64@0.19.12:
-    resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==, tarball: https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz}
+    resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
     engines: {node: '>=12'}
     cpu: [riscv64]
     os: [linux]
@@ -317,7 +327,7 @@ packages:
     optional: true
 
   /@esbuild/linux-s390x@0.19.12:
-    resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==, tarball: https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz}
+    resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
     engines: {node: '>=12'}
     cpu: [s390x]
     os: [linux]
@@ -326,7 +336,7 @@ packages:
     optional: true
 
   /@esbuild/linux-x64@0.19.12:
-    resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==, tarball: https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [linux]
@@ -335,7 +345,7 @@ packages:
     optional: true
 
   /@esbuild/netbsd-x64@0.19.12:
-    resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [netbsd]
@@ -344,7 +354,7 @@ packages:
     optional: true
 
   /@esbuild/openbsd-x64@0.19.12:
-    resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [openbsd]
@@ -353,7 +363,7 @@ packages:
     optional: true
 
   /@esbuild/sunos-x64@0.19.12:
-    resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==, tarball: https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [sunos]
@@ -362,7 +372,7 @@ packages:
     optional: true
 
   /@esbuild/win32-arm64@0.19.12:
-    resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==, tarball: https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz}
+    resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [win32]
@@ -371,7 +381,7 @@ packages:
     optional: true
 
   /@esbuild/win32-ia32@0.19.12:
-    resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz}
+    resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [win32]
@@ -380,7 +390,7 @@ packages:
     optional: true
 
   /@esbuild/win32-x64@0.19.12:
-    resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==, tarball: https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz}
+    resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [win32]
@@ -435,7 +445,7 @@ packages:
     dev: true
 
   /@rollup/rollup-android-arm-eabi@4.12.0:
-    resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz}
+    resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==}
     cpu: [arm]
     os: [android]
     requiresBuild: true
@@ -443,7 +453,7 @@ packages:
     optional: true
 
   /@rollup/rollup-android-arm64@4.12.0:
-    resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz}
+    resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
@@ -451,7 +461,7 @@ packages:
     optional: true
 
   /@rollup/rollup-darwin-arm64@4.12.0:
-    resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz}
+    resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
@@ -459,7 +469,7 @@ packages:
     optional: true
 
   /@rollup/rollup-darwin-x64@4.12.0:
-    resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz}
+    resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
@@ -467,7 +477,7 @@ packages:
     optional: true
 
   /@rollup/rollup-linux-arm-gnueabihf@4.12.0:
-    resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz}
+    resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
@@ -475,7 +485,7 @@ packages:
     optional: true
 
   /@rollup/rollup-linux-arm64-gnu@4.12.0:
-    resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz}
+    resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
@@ -484,7 +494,7 @@ packages:
     optional: true
 
   /@rollup/rollup-linux-arm64-musl@4.12.0:
-    resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz}
+    resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
@@ -493,7 +503,7 @@ packages:
     optional: true
 
   /@rollup/rollup-linux-riscv64-gnu@4.12.0:
-    resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz}
+    resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==}
     cpu: [riscv64]
     os: [linux]
     libc: [glibc]
@@ -502,7 +512,7 @@ packages:
     optional: true
 
   /@rollup/rollup-linux-x64-gnu@4.12.0:
-    resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz}
+    resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
@@ -511,7 +521,7 @@ packages:
     optional: true
 
   /@rollup/rollup-linux-x64-musl@4.12.0:
-    resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz}
+    resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==}
     cpu: [x64]
     os: [linux]
     libc: [musl]
@@ -520,7 +530,7 @@ packages:
     optional: true
 
   /@rollup/rollup-win32-arm64-msvc@4.12.0:
-    resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz}
+    resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
@@ -528,7 +538,7 @@ packages:
     optional: true
 
   /@rollup/rollup-win32-ia32-msvc@4.12.0:
-    resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz}
+    resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==}
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
@@ -536,7 +546,7 @@ packages:
     optional: true
 
   /@rollup/rollup-win32-x64-msvc@4.12.0:
-    resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz}
+    resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
@@ -544,7 +554,7 @@ packages:
     optional: true
 
   /@sxzz/popperjs-es@2.11.7:
-    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==, tarball: https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz}
+    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
     dev: false
 
   /@types/estree@1.0.5:
@@ -702,6 +712,13 @@ packages:
       - debug
     dev: false
 
+  /babel-runtime@6.26.0:
+    resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==}
+    dependencies:
+      core-js: 2.6.12
+      regenerator-runtime: 0.11.1
+    dev: false
+
   /binary-extensions@2.2.0:
     resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
     engines: {node: '>=8'}
@@ -755,6 +772,12 @@ packages:
       delayed-stream: 1.0.0
     dev: false
 
+  /core-js@2.6.12:
+    resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
+    deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
+    requiresBuild: true
+    dev: false
+
   /core-js@3.36.0:
     resolution: {integrity: sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==}
     requiresBuild: true
@@ -903,8 +926,12 @@ packages:
       mime-types: 2.1.35
     dev: false
 
+  /froala-editor@4.1.4:
+    resolution: {integrity: sha512-oWF8SZNtLvfweURV5T0WYO69ZQpB1LQiGO2e6zoYRAlOwmqlW5yqLWfGi0tfn99qOgZ/4dxqBBDxqfOsRCQFiA==}
+    dev: false
+
   /fsevents@2.3.3:
-    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz}
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     os: [darwin]
     requiresBuild: true
@@ -999,7 +1026,7 @@ packages:
     dev: false
 
   /lodash@4.17.21:
-    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, tarball: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz}
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
     dev: false
 
   /magic-string@0.30.7:
@@ -1095,6 +1122,14 @@ packages:
       picomatch: 2.3.1
     dev: true
 
+  /regenerator-runtime@0.11.1:
+    resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==}
+    dev: false
+
+  /regenerator-runtime@0.14.1:
+    resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+    dev: false
+
   /rollup@4.12.0:
     resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -1224,6 +1259,18 @@ packages:
       vue: 3.4.20
     dev: false
 
+  /vue-froala-wysiwyg@4.1.4:
+    resolution: {integrity: sha512-eut/YwWJf/LiM4eVTsgazCLyxTCiHbdPiLWhHXGH0RExMUEu/g191Ajzy5qkfLkfXTLDVoD7+yyOe8Kzg4Ha/g==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/runtime-corejs2': 7.24.0
+      babel-runtime: 6.26.0
+      froala-editor: 4.1.4
+      vue: 3.4.20
+    transitivePeerDependencies:
+      - typescript
+    dev: false
+
   /vue-router@4.3.0(vue@3.4.20):
     resolution: {integrity: sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==}
     peerDependencies:
@@ -1263,3 +1310,7 @@ packages:
     dependencies:
       isexe: 2.0.0
     dev: true
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false

+ 14 - 0
src/main.js

@@ -13,6 +13,19 @@ import '@/styles/global.scss'
 import vue3TreeOrg from 'vue3-tree-org';
 import "vue3-tree-org/lib/vue3-tree-org.css";
 
+
+import 'froala-editor/js/plugins.pkgd.min.js';
+//Import third party plugins
+import 'froala-editor/js/third_party/embedly.min';
+import 'froala-editor/js/third_party/font_awesome.min';
+import 'froala-editor/js/third_party/spell_checker.min';
+import 'froala-editor/js/third_party/image_tui.min';
+// Import Froala Editor css files.
+import 'froala-editor/css/froala_editor.pkgd.min.css';
+import 'froala-editor/css/froala_style.min.css';
+
+import VueFroala from 'vue-froala-wysiwyg';
+
 function setupApp() {
   const app = createApp(App)
   initStore(app)
@@ -21,6 +34,7 @@ function setupApp() {
 
   app.use(ElementPlus,{locale: zhCn})
   app.use(vue3TreeOrg)
+  app.use(VueFroala);
   app.mount('#app')
 }
 

+ 20 - 20
src/router/modules/ficcXcxRoutes.js

@@ -13,26 +13,26 @@ export default [
 				name:"中文视频",
 				component:()=>import('@/views/interaction_manage/videoManage.vue')
 			},
-            // {	
-			// 	path:"videoManageEN",
-			// 	name:"英文视频",
-			// 	component:()=>import('@/views/interaction_manage/videoManageEN.vue')
-			// },
-            // {	
-			// 	path:"videoManageENAdd",
-			// 	name:"添加视频",
-			// 	component:()=>import('@/views/interaction_manage/videoManageENAdd.vue'),
-			// 	meta: {
-			// 		pathFrom:'videoManageEN',
-			// 		pathName:'英文视频',
-			// 		keepAlive: false
-			// 	}
-			// },
-            // {	
-			// 	path:"videoManageENEdit",
-			// 	name:"编辑视频",
-			// 	component:()=>import('@/views/interaction_manage/videoManageENAdd.vue')
-			// },
+            {	
+				path:"videoManageEN",
+				name:"英文视频",
+				component:()=>import('@/views/interaction_manage/videoManageEN.vue')
+			},
+            {	
+				path:"videoManageENAdd",
+				name:"添加视频",
+				component:()=>import('@/views/interaction_manage/videoManageENAdd.vue'),
+				meta: {
+					pathFrom:'videoManageEN',
+					pathName:'英文视频',
+					keepAlive: false
+				}
+			},
+            {	
+				path:"videoManageENEdit",
+				name:"编辑视频",
+				component:()=>import('@/views/interaction_manage/videoManageENAdd.vue')
+			},
             // {
 			// 	path: 'reportupdate',
 			// 	component: () => import('@/views/report_manage/dayWeekUpdate.vue'),

+ 1114 - 0
src/views/interaction_manage/videoManageEN.vue

@@ -0,0 +1,1114 @@
+<script setup>
+import { reactive, ref } from 'vue'
+import { videoENInterface, PVDetailList } from '@/api/modules/reportEnApi'
+import * as reportEnInterface from '@/api/modules/reportEnApi';
+import { customInterence, videoInterface, reportVarietyENInterence } from '@/api/api.js'
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { Search } from "@element-plus/icons-vue";
+
+const router = useRouter()
+
+function formatDuration(e) {
+  let tem = parseInt(e)
+  let min = parseInt(tem / 60)
+  let sen = parseInt(tem % 60)
+  if (min > 0) {
+    return `${min}分${sen}秒`
+  } else {
+    return `${sen}秒`
+  }
+}
+
+const pageState = reactive({
+  keyword: '',
+  list: [],
+  page: 1,
+  pageSize: 15,
+  total: 0,
+  options: [],
+  perList: [],
+  SortParam: '',
+  SortType: '',
+
+  navType: 3,// 2视频封面库 3线上路演
+  showImgPop: false,
+  imgList: [],
+  imgType: '',
+  imgPopData: {
+    type: '添加封面',
+    coverId: 0,
+    imgUrl: '',
+    name: ''
+  },
+
+  imgrules: {
+    name: [{ required: true, message: '请输入封面名称', trigger: 'blur' }],
+    imgUrl: [{ required: true, message: '请上传视频封面', trigger: 'change' }]
+  },
+
+  showClick: false,
+  logVideoId: 0,
+  logVideoName: '',
+  logPage: 1,
+  logPageSize: 10,
+  logTotal: 0,
+  logList: [],
+  logSortType: '',
+
+  // ---------------预览视频弹窗
+  previewPop: false,
+  previewPopTitle: "",
+  previewVideoUrl: "",
+
+  popEmailData: {
+    show: false,
+    radio: '1',
+    checkUser: false,//是否选择指定人员
+    options: [],
+    value: [],
+    customOptions: [],
+    customValue: [],
+    theme: '',
+    videoId: 0,
+    varietyOpt: [],
+    varietyVal: [],
+  },
+})
+
+function initPage() {
+  getVideoList()
+}
+initPage()
+
+function handleNavChange(type) {
+  pageState.page = 1
+  pageState.total = 0
+  pageState.navType = type
+  pageState.keyword = ''
+  if (type == 2) {
+    handleGetVideoCoverImgList()
+  } else {
+    getVideoList()
+  }
+
+}
+
+function searchHandle() {
+  pageState.page = 1
+  getVideoList()
+}
+
+function handlePageChange(page) {
+  pageState.page = page
+  if (pageState.navType !== 2) {
+    getVideoList()
+  } else {
+    handleGetVideoCoverImgList()
+  }
+
+}
+async function getVideoList() {
+  const params = {
+    KeyWord: pageState.keyword,
+    PageSize: pageState.pageSize,
+    CurrentIndex: pageState.page,
+  }
+  const res = await videoENInterface.roadVideoList(params)
+  if (res.Ret === 200) {
+    pageState.total = res.Data.Paging.Totals
+    pageState.list = res.Data.List || []
+  }
+}
+
+//新增\编辑视频
+function handleShowPop(item) {
+  console.log(item);
+  sessionStorage.setItem('videoENItem', JSON.stringify(item))
+  router.push({
+    path: !item ? '/videoManageENAdd' : 'videoManageENEdit'
+  })
+}
+
+//上传封面
+async function handleUploadImg(e) {
+  console.log(e);
+  let form = new FormData()
+  form.append("file", e.file)
+  const res = await customInterence.upload(form)
+  if (res.Ret === 200) {
+    pageState.imgPopData.imgUrl = res.Data.ResourceUrl
+  }
+}
+
+
+//发布
+async function handlePublished({ Id }) {
+  let res = await videoENInterface.roadVideoPublished({ Id })
+  if (res.Ret === 200) {
+    ElMessage.success('发布成功')
+    getVideoList()
+  }
+}
+
+//取消发布
+async function handleCancelPublish({ Id }) {
+  let res = await videoENInterface.roadVideoPublishedCancel({ Id })
+  if (res.Ret === 200) {
+    ElMessage.success('取消发布成功')
+    getVideoList()
+  }
+}
+
+// 删除
+async function handelDel(item) {
+  const res = await videoENInterface.roadDelVideo({ Id: item.Id })
+  if (res.Ret === 200) {
+    ElMessage.success('删除成功')
+    getVideoList()
+  }
+}
+
+function handleOpt(item, type) {
+  if (type === '发布') {
+    handlePublished(item)
+    return
+  }
+
+  if (type === '编辑') {
+    handleShowPop(item)
+    return
+  }
+
+  if (type === '删除') {
+    ElMessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }).then(() => {
+      handelDel(item)
+    }).catch(() => {
+
+    });
+    return
+  }
+
+  if (type === '取消发布') {
+    handleCancelPublish(item)
+    return
+  }
+
+  if (type === '群发邮件') {
+    pageState.popEmailData.videoId = item.Id
+    pageState.popEmailData.theme = 'Online Presentation: ' + item.Abstract
+    pageState.popEmailData.checkUser = false
+    pageState.popEmailData.varietyOpt = []
+    pageState.popEmailData.varietyVal = []
+    pageState.popEmailData.show = true
+
+    getAllCustomEmail()
+    getReportVarietyOpts()
+    return
+  }
+
+  if (type === '群发日志') {
+    router.push({
+      path: '/sendlog',
+      query: { id: item.Id, type: 1 },//类型:0英文研报,1英文线上路演
+    })
+    return
+  }
+
+}
+
+function sortChangeHandle({ prop, order }) {
+  console.log(prop, order)
+  pageState.SortType = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : ''
+  if (pageState.SortType.length) {
+    pageState.SortParam = prop
+  } else {
+    pageState.SortParam = ''
+  }
+  getVideoList()
+}
+
+const clickNumberRef = ref(null)
+function handleShowClickNum(item) {
+  if (!item.PvEmail) return
+  pageState.logVideoId = item.Id
+  pageState.logVideoName = item.Title
+  pageState.showClick = true
+  pageState.logPage = 1
+  pageState.logList = []
+  pageState.logTotal = 0
+  pageState.logSortType = ''
+  clickNumberRef.value?.clearSort()
+  getVideoLogslist()
+}
+
+// 获取点击量详情
+async function getVideoLogslist() {
+  const params = {
+    CurrentIndex: pageState.logPage,
+    PageSize: pageState.logPageSize,
+    ReportId: Number(pageState.logVideoId),
+    ReportType: 1
+  }
+  const res = await PVDetailList(params)
+  if (res.Ret === 200) {
+    pageState.logTotal = res.Data.Paging.Totals
+    // pageState.logCount=res.Data.Count
+    pageState.logList = res.Data.List || []
+  }
+}
+// 点击量详情 - 表格排序
+function detailSortChange({ order }) {
+  pageState.logSortType = order === 'ascending' ? '2' : order === 'descending' ? '1' : ''
+  getVideoLogslist()
+}
+function handleLogPageChange(e) {
+  pageState.logPage = e
+  getVideoLogslist()
+}
+//获取视频封面列表
+async function handleGetVideoCoverImgList() {
+  const res = await videoENInterface.videoCoverImgList({
+    PageSize: pageState.pageSize,
+    CurrentIndex: pageState.page
+  })
+  if (res.Ret === 200) {
+    pageState.total = res.Data.Paging.Totals
+    pageState.imgList = res.Data.List || []
+  }
+}
+
+//显示添加视频封面弹窗
+const imgform = ref(null)
+function handleShowImgPop(item) {
+  pageState.imgPopData.name = ''
+  pageState.imgPopData.imgUrl = ''
+  pageState.imgPopData.coverId = 0
+  if (!item) {
+    pageState.imgPopData.type = '添加封面'
+  } else {
+    pageState.imgPopData.type = '编辑封面'
+    pageState.imgPopData.name = item.CoverName
+    pageState.imgPopData.imgUrl = item.CoverUrl
+    pageState.imgPopData.coverId = item.Id
+  }
+  pageState.showImgPop = true
+  nextTick(() => {
+    imgform.value?.clearValidate();
+  })
+}
+
+//保存视频封面
+function handleSaveImg() {
+  imgform.value.validate((valid) => {
+    if (valid) {
+      let params = {
+        Id: Number(pageState.imgPopData.coverId),
+        CoverName: pageState.imgPopData.name,
+        CoverUrl: pageState.imgPopData.imgUrl
+      }
+      videoENInterface.videoCoverImgSave(params).then(res => {
+        if (res.Ret == 200) {
+          handleGetVideoCoverImgList()
+          if (pageState.imgPopData.type == '添加封面') {
+            ElMessage.success('添加成功')
+          } else {
+            ElMessage.success('编辑成功')
+          }
+          pageState.showImgPop = false
+        }
+      })
+    }
+  })
+}
+
+//删除视频封面
+function handleDelVideoCoverImg(item) {
+  ElMessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    videoENInterface.videoCoverImgDel({
+      Id: Number(item.Id)
+    }).then(res => {
+      if (res.Ret === 200) {
+        ElMessage.success('删除成功')
+        handleGetVideoCoverImgList()
+      }
+    })
+  }).catch(() => {
+
+  });
+}
+
+// 预览视频
+const previewVideo = ref(null)
+function handlePreviewVideo({ Title, VideoUrl }) {
+  previewVideo.value?.play()
+  pageState.previewPopTitle = Title
+  pageState.previewVideoUrl = VideoUrl
+  pageState.previewPop = true
+}
+// 结束预览弹窗关闭回调 -- 暂停视频
+function endingPreview() {
+  previewVideo.value?.pause()
+}
+
+// 获取该报告对应的品种权限数据
+function getReportVarietyOpts() {
+  reportVarietyENInterence.varietyList({
+    VideoId: pageState.popEmailData.videoId
+  }).then(res => {
+    if (res.Ret === 200) {
+      const arr = res.Data || []
+      pageState.popEmailData.varietyOpt = arr.filter(e => {
+        e.Child && e.Child.forEach(_e => delete _e.Child)
+        return e.Child && e.Child.length > 0
+      })
+      // 设置全部选中
+      pageState.popEmailData.varietyOpt.forEach(e => {
+        e.Child.forEach(_e => {
+          pageState.popEmailData.varietyVal.push([e.EnPermissionId, _e.EnPermissionId])
+        })
+      })
+      getCustomListEnFun()
+    }
+  })
+}
+
+// 获取所有邮件客户
+async function getAllCustomEmail() {
+  const res = await reportEnInterface.customEmailList({
+    CurrentIndex: pageState.page,
+    PageSize: 10000,
+  })
+  if (res.Ret === 200) {
+    const arr = res.Data.List || []
+    pageState.popEmailData.options = arr
+  }
+}
+// 获取英文客户列表
+function getCustomListEnFun() {
+  let arr = []
+  pageState.popEmailData.varietyVal && pageState.popEmailData.varietyVal.forEach(_e => {
+    arr.push(_e[1])
+  });
+  if (!(arr.length > 0)) {
+    pageState.popEmailData.customOptions = []
+    return
+  }
+
+  customInterence.getCustomListEn({
+    PageSize: 100000,
+    CurrentIndex: 1,
+    EnPermissionIds: arr.join(',')
+  }).then(res => {
+    if (res.Ret !== 200) return
+    pageState.popEmailData.customOptions = res.Data.List || []
+  })
+}
+//删除选中的群发客户
+function handleDelSelectCustom(item, index) {
+  pageState.popEmailData.value.splice(index, 1)
+}
+//删除选中的英文客户
+function handleDelSelectCompany(item, index) {
+  pageState.popEmailData.customValue.splice(index, 1)
+}
+//发送邮件
+function handleConfirmSendEmail() {
+  let ids = []
+  pageState.popEmailData.value.forEach(item => {
+    ids.push(item.Id)
+  })
+  let customIds = []
+  pageState.popEmailData.customValue.forEach(item => {
+    customIds.push(item.CompanyId)
+  })
+
+  if (pageState.popEmailData.checkUser && ids.length === 0) {//指定人员
+    ElMessage.warning('请选择需要发送邮件的人员')
+    return
+  }
+  if (!pageState.popEmailData.checkUser) {
+    ids = []
+  }
+
+  if (!pageState.popEmailData.theme) {
+    ElMessage.warning('请填写邮件主题')
+    return
+  }
+  if (pageState.popEmailData.theme.length > 100) {
+    ElMessage.warning('邮件主题请控制在100个字符以内')
+    return
+  }
+  const arr = []
+  pageState.popEmailData.varietyVal && pageState.popEmailData.varietyVal.forEach(_e => {
+    arr.push(_e[1])
+  });
+  videoENInterface.videoEmailSend({
+    ReportId: pageState.popEmailData.videoId,
+    EmailIds: ids.join(','),
+    Theme: pageState.popEmailData.theme,
+    EnPermissions: arr,
+    NoCompanyIds: customIds,
+  }).then(res => {
+    if (res.Ret === 200) {
+      ElMessage.success('发送成功')
+      pageState.popEmailData.show = false
+      getVideoList();
+    }
+  })
+}
+
+
+
+</script>
+
+<template>
+  <div class="video-manage-page">
+    <div class="top-wrap">
+      <el-button
+        type="primary"
+        @click="handleShowPop('')"
+        v-if="pageState.navType !== 2"
+        >添加视频</el-button
+      >
+      <div v-if="pageState.navType == 2">
+        <el-button type="primary" @click="handleShowImgPop('')"
+          >添加视频封面</el-button
+        >
+      </div>
+      <el-input
+        placeholder="关键词搜索"
+        v-model="pageState.keyword"
+        style="max-width: 520px"
+        @input="searchHandle"
+        clearable
+        :prefix-icon="Search"
+        v-if="pageState.navType !== 2"
+      >
+      </el-input>
+    </div>
+    <div class="list-wrap">
+      <div class="nav-box">
+        <span
+          :class="pageState.navType == 3 ? 'active' : ''"
+          @click="handleNavChange(3)"
+          >线上路演</span
+        >
+        <span
+          :class="pageState.navType == 2 ? 'active' : ''"
+          @click="handleNavChange(2)"
+          >视频封面库</span
+        >
+      </div>
+      <el-table
+        :data="pageState.list"
+        border
+        style="width: 100%"
+        @sort-change="sortChangeHandle"
+        v-if="pageState.navType !== 2"
+      >
+        <el-table-column prop="Title" label="视频标题" align="center">
+          <template #default="{ row }">
+            <span
+              style="cursor: pointer; color: #409eff"
+              @click="handlePreviewVideo(row)"
+              >{{ row.Title }}</span
+            >
+          </template>
+        </el-table-column>
+        <el-table-column prop="Abstract" label="摘要" align="center">
+        </el-table-column>
+        <el-table-column prop="ClassifyNameFirst" label="分类" align="center">
+          <template #default="{ row }">
+            <span>{{ row.ClassifyNameRoot }}</span>
+            <span v-if="row.ClassifyNameFirst"
+              >/{{ row.ClassifyNameFirst }}</span
+            >
+            <span v-if="row.ClassifyNameSecond"
+              >/{{ row.ClassifyNameSecond }}</span
+            >
+          </template>
+        </el-table-column>
+        <el-table-column prop="AdminRealName" label="创建人" align="center">
+        </el-table-column>
+        <el-table-column prop="ModifyTime" label="更新时间" align="center">
+        </el-table-column>
+        <el-table-column prop="VideoSeconds" label="时长" align="center">
+          <template #default="{ row }">
+            <span>{{ formatDuration(row.VideoSeconds) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="PublishState" label="状态" align="center">
+          <template #default="{ row }">
+            <span :style="{ color: row.State == 2 ? '#1dd411' : '' }">{{
+              row.State == 2 ? "已发布" : "未发布"
+            }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="Pv"
+          label="点击量"
+          sortable="custom"
+          align="center"
+        >
+          <template #default="{ row }">
+            <span
+              :style="{
+                color: row.PvEmail ? '#409eff' : '',
+                cursor: 'pointer',
+              }"
+              @click="handleShowClickNum(row)"
+              >{{ row.PvEmail || "--" }}</span
+            >
+          </template>
+        </el-table-column>
+        <el-table-column prop="opt" label="操作" align="center">
+          <template #default="{ row }">
+            <!-- 未发布 -->
+            <div v-if="row.State == 1">
+              <span
+                style="
+                  cursor: pointer;
+                  color: #409eff;
+                  margin: 0 5px;
+                  display: inline-block;
+                "
+                @click="handleOpt(row, '发布')"
+                >发布</span
+              >
+              <span
+                style="
+                  cursor: pointer;
+                  color: #409eff;
+                  margin: 0 5px;
+                  display: inline-block;
+                "
+                @click="handleOpt(row, '编辑')"
+                >编辑</span
+              >
+              <span
+                style="
+                  cursor: pointer;
+                  color: #ff0000;
+                  margin: 0 5px;
+                  display: inline-block;
+                "
+                @click="handleOpt(row, '删除')"
+                >删除</span
+              >
+            </div>
+            <div v-else>
+              <span
+                style="
+                  cursor: pointer;
+                  color: #ff0000;
+                  margin: 0 5px;
+                  display: inline-block;
+                "
+                @click="handleOpt(row, '取消发布')"
+                >取消发布</span
+              >
+              <span
+                v-if="row.EmailState == 1"
+                style="
+                  cursor: pointer;
+                  color: #1dd411;
+                  margin: 0 5px;
+                  display: inline-block;
+                "
+                @click="handleOpt(row, '群发日志')"
+                >群发日志 <span class="warn-tag" v-if="row.EmailHasFail"></span
+              ></span>
+              <span
+                v-if="row.EmailState === 0 && row.EmailAuth"
+                style="
+                  cursor: pointer;
+                  color: #409eff;
+                  margin: 0 5px;
+                  display: inline-block;
+                "
+                @click="handleOpt(row, '群发邮件')"
+                >群发邮件</span
+              >
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-table
+        :data="pageState.imgList"
+        border
+        style="width: 100%"
+        v-if="pageState.navType == 2"
+      >
+        <el-table-column prop="CoverUrl" label="封面图" align="center">
+          <template #default="{ row }">
+            <el-image
+              style="width: 100px; height: 100px"
+              :src="row.CoverUrl"
+              :preview-src-list="[row.CoverUrl]"
+            >
+            </el-image>
+          </template>
+        </el-table-column>
+        <el-table-column prop="CoverName" label="封面名称" align="center" />
+        <el-table-column prop="ModifyTime" label="更新时间" align="center" />
+        <el-table-column prop="opt" label="操作" align="center">
+          <template #default="{ row }">
+            <span
+              style="
+                cursor: pointer;
+                color: #409eff;
+                margin: 0 5px;
+                display: inline-block;
+              "
+              @click="handleShowImgPop(row)"
+              >编辑</span
+            >
+            <span
+              style="
+                cursor: pointer;
+                color: #ff0000;
+                margin: 0 5px;
+                display: inline-block;
+              "
+              @click="handleDelVideoCoverImg(row)"
+              >删除</span
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        layout="total,prev,pager,next"
+        background
+        :current-page="pageState.page"
+        @current-change="handlePageChange"
+        :page-size="pageState.pageSize"
+        :total="pageState.total"
+        style="float: right; margin-top: 20px"
+      >
+      </el-pagination>
+    </div>
+
+    <!-- 点击量详情 -->
+    <el-dialog
+      v-model="pageState.showClick"
+      :modal-append-to-body="false"
+      draggable
+      width="65vw"
+      title="点击量详情"
+    >
+      <div style="margin-bottom: 118px">
+        <!-- <div style="margin-bottom:10px">{{logVideoName}}</div> -->
+        <el-table
+          :data="pageState.logList"
+          style="width: 100%; margin: 20px 0 30px"
+          ref="clickNumberRef"
+          border
+          @sort-change="detailSortChange"
+        >
+          <el-table-column prop="Name" label="客户姓名" align="center">
+            <template #default="scope">
+              <span>{{ scope.row.Name || "--" }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="Email" label="邮箱地址" align="center">
+          </el-table-column>
+          <el-table-column
+            prop="RecentClickTime"
+            label="最近点击时间"
+            align="center"
+            min-width="140"
+          >
+          </el-table-column>
+          <el-table-column
+            prop="ClickNum"
+            label="点击量"
+            align="center"
+            min-width="140"
+          >
+          </el-table-column>
+        </el-table>
+        <div>
+          <el-pagination
+            layout="total,prev,pager,next"
+            background
+            :current-page="pageState.logPage"
+            @current-change="handleLogPageChange"
+            :page-size="pageState.logPageSize"
+            :total="pageState.logTotal"
+            style="float: right; margin-top: 20px"
+          >
+          </el-pagination>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 上传视频封面弹窗 -->
+    <el-dialog
+      v-model="pageState.showImgPop"
+      :modal-append-to-body="false"
+      draggable
+      width="50vw"
+    >
+      <template #header>
+        <div>{{ pageState.imgPopData.type }}</div>
+      </template>
+      <div class="pop-wrap">
+        <el-form
+          ref="imgform"
+          :model="pageState.imgPopData"
+          label-width="100px"
+          :rules="pageState.imgrules"
+        >
+          <el-form-item label="封面名称" prop="name">
+            <el-input
+              v-model="pageState.imgPopData.name"
+              placeholder="请填写封面名称"
+            ></el-input>
+          </el-form-item>
+          <el-form-item
+            label="封面图"
+            prop="imgUrl"
+            :disabled="pageState.imgPopData.coverId != 0"
+          >
+            <el-input
+              :disabled="pageState.imgPopData.coverId != 0"
+              readonly
+              v-model="pageState.imgPopData.imgUrl"
+              placeholder="请上传视频封面"
+            ></el-input>
+            <el-upload
+              style="display: inline-block"
+              action=""
+              accept="image/*"
+              :http-request="handleUploadImg"
+              :show-file-list="false"
+              :disabled="pageState.imgPopData.coverId != 0"
+            >
+              <el-button
+                type="primary"
+                :disabled="pageState.imgPopData.coverId != 0"
+                >选择封面</el-button
+              >
+            </el-upload>
+          </el-form-item>
+          <div style="text-align: center; margin: 50px 0">
+            <el-button @click="pageState.showImgPop = false">取消</el-button>
+            <el-button type="primary" @click="handleSaveImg">保存</el-button>
+          </div>
+        </el-form>
+      </div>
+    </el-dialog>
+
+    <!-- 预览视频弹窗 -->
+    <el-dialog
+      v-model="pageState.previewPop"
+      :modal-append-to-body="false"
+      draggable
+      width="60vw"
+      :title="pageState.previewPopTitle"
+      @close="endingPreview"
+    >
+      <video
+        style="width: 100%; height: 100%; max-height: 70vh; outline: none"
+        controls
+        :src="pageState.previewVideoUrl"
+        autoplay
+        ref="previewVideo"
+      >
+        您的浏览器暂不支持,请更换浏览器
+      </video>
+    </el-dialog>
+
+    <!-- 群发邮件弹窗 -->
+    <el-dialog
+      draggable
+      :append-to-body="true"
+      v-model="pageState.popEmailData.show"
+      width="900px"
+      title="群发邮件"
+    >
+      <div class="send-email-wrap">
+        <el-form ref="form" :model="pageState.popEmailData" label-width="100px">
+          <el-form-item label="邮件主题:">
+            <div style="flex: 1">
+              <el-input
+                placeholder="请输入邮件主题"
+                v-model="pageState.popEmailData.theme"
+                type="textarea"
+                maxlength="100"
+                show-word-limit
+                :autosize="{ minRows: 3, maxRows: 5 }"
+              ></el-input>
+            </div>
+          </el-form-item>
+          <el-form-item label="收件人:">
+            <div class="addressee-box" style="flex: 1">
+              <div class="addressee-cancel-box">
+                <!-- <el-radio v-model="popEmailData.radio" label="1">默认全部</el-radio> -->
+                <el-cascader
+                  v-if="pageState.popEmailData.show"
+                  v-model="pageState.popEmailData.varietyVal"
+                  :options="pageState.popEmailData.varietyOpt"
+                  collapse-tags
+                  clearable
+                  :props="{
+                    multiple: true,
+                    value: 'EnPermissionId',
+                    label: 'EnPermissionName',
+                    children: 'Child',
+                  }"
+                  placeholder="请选择品种权限"
+                  style="margin-bottom: 10px"
+                />
+                <div class="user-box-hint">取消发送客户信息:</div>
+                <el-select
+                  v-model="pageState.popEmailData.customValue"
+                  multiple
+                  filterable
+                  collapse-tags
+                  placeholder="请选择客户"
+                  value-key="CompanyId"
+                >
+                  <el-option
+                    v-for="item in pageState.popEmailData.customOptions"
+                    :key="item.CompanyId"
+                    :label="item.CompanyName"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+                <div class="user-box">
+                  <div style="margin-top: 10px" class="box">
+                    <!-- <el-tooltip 
+                      effect="dark" 
+                      :content="item.CompanyName" 
+                      placement="top"
+                      v-for="(item,index) in popEmailData.customValue"
+                      :key="item.CompanyId"
+                      > -->
+                    <el-tag
+                      closable
+                      v-for="(item, index) in pageState.popEmailData
+                        .customValue"
+                      :key="item.CompanyId"
+                      style="margin: 0 10px 10px 0"
+                      @close="handleDelSelectCompany(item, index)"
+                    >
+                      {{ item.CompanyName }}
+                    </el-tag>
+                    <!-- </el-tooltip> -->
+                  </div>
+                </div>
+              </div>
+              <div class="addressee-append-box">
+                <el-checkbox
+                  v-model="pageState.popEmailData.checkUser"
+                  style="margin: 0 0 10px 20px; width: 100px"
+                  >指定人员</el-checkbox
+                >
+                <div
+                  class="user-box-hint"
+                  v-show="pageState.popEmailData.checkUser"
+                >
+                  指定发送人员信息:
+                </div>
+                <el-select
+                  v-show="pageState.popEmailData.checkUser"
+                  v-model="pageState.popEmailData.value"
+                  multiple
+                  filterable
+                  collapse-tags
+                  placeholder="请选择人员"
+                  value-key="Id"
+                >
+                  <el-option
+                    v-for="item in pageState.popEmailData.options"
+                    :key="item.Id"
+                    :label="item.Name"
+                    :value="item"
+                    :disabled="item.Enabled === 0"
+                  >
+                    <span>{{ item.Name }} &lt;{{ item.Email }}&gt;</span>
+                  </el-option>
+                </el-select>
+                <div class="user-box" v-show="pageState.popEmailData.checkUser">
+                  <div style="margin-top: 10px" class="box">
+                    <el-tooltip
+                      effect="dark"
+                      :content="item.Email"
+                      placement="top"
+                      v-for="(item, index) in pageState.popEmailData.value"
+                      :key="item.Id"
+                    >
+                      <el-tag
+                        closable
+                        style="margin: 0 10px 10px 0"
+                        @close="handleDelSelectCustom(item, index)"
+                      >
+                        {{ item.Name }}
+                      </el-tag>
+                    </el-tooltip>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-form-item>
+          <div
+            style="text-align: center; margin-bottom: 30px; margin-top: 40px"
+          >
+            <el-button
+              type="primary"
+              plain
+              style="margin-right: 10px"
+              @click="pageState.popEmailData.show = false"
+              >取消</el-button
+            >
+            <el-button type="primary" @click="handleConfirmSendEmail"
+              >确定</el-button
+            >
+          </div>
+        </el-form>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+
+<style lang="scss">
+.video-manage-page {
+  .list-wrap {
+    .el-image:hover {
+      border: 1px solid #409eff;
+    }
+  }
+}
+.send-email-wrap {
+  .el-input {
+    width: 100%;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.video-manage-page {
+  .top-wrap {
+    height: 80px;
+    border: 1px solid #dcdfe6;
+    background-color: #fff;
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 30px;
+  }
+  .list-wrap {
+    border: 1px solid #dcdfe6;
+    background-color: #fff;
+    border-radius: 4px;
+    margin-top: 30px;
+    padding: 30px;
+    padding-bottom: 80px;
+    .nav-box {
+      margin-bottom: 30px;
+      span {
+        padding-bottom: 5px;
+        font-size: 16px;
+        display: inline-block;
+        margin-right: 30px;
+        cursor: pointer;
+      }
+      .active {
+        color: #409eff;
+        border-bottom: 2px solid #409eff;
+      }
+    }
+    .warn-tag {
+      width: 6px;
+      height: 6px;
+      border-radius: 50%;
+      background: #f00;
+      display: inline-block;
+      position: absolute;
+      right: -10px;
+      top: 30%;
+    }
+  }
+  // .pop-wrap{
+
+  // }
+  .choose-img-wrap {
+    padding-bottom: 80px;
+    .list {
+      display: flex;
+      flex-wrap: wrap;
+    }
+    .item {
+      flex-shrink: 0;
+      text-align: center;
+      margin-right: 20px;
+      margin-bottom: 20px;
+      border: 1px solid rgb(199, 198, 198);
+      padding: 5px;
+      border-radius: 4px;
+    }
+  }
+  .pre-img-box {
+    position: relative;
+    width: 120px;
+    height: 120px;
+    .del {
+      display: none;
+      color: #fff;
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      text-align: center;
+      line-height: 24px;
+      background-color: rgba($color: #000000, $alpha: 0.8);
+      cursor: pointer;
+    }
+    &:hover {
+      .del {
+        display: block;
+      }
+    }
+  }
+}
+.send-email-wrap {
+  .addressee-box {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    .addressee-cancel-box,
+    .addressee-append-box {
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      width: calc(50% - 12px);
+    }
+    .user-box-hint {
+      color: #999999;
+    }
+    .user-box {
+      border: 1px dashed #dcdfe6;
+      border-radius: 4px;
+      padding: 20px;
+      margin-top: 20px;
+      .box {
+        max-height: 200px;
+        overflow-y: auto;
+      }
+    }
+  }
+}
+</style>

+ 508 - 0
src/views/interaction_manage/videoManageENAdd.vue

@@ -0,0 +1,508 @@
+<script setup>
+import { reactive, ref } from 'vue'
+import MD5 from 'js-md5'
+import { customInterence, videoInterface, departInterence, getOSSSign } from '@/api/api.js'
+import * as classifyEnInterface from "@/api/modules/classifyEnApi.js";
+import { videoENInterface } from '@/api/modules/reportEnApi'
+import { ElMessage } from 'element-plus';
+import { useRouter, onBeforeRouteLeave } from 'vue-router';
+let ALOSSINS = null //阿里云上传实例
+let ALOSSAbortCheckpoint = null //阿里云上传实例中断点
+
+const router = useRouter()
+
+const pageState = reactive({
+  overviewConfig: {
+    toolbarButtons: [
+      'textColor',
+      'bold',
+      'italic',
+      'underline',
+      'strikeThrough',
+      'subscript',
+      'superscript',
+      'fontFamily',
+      'fontSize',
+      'color',
+      'inlineClass',
+      'inlineStyle',
+      'paragraphStyle',
+      'lineHeight',
+      'paragraphFormat',
+      'align',
+      'outdent',
+      'indent',
+      'quote',
+      'specialCharacters',
+      'insertHR',
+      'selectAll',
+      'clearFormatting',
+      'undo',
+      'redo',
+    ],
+    height: 300,
+    fontSizeDefaultSelection: "16",
+    quickInsertEnabled: false,
+    theme: "dark", //主题
+    placeholderText: '请输入overview',
+    language: "zh_cn",
+    //允许粘贴的样式
+    pasteAllowedStyleProps: ['font-family', 'font-size', 'color']
+  },
+  formData: {
+    id: 0,
+    title: '',
+    classify: [],
+    des: '',
+    videoUrl: '',
+    VideoSeconds: 0,//时长
+    imgUrl: '',
+    overview: '',
+    state: 1,
+  },
+  rules: {
+    title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+    classify: [{ required: true, message: '请选择标签', trigger: 'change' }],
+    des: [{ required: true, message: '请填写摘要', trigger: 'blur' }],
+
+    videoUrl: [{ required: true, message: '请上传视频', trigger: 'change' }],
+    imgUrl: [{ required: true, message: '请上传视频封面', trigger: 'change' }],
+    overview: [{ required: true, message: '请填写overview', trigger: 'blur' }],
+  },
+  options: [],
+
+  percentage: 0,
+  startUpload: false,//开始上传
+
+  chooseImgPop: false,
+  imgPopPage: 1,
+  imgPopPagesize: 20,
+  imgPopTotal: 0,
+  popImgList: [],
+})
+
+// 分类变化设置标题
+function handleClassifyChange(e) {
+  if (pageState.formData.id) return //编辑情况不改
+  let name = ''
+  pageState.options.forEach(item => {
+    if (item.Id == pageState.formData.classify[0]) {
+      item.Child.forEach(_item => {
+        if (_item.Id == pageState.formData.classify[1]) {
+          _item.Child.forEach(_it => {
+            if (_it.Id == pageState.formData.classify[2]) {
+              name = _it.ClassifyName
+            }
+          })
+        }
+      })
+    }
+  });
+  pageState.formData.title = 'Online Presentation:' + name
+
+}
+
+//获取视频时长的promise
+function handleGetDuration(file) {
+  return new Promise((resolve, reject) => {
+    const fileUrl = URL.createObjectURL(file)
+    const audioEl = new Audio(fileUrl)
+    audioEl.addEventListener('loadedmetadata', (e) => {
+      console.log('e.path', e.path)
+      console.log('e.composedPath', e.composedPath())
+      console.log('获取视频时长', e.composedPath()[0].duration);
+      console.log(audioEl.duration);
+      const t = e.composedPath()[0].duration
+      resolve(t)
+    })
+  })
+}
+
+//上传视频判断格式
+function handelBeforeUploadVideo(e) {
+  if (e.type != 'video/mp4') {
+    ElMessage.warning('上传失败,上传视频格式不正确')
+    return false
+  }
+}
+
+// 上传视频
+async function handleUpload(e) {
+  const duration = await handleGetDuration(e.file)
+  if (duration > 600 && pageState.navType === 1) {
+    ElMessage.warning('视频时长不得超过10分钟')
+    return
+  }
+  console.log(e);
+  pageState.formData.VideoSeconds = duration
+  const res = await getOSSSign()
+  if (res.Ret === 200) {
+    let accessKeyId = res.Data.AccessKeyId
+    let accessKeySecret = res.Data.AccessKeySecret
+    let stsToken = res.Data.SecurityToken
+    handleUploadToOSS(e.file, accessKeyId, accessKeySecret, stsToken)
+  }
+}
+
+//上传到阿里云
+async function handleUploadToOSS(file, accessKeyId, accessKeySecret, stsToken) {
+  pageState.startUpload = true
+  ALOSSINS = new OSS({
+    // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
+    region: "oss-cn-shanghai",
+    // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
+    accessKeyId: accessKeyId,
+    accessKeySecret: accessKeySecret,
+    // 从STS服务获取的安全令牌(SecurityToken)。
+    stsToken: stsToken,
+    // 填写Bucket名称,例如examplebucket。
+    bucket: "hzchart",
+    endpoint: 'hzstatic.hzinsights.com',
+    cname: true,
+    timeout: 600000000000
+  });
+  // 生成文件名
+  const t = new Date().getTime().toString()
+  const temName = `static/yb/en/video/${MD5(t)}.${file.type.split('/')[1]}`
+  console.log(temName);
+
+  const options = {
+    // 获取分片上传进度、断点和返回值。
+    progress: (p, cpt, res) => {
+      console.log(p);
+      ALOSSAbortCheckpoint = cpt
+      pageState.percentage = parseInt(p * 100)
+    },
+    // 设置并发上传的分片数量。
+    parallel: 10,
+    // 设置分片大小。默认值为1 MB,最小值为100 KB。
+    partSize: 1024 * 1024 * 10, // 10MB
+  };
+  try {
+    const res = await ALOSSINS.multipartUpload(temName, file, { ...options })
+    console.log('上传结果', res);
+    if (res.res.status === 200) {
+      pageState.formData.videoUrl = 'https://hzstatic.hzinsights.com/' + res.name
+      pageState.startUpload = false
+      pageState.percentage = 0
+      ALOSSAbortCheckpoint = null
+    }
+  } catch (error) {
+    console.log('上传到阿里云失败', error);
+    if (error.name !== "cancel") {//不是取消上传的则给错误提示
+      ElMessage.warning('上传失败,请刷新重试')
+    }
+    pageState.startUpload = false
+    pageState.percentage = 0
+    ALOSSAbortCheckpoint = null
+  }
+}
+
+onBeforeRouteLeave(() => {
+  if (ALOSSAbortCheckpoint) {
+    console.log('终止上传');
+    ALOSSINS.abortMultipartUpload(ALOSSAbortCheckpoint.name, ALOSSAbortCheckpoint.uploadId)
+  }
+})
+
+//显示选择视频封面列表
+function handleshowChooseImgPop() {
+  pageState.imgPopPage = 1
+  handleGetPopImgList()
+  pageState.chooseImgPop = true
+}
+
+//弹窗中选择视频封面的列表数据
+async function handleGetPopImgList() {
+  const res = await videoENInterface.videoCoverImgList({
+    PageSize: pageState.imgPopPagesize,
+    CurrentIndex: pageState.imgPopPage,
+  })
+  if (res.Ret === 200) {
+    pageState.imgPopTotal = res.Data.Paging.Totals
+    pageState.popImgList = res.Data.List || []
+  }
+}
+
+function handleChooseImg(item) {
+  pageState.formData.imgUrl = item.CoverUrl
+  pageState.chooseImgPop = false
+}
+
+//保存数据
+const form = ref(null)
+function handleSave(e) {
+  form.value.validate(async (valid) => {
+    if (!valid) return
+    let params = {
+      Id: pageState.formData.id,
+      // 传二级和三级分类
+      ClassifyIdFirst: pageState.formData.classify[1],
+      ClassifyIdSecond: pageState.formData.classify[2],
+      Title: pageState.formData.title,
+      Abstract: pageState.formData.des,
+      State: pageState.formData.state,//状态:1:未发布,2:已发布
+      VideoUrl: pageState.formData.videoUrl,
+      VideoCoverUrl: pageState.formData.imgUrl,
+      VideoSeconds: pageState.formData.VideoSeconds.toString(),
+      Overview: pageState.formData.overview.replace(/<p data-f-id=\"pbf\".*?<\/p>/g, '')
+    }
+
+    const res = await videoENInterface.roadVideoSave(params)
+    if (res.Ret === 200) {
+      if (e === 'publish') {
+        let res2 = await videoENInterface.roadVideoPublished({ Id: res.Data.Id })
+        if (res2.Ret === 200) {
+          ElMessage.success('发布成功')
+          router.back()
+        }
+      } else {
+        ElMessage.success('保存成功')
+        router.back()
+      }
+    }
+  })
+
+}
+
+//初始化
+function initPage() {
+  const obj = sessionStorage.getItem('videoENItem') ? JSON.parse(sessionStorage.getItem('videoENItem')) : ''
+  console.log(obj);
+  if (obj) {
+    pageState.formData.id = obj.Id
+    pageState.formData.title = obj.Title
+    pageState.formData.classify = [obj.ClassifyIdRoot, obj.ClassifyIdFirst, obj.ClassifyIdSecond]
+    pageState.formData.des = obj.Abstract
+    pageState.formData.videoUrl = obj.VideoUrl
+    pageState.formData.VideoSeconds = obj.VideoSeconds
+    pageState.formData.imgUrl = obj.VideoCoverUrl
+    pageState.formData.overview = obj.Overview
+    pageState.formData.state = obj.State
+  }
+  getClassify()
+}
+initPage()
+
+//获取分类数据
+async function getClassify() {
+  const res = await classifyEnInterface.classifyList({ CurrentIndex: 1, PageSize: 1000, ClassifyType: 1 })
+  if (res.Ret === 200) {
+    const arr = res.Data.List || []
+    pageState.options = arr.map(item => {
+      if (item.Child && item.Child.length > 0) {
+        item.Child.map(it => {
+          console.log(it);
+          it.disabled = it.Child ? false : true
+        })
+      }
+
+      return {
+        ...item,
+        disabled: item.Child ? false : true
+      }
+    })
+  }
+}
+
+
+</script>
+
+<template>
+  <div class="video-add-page">
+    <el-form
+      ref="form"
+      :model="pageState.formData"
+      label-width="80px"
+      :rules="pageState.rules"
+    >
+      <div style="display: flex">
+        <div style="width: 50%">
+          <el-form-item label="分类" prop="classify">
+            <el-cascader
+              style="width: 100%"
+              :options="pageState.options"
+              :show-all-levels="true"
+              :props="{
+                value: 'Id',
+                label: 'ClassifyName',
+                children: 'Child',
+              }"
+              v-model="pageState.formData.classify"
+              placeholder="请选择分类"
+              @change="handleClassifyChange"
+            ></el-cascader>
+          </el-form-item>
+          <el-form-item label="标题" prop="title">
+            <el-input
+              v-model="pageState.formData.title"
+              placeholder="请填写标题"
+            ></el-input>
+          </el-form-item>
+          <el-form-item label="摘要" prop="des">
+            <el-input
+              v-model="pageState.formData.des"
+              type="textarea"
+              :rows="2"
+              placeholder="请填写摘要"
+            ></el-input>
+          </el-form-item>
+        </div>
+        <div style="width: 50%">
+          <el-form-item label="视频" prop="videoUrl">
+            <div style="display: flex">
+              <el-input
+                readonly
+                v-model="pageState.formData.videoUrl"
+                placeholder="请上传视频"
+              ></el-input>
+              <el-upload
+                style="display: inline-block; margin-left: 5px"
+                action=""
+                accept=".mp4"
+                :http-request="handleUpload"
+                :before-upload="handelBeforeUploadVideo"
+                :show-file-list="false"
+                :disabled="pageState.startUpload"
+              >
+                <el-button type="primary" :loading="pageState.startUpload"
+                  >上传视频</el-button
+                >
+              </el-upload>
+              <el-progress
+                type="circle"
+                :percentage="pageState.percentage"
+                width="40"
+                style="margin-left: 10px"
+                v-if="pageState.startUpload"
+              ></el-progress>
+            </div>
+            <p style="color: #999">*注:要求视频格式为mp4,编码器为H.264</p>
+          </el-form-item>
+          <el-form-item label="封面" prop="imgUrl">
+            <el-image
+              style="width: 120px; height: 120px"
+              :src="require('@/assets/img/icons/add-img.png')"
+              v-if="!pageState.formData.imgUrl"
+              @click="handleshowChooseImgPop"
+            />
+            <div v-else class="pre-img-box">
+              <el-image
+                style="width: 120px; height: 120px"
+                :src="pageState.formData.imgUrl"
+                :preview-src-list="[pageState.formData.imgUrl]"
+              >
+              </el-image>
+              <span class="del" @click="pageState.formData.imgUrl = ''"
+                >删除</span
+              >
+            </div>
+          </el-form-item>
+        </div>
+      </div>
+      <el-form-item label="Overview" prop="overview">
+        <div id="leftoverview">
+          <froala
+            :id="`overview-editor`"
+            :style="{ display: 'none' }"
+            :ref="`overviewEditor`"
+            :tag="'textarea'"
+            :config="pageState.overviewConfig"
+            v-model="pageState.formData.overview"
+          >
+          </froala>
+        </div>
+      </el-form-item>
+      <div style="text-align: left; margin: 50px 0">
+        <el-button type="primary" @click="handleSave">保存</el-button>
+        <el-button type="primary" plain @click="handleSave('publish')"
+          >发布</el-button
+        >
+        <el-button @click="$router.back()">取消</el-button>
+      </div>
+    </el-form>
+
+    <!-- 选择视频封面弹窗 -->
+    <el-dialog
+      v-model="pageState.chooseImgPop"
+      :modal-append-to-body="false"
+      draggable
+      width="60vw"
+      title="视频封面库"
+    >
+      <div class="choose-img-wrap">
+        <div class="list">
+          <div
+            class="item"
+            v-for="item in pageState.popImgList"
+            :key="item.CommunityVideoCoverId"
+            @click="handleChooseImg(item)"
+          >
+            <el-image style="width: 120px; height: 120px" :src="item.CoverUrl">
+            </el-image>
+            <div>{{ item.CoverName }}</div>
+          </div>
+        </div>
+        <div>
+          <el-pagination
+            layout="total,prev,pager,next"
+            background
+            :current-page="pageState.imgPopPage"
+            @current-change="handleimgPopPageChange"
+            :page-size="pageState.imgPopPagesize"
+            :total="pageState.imgPopTotal"
+            style="float: right; margin-top: 20px"
+          >
+          </el-pagination>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.video-add-page {
+  border: 1px solid #dcdfe6;
+  background-color: #fff;
+  border-radius: 4px;
+  padding: 30px;
+  .choose-img-wrap {
+    padding-bottom: 80px;
+    .list {
+      display: flex;
+      flex-wrap: wrap;
+    }
+    .item {
+      flex-shrink: 0;
+      text-align: center;
+      margin-right: 20px;
+      margin-bottom: 20px;
+      border: 1px solid rgb(199, 198, 198);
+      padding: 5px;
+      border-radius: 4px;
+    }
+  }
+  .pre-img-box {
+    position: relative;
+    width: 120px;
+    height: 120px;
+    .del {
+      display: none;
+      color: #fff;
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      text-align: center;
+      line-height: 24px;
+      background-color: rgba($color: #000000, $alpha: 0.8);
+      cursor: pointer;
+    }
+    &:hover {
+      .del {
+        display: block;
+      }
+    }
+  }
+}
+</style>