upload.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <template>
  2. <v-row justify="center" align="center">
  3. <v-col>
  4. <v-card
  5. class="mx-auto"
  6. outlined
  7. >
  8. <v-row justify="center">
  9. <v-col cols="10">
  10. <h2>发布视频贴</h2>
  11. </v-col>
  12. </v-row>
  13. <v-row justify="center">
  14. <v-col>
  15. <uploader
  16. class="uploader-example"
  17. :options="options"
  18. :auto-start="true"
  19. @file-added="onFileAdded"
  20. @file-success="onFileSuccess"
  21. @file-progress="onFileProgress"
  22. @file-error="onFileError"
  23. >
  24. <uploader-unsupport />
  25. <uploader-drop>
  26. <p>拖动视频文件到此处或</p>
  27. <uploader-btn :attrs="attrs">选择视频文件</uploader-btn>
  28. </uploader-drop>
  29. <uploader-list />
  30. </uploader>
  31. </v-col>
  32. </v-row>
  33. <v-row justify="center">
  34. <v-col cols="5">
  35. <v-card outlined>
  36. <v-img :src="videoPost.imageUrl" aspect-ratio="1.77" contain max-height="150" alt="封面图,推荐16:9" />
  37. </v-card>
  38. </v-col>
  39. <v-col cols="5">
  40. <v-file-input
  41. :rules="rules"
  42. accept="image/png, image/jpeg, image/bmp"
  43. prepend-icon="mdi-image"
  44. placeholder="上传视频封面"
  45. label="封面"
  46. @change="setFile"
  47. />
  48. <v-btn color="primary" @click="uploadVideoCover">
  49. 上传
  50. </v-btn>
  51. </v-col>
  52. </v-row>
  53. <v-divider />
  54. <v-row justify="center">
  55. <v-col cols="10">
  56. <h2>稿件信息</h2>
  57. </v-col>
  58. </v-row>
  59. <v-row justify="center">
  60. <v-col cols="5">
  61. <v-select
  62. :items="category"
  63. label="分区"
  64. @change="getCategory"
  65. />
  66. </v-col>
  67. <v-col cols="5">
  68. <v-select
  69. :items="childCategory"
  70. label="子分区"
  71. @change="getChildCategory"
  72. />
  73. </v-col>
  74. </v-row>
  75. <v-row justify="center">
  76. <v-col cols="10">
  77. <v-text-field
  78. v-model="videoPost.title"
  79. placeholder="标题"
  80. label="标题(50字以内)"
  81. clearable
  82. />
  83. </v-col>
  84. </v-row>
  85. <v-row justify="center">
  86. <v-col cols="10">
  87. <v-textarea
  88. v-model="videoPost.description"
  89. label="简介(200字以内)"
  90. clearable
  91. placeholder="填写更全面的视频信息,让更多的人找到你!"
  92. />
  93. </v-col>
  94. </v-row>
  95. <v-row justify="center">
  96. <v-col cols="10">
  97. <v-combobox
  98. v-model="videoPost.tags"
  99. label="添加标签让更多人找到你(最多6个)"
  100. multiple
  101. chips
  102. clearable
  103. />
  104. </v-col>
  105. </v-row>
  106. <v-row justify="center">
  107. <v-col cols="10">
  108. <v-select
  109. :items="scope"
  110. label="可见范围"
  111. @change="setVideoScope"
  112. />
  113. </v-col>
  114. </v-row>
  115. <v-row justify="center">
  116. <v-col cols="10">
  117. <v-btn large color="primary" @click="publish">立即投稿</v-btn>
  118. </v-col>
  119. </v-row>
  120. </v-card>
  121. </v-col>
  122. <v-snackbar
  123. v-model="showMessage"
  124. :top="true"
  125. :timeout="3000"
  126. >
  127. {{ message }}
  128. <template v-slot:action="{ attrs }">
  129. <v-btn
  130. color="pink"
  131. text
  132. v-bind="attrs"
  133. @click="showMessage = false"
  134. >
  135. 关闭
  136. </v-btn>
  137. </template>
  138. </v-snackbar>
  139. </v-row>
  140. </template>
  141. <script>
  142. import { videoCategory, submitVideoPost } from '@/api/media/video'
  143. /* import { hashFile } from '@/utils/hash' */
  144. export default {
  145. data() {
  146. return {
  147. options: {
  148. target: '//file.reghao.cn' + '/api/file/upload/video',
  149. chunkSize: 1024 * 1024 * 1024 * 5, // 分片大小 5GiB
  150. forceChunkSize: true,
  151. fileParameterName: 'file',
  152. maxChunkRetries: 3,
  153. testChunks: true,
  154. checkChunkUploadedByResponse: function(chunk, message) {
  155. const objMessage = JSON.parse(message)
  156. console.log('分片文件检验')
  157. console.log(objMessage)
  158. if (objMessage.skipUpload) {
  159. return true
  160. }
  161. return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
  162. },
  163. headers: {
  164. Authorization: 'Bearer ' + this.$store.getters.token
  165. }
  166. },
  167. attrs: {
  168. accept: 'video/*'
  169. },
  170. rules: [
  171. value => !value || value.size < 2000000 || 'Avatar size should be less than 2 MB!'
  172. ],
  173. // 提交给后端的数据
  174. videoPost: {
  175. videoFileId: null,
  176. coverFileId: null,
  177. title: null,
  178. description: null,
  179. categoryId: null,
  180. tags: [],
  181. scope: null
  182. },
  183. categoryMap: {
  184. Set: function(key, value) { this[key] = value },
  185. Get: function(key) { return this[key] },
  186. Contains: function(key) { return this.Get(key) !== null },
  187. Remove: function(key) { delete this[key] }
  188. },
  189. category: [],
  190. childCategory: [],
  191. scope: [
  192. '所有人可见',
  193. '验证码可见',
  194. 'VIP 可见',
  195. '仅自己可见'
  196. ],
  197. nowCategory: {},
  198. coverFile: null,
  199. showMessage: false,
  200. message: ''
  201. }
  202. },
  203. created() {
  204. this.getVideoCategory()
  205. },
  206. methods: {
  207. onFileAdded(file) {
  208. this.setTitle(file.file.name)
  209. /* file.pause()
  210. hashFile(file.file).then(res => {
  211. const formData = new FormData()
  212. formData.append('filename', file.file.name)
  213. formData.append('size', file.file.size)
  214. formData.append('sha256sum', res.sha256sum)
  215. fetch(`//file.reghao.cn` + `/api/file/upload/video/prepare`, {
  216. headers: {
  217. Authorization: 'Bearer ' + this.$store.getters.token
  218. },
  219. method: 'POST',
  220. credentials: 'include',
  221. body: formData
  222. }).then(response => response.json())
  223. .then(json => {
  224. const uploadId = json.data.uploadId
  225. const exist = json.data.exist
  226. if (exist) {
  227. this.message = '视频已存在'
  228. this.showMessage = true
  229. file.cancel()
  230. } else {
  231. file.uniqueIdentifier = uploadId
  232. file.resume()
  233. }
  234. })
  235. .catch(e => {
  236. return null
  237. })
  238. })*/
  239. },
  240. onFileProgress(rootFile, file, chunk) {
  241. },
  242. onFileSuccess(rootFile, file, response, chunk) {
  243. const res = JSON.parse(response)
  244. if (res.code === 0) {
  245. const resData = res.data
  246. if (resData.merged) {
  247. this.message = '视频已上传'
  248. this.showMessage = true
  249. this.videoPost.videoFileId = resData.videoFileId
  250. }
  251. }
  252. },
  253. onFileError(rootFile, file, response, chunk) {
  254. console.log('文件上传错误')
  255. },
  256. publish() {
  257. if (!this.videoPost.videoFileId) {
  258. this.message = '你还没有上传视频'
  259. this.showMessage = true
  260. return
  261. }
  262. if (!this.videoPost.coverFileId) {
  263. this.message = '你还没有上传视频封面'
  264. this.showMessage = true
  265. return
  266. }
  267. if (this.videoPost.title === '' || this.videoPost.categoryId === -1) {
  268. this.message = '分区和稿件标题不能为空'
  269. this.showMessage = true
  270. return
  271. }
  272. if (this.videoPost.scope === null) {
  273. this.message = '稿件可见范围不能为空'
  274. this.showMessage = true
  275. return
  276. }
  277. if (this.videoPost.tags.length === 0 || this.videoPost.tags.length > 10) {
  278. this.message = '标签最少 1 个, 最多 10 个'
  279. this.showMessage = true
  280. return
  281. }
  282. submitVideoPost(this.videoPost)
  283. .then(res => {
  284. if (res.code === 0) {
  285. this.message = '投稿成功,等待审核通过后其他人就可以看到你的视频了'
  286. this.showMessage = true
  287. this.$router.push('/studio')
  288. } else {
  289. this.message = res.msg
  290. this.showMessage = true
  291. }
  292. })
  293. .catch(error => {
  294. console.error(error.message)
  295. })
  296. },
  297. setFile(value) {
  298. this.coverFile = value
  299. },
  300. setTitle(title) {
  301. if (title.length > 50) {
  302. this.videoPost.title = title.substring(0, 50)
  303. } else {
  304. this.videoPost.title = title
  305. }
  306. },
  307. uploadVideoCover() {
  308. if (this.coverFile === null) {
  309. this.message = '请先选择视频封面,然后上传!'
  310. this.showMessage = true
  311. return
  312. }
  313. if (this.videoPost.videoFileId === null) {
  314. this.message = '等待视频上传完成后再上传封面!'
  315. this.showMessage = true
  316. return
  317. }
  318. const formData = new FormData()
  319. formData.append('videoFileId', this.videoPost.videoFileId)
  320. formData.append('file', this.coverFile)
  321. fetch(`//file.reghao.cn/api/file/upload/video/cover`, {
  322. headers: {
  323. 'Authorization': 'Bearer ' + this.$store.getters.token
  324. },
  325. method: 'POST',
  326. credentials: 'include',
  327. body: formData
  328. }).then(response => response.json())
  329. .then(json => {
  330. if (json.code === 0) {
  331. this.message = '封面已上传'
  332. this.showMessage = true
  333. this.videoPost.coverFileId = json.data.imageFileId
  334. this.videoPost.imageUrl = json.data.imageUrl
  335. } else {
  336. this.message = '上传失败,请重试!' + json.message
  337. this.showMessage = true
  338. }
  339. })
  340. .catch(e => {
  341. return null
  342. })
  343. },
  344. getVideoCategory() {
  345. videoCategory()
  346. .then(res => {
  347. if (res.code === 0) {
  348. for (let i = 0; i < res.data.length; i++) {
  349. const name = res.data[i].name
  350. this.category.push(name)
  351. this.categoryMap.Set(name, res.data[i])
  352. }
  353. } else {
  354. console.error(res.msg)
  355. }
  356. })
  357. .catch(error => {
  358. console.error(error.message)
  359. })
  360. },
  361. getCategory(name) {
  362. // 重置子分区,清除前一次选择分区时留下的缓存
  363. this.childCategory = []
  364. this.currentCategory = this.categoryMap.Get(name)
  365. this.videoPost.categoryId = this.currentCategory.id
  366. const c = this.currentCategory.children
  367. if (c) {
  368. for (let i = 0; i < c.length; i++) {
  369. this.childCategory.push(c[i].name)
  370. }
  371. }
  372. },
  373. getChildCategory(name) {
  374. const c = this.currentCategory.children
  375. for (let i = 0; i < c.length; i++) {
  376. if (c[i].name === name) {
  377. this.videoPost.categoryId = c[i].id
  378. }
  379. }
  380. },
  381. setVideoScope(scope) {
  382. if (scope === '所有人可见') {
  383. this.videoPost.scope = 1
  384. } else if (scope === '验证码可见') {
  385. this.videoPost.scope = 2
  386. } else if (scope === 'VIP 可见') {
  387. this.videoPost.scope = 3
  388. } else if (scope === '仅自己可见') {
  389. this.videoPost.scope = 4
  390. }
  391. }
  392. }
  393. }
  394. </script>
  395. <style>
  396. .uploader-example {
  397. width: 500px;
  398. padding: 15px;
  399. margin: 40px auto 0;
  400. font-size: 12px;
  401. box-shadow: 0 0 10px rgba(0, 0, 0, .4);
  402. }
  403. .uploader-example .uploader-btn {
  404. margin-right: 4px;
  405. }
  406. .uploader-example .uploader-list {
  407. max-height: 440px;
  408. overflow: auto;
  409. overflow-x: hidden;
  410. overflow-y: auto;
  411. }
  412. </style>