index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <template>
  2. <div class="l-flex--col">
  3. <div class="c-sibling-item--v has-bottom-padding--sm has-bottom-border">
  4. <div class="c-sibling-item--v u-bold">
  5. <span class="c-sibling-item u-font-size--sm">上播内容</span>
  6. <i
  7. v-if="isAssets"
  8. class="c-sibling-item el-icon-edit u-color--blue u-pointer"
  9. @click="onEditAssets"
  10. />
  11. </div>
  12. <div
  13. v-if="!simple"
  14. class="l-flex--row c-sibling-item--v near"
  15. >
  16. <el-select
  17. v-model="currentTarget.type"
  18. class="c-sibling-item"
  19. @change="onChangeTargetType"
  20. >
  21. <el-option
  22. v-for="option in eventTypeOptions"
  23. :key="option.value"
  24. :label="option.label"
  25. :value="option.value"
  26. />
  27. </el-select>
  28. <div
  29. v-if="isAssets"
  30. class="c-sibling-item o-button"
  31. @click="onImportDataset"
  32. >
  33. 导入
  34. </div>
  35. <div
  36. class="l-flex--row c-sibling-item far has-active u-ellipsis"
  37. @click="onViewCurrentTarget"
  38. >
  39. {{ targetInfo }}
  40. </div>
  41. </div>
  42. </div>
  43. <template v-if="isAssets">
  44. <draggable
  45. v-if="currentTarget.assets.length"
  46. v-model="currentTarget.assets"
  47. class="l-flex__fill c-sibling-item--v u-overflow-y--auto"
  48. handle=".mover"
  49. animation="300"
  50. >
  51. <draggable-item
  52. v-for="(asset, index) in currentTarget.assets"
  53. :key="asset.key"
  54. :item="asset"
  55. :hide-duration="hideDuration"
  56. @view="onViewAsset"
  57. @del="onDelAsset(index)"
  58. />
  59. </draggable>
  60. <el-empty
  61. v-else
  62. class="l-flex__fill l-flex--row center"
  63. description="请添加素材"
  64. />
  65. </template>
  66. <schema-table
  67. v-else
  68. :key="currentTarget.type"
  69. class="c-sibling-item--v"
  70. :schema="schema"
  71. row-key="id"
  72. :current-row-key="selectedId"
  73. highlight-current-row
  74. @row-click="onClickRow"
  75. />
  76. <confirm-dialog
  77. ref="assetTableDialog"
  78. title="上播内容"
  79. size="xl fixed"
  80. append-to-body
  81. @confirm="onSaveAssets"
  82. >
  83. <c-transfer
  84. class="l-flex__auto"
  85. :source-schema="assetTableSchema"
  86. :add="onAddAssets"
  87. >
  88. <el-empty
  89. v-if="!selectionAssets.length"
  90. class="l-flex__auto l-flex--row center"
  91. description="请添加素材"
  92. />
  93. <draggable
  94. v-else
  95. v-model="selectionAssets"
  96. class="l-flex__auto l-flex--col u-font-size--sm u-overflow-y--auto"
  97. handle=".mover"
  98. animation="300"
  99. >
  100. <draggable-item
  101. v-for="(asset, index) in selectionAssets"
  102. :key="asset.key"
  103. :item="asset"
  104. @view="onViewAsset"
  105. @del="onDelSelectionAsset(index)"
  106. />
  107. </draggable>
  108. </c-transfer>
  109. </confirm-dialog>
  110. <radio-table-dialog
  111. ref="datasetDialog"
  112. title="素材包"
  113. message="请选择素材包"
  114. :schema="datasetSchema"
  115. append-to-body
  116. @confirm="onChoosenDataset"
  117. />
  118. <table-dialog
  119. ref="contentDialog"
  120. title="素材包"
  121. :schema="contentSchema"
  122. append-to-body
  123. />
  124. <preview-dialog ref="previewDialog" />
  125. <material-dialog ref="materialDialog" />
  126. </div>
  127. </template>
  128. <script>
  129. import {
  130. State,
  131. ScheduleType,
  132. EventTarget,
  133. EventTargetInfo,
  134. AssetTagInfo,
  135. AssetType,
  136. AssetTypeInfo,
  137. Dataset
  138. } from '@/constant'
  139. import {
  140. parseDuration,
  141. getAssetThumb,
  142. getAssetDuration
  143. } from '@/utils'
  144. import { getRatios } from '@/api/device'
  145. import {
  146. getDatasets,
  147. getCommonDataset
  148. } from '@/api/asset'
  149. import { getSchedules } from '@/api/calendar'
  150. import { getPrograms } from '@/api/program'
  151. import { assetTableMixin } from '@/mixins/asset-table'
  152. import Draggable from 'vuedraggable'
  153. export default {
  154. name: 'EventTargetPicker',
  155. components: {
  156. Draggable
  157. },
  158. mixins: [assetTableMixin],
  159. props: {
  160. eventTarget: {
  161. type: Object,
  162. default: null
  163. },
  164. simple: {
  165. type: [Boolean, String],
  166. default: false
  167. },
  168. ratio: {
  169. type: String,
  170. default: ''
  171. }
  172. },
  173. data () {
  174. return {
  175. eventTypeOptions: [
  176. { value: EventTarget.PROGRAM, label: EventTargetInfo[EventTarget.PROGRAM] },
  177. { value: EventTarget.RECUR, label: EventTargetInfo[EventTarget.RECUR] },
  178. { value: EventTarget.ASSETS, label: EventTargetInfo[EventTarget.ASSETS] }
  179. ],
  180. currentTarget: null,
  181. selectionAssets: [],
  182. datasetSchema: {
  183. list: getDatasets,
  184. condition: { type: Dataset.COMMON },
  185. cols: [
  186. { prop: 'name', label: '名称', 'align': 'center' },
  187. { type: 'invoke', render: [
  188. { label: '查看', on: this.onViewDataset }
  189. ] }
  190. ]
  191. },
  192. contentSchema: {
  193. singlePage: true,
  194. list: this.getAssets,
  195. cols: [
  196. { prop: 'tagInfo', label: '类型', align: 'center', width: 80 },
  197. { prop: 'typeName', label: '文件', align: 'center', width: 80 },
  198. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  199. { prop: 'name', label: '' },
  200. { prop: 'duration', label: '上播时长', 'align': 'center', width: 100 },
  201. { type: 'invoke', render: [
  202. { label: '查看', on: this.onViewAsset }
  203. ] }
  204. ]
  205. }
  206. }
  207. },
  208. computed: {
  209. isAssets () {
  210. return this.currentTarget?.type === EventTarget.ASSETS
  211. },
  212. hideDuration () {
  213. return this.isAssets && this.currentTarget.assets.length <= 1
  214. },
  215. selectedId () {
  216. return this.currentTarget?.detail?.id
  217. },
  218. targetInfo () {
  219. if (this.isAssets) {
  220. return ''
  221. }
  222. return this.currentTarget?.detail
  223. ? this.currentTarget.detail.name || '未知'
  224. : ''
  225. },
  226. schema () {
  227. if (!this.currentTarget) {
  228. return {}
  229. }
  230. const { type } = this.currentTarget
  231. return {
  232. list: this.getListInvoke,
  233. condition: { resolutionRatio: this.ratio, name: '' },
  234. filters: [
  235. { key: 'resolutionRatio', type: 'select', placeholder: '分辨率', remote: getRatios },
  236. { key: 'name', type: 'search', placeholder: '名称' }
  237. ],
  238. cols: [
  239. { render: ({ id }, h) => h(
  240. 'span',
  241. { staticClass: `el-radio__input ${id === this.selectedId ? 'is-checked' : ''}` },
  242. [h('span', { staticClass: 'el-radio__inner' })]
  243. ), width: 60, align: 'center' },
  244. type === EventTarget.PROGRAM
  245. ? { label: '缩略图', type: 'asset', render ({ img }) {
  246. return img
  247. ? { thumb: img }
  248. : null
  249. }, on: this.onView }
  250. : null,
  251. { prop: 'name', label: '名称' },
  252. { prop: 'resolutionRatio', label: '分辨率' },
  253. { type: 'invoke', render: [
  254. { label: '查看', on: this.onView }
  255. ] }
  256. ]
  257. }
  258. }
  259. },
  260. watch: {
  261. eventTarget: {
  262. handler () {
  263. this.init()
  264. },
  265. immediate: true
  266. }
  267. },
  268. methods: {
  269. init () {
  270. const target = this.eventTarget
  271. switch (target?.type) {
  272. case EventTarget.PROGRAM:
  273. case EventTarget.RECUR:
  274. this.currentTarget = {
  275. type: target.type,
  276. detail: target,
  277. assets: []
  278. }
  279. break
  280. case EventTarget.ASSETS:
  281. this.currentTarget = {
  282. type: target.type,
  283. detail: null,
  284. assets: (target.sources || []).map(asset => {
  285. return {
  286. info: `${AssetTagInfo[asset.tag]} ${AssetTypeInfo[asset.type]}`,
  287. ...asset
  288. }
  289. })
  290. }
  291. break
  292. default:
  293. this.currentTarget = {
  294. type: EventTarget.PROGRAM,
  295. detail: null,
  296. assets: []
  297. }
  298. break
  299. }
  300. },
  301. onChangeTargetType () {
  302. this.currentTarget.detail = null
  303. this.currentTarget.assets = []
  304. },
  305. getListInvoke (params) {
  306. switch (this.currentTarget.type) {
  307. case EventTarget.RECUR:
  308. return getSchedules({
  309. type: ScheduleType.RECUR,
  310. status: State.AVAILABLE,
  311. ...params
  312. })
  313. default:
  314. return getPrograms({
  315. status: State.AVAILABLE,
  316. ...params
  317. })
  318. }
  319. },
  320. onView ({ id }) {
  321. switch (this.currentTarget.type) {
  322. case EventTarget.PROGRAM:
  323. this.$refs.materialDialog.showProgram(id)
  324. break
  325. case EventTarget.RECUR:
  326. this.$refs.materialDialog.showSchedule(id)
  327. break
  328. default:
  329. break
  330. }
  331. },
  332. onViewCurrentTarget () {
  333. this.onView(this.currentTarget.detail)
  334. },
  335. onClickRow (row) {
  336. this.currentTarget.detail = row
  337. },
  338. onEditAssets () {
  339. this.selectionAssets = this.currentTarget.assets.map(asset => {
  340. return {
  341. ...asset
  342. }
  343. })
  344. this.$refs.assetTableDialog.show()
  345. },
  346. onDelSelectionAsset (index) {
  347. this.selectionAssets.splice(index, 1)
  348. },
  349. onAddAssets (value) {
  350. value.forEach(item => {
  351. const { tag, type, keyName, originalName, size, md5, file } = item
  352. this.selectionAssets.push({
  353. key: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
  354. tag,
  355. type,
  356. keyName,
  357. size,
  358. md5,
  359. info: `${AssetTagInfo[tag]} ${AssetTypeInfo[type]}`,
  360. name: originalName,
  361. duration: getAssetDuration(item),
  362. disabled: type === AssetType.VIDEO,
  363. file
  364. })
  365. })
  366. return Promise.resolve()
  367. },
  368. onSaveAssets (done) {
  369. this.currentTarget.assets = this.selectionAssets.map(asset => {
  370. return {
  371. ...asset
  372. }
  373. })
  374. done()
  375. },
  376. onImportDataset () {
  377. this.$datasetAssets = null
  378. this.$refs.datasetDialog.show()
  379. },
  380. onViewDataset ({ id }) {
  381. this.$datasetId = id
  382. this.$datasetAssets = null
  383. this.$refs.contentDialog.show()
  384. },
  385. getAssets () {
  386. if (this.$datasetAssets) {
  387. return Promise.resolve({ data: this.$datasetAssets })
  388. }
  389. return getCommonDataset(this.$datasetId).then(({ data: { mediaList } }) => {
  390. this.$datasetAssets = mediaList.map(this.transformDatasetAsset)
  391. return {
  392. data: this.$datasetAssets
  393. }
  394. })
  395. },
  396. transformDatasetAsset (asset) {
  397. asset.tagInfo = AssetTagInfo[asset.tag]
  398. asset.typeName = AssetTypeInfo[asset.type]
  399. asset.file = {
  400. type: asset.type,
  401. url: asset.keyName,
  402. thumb: asset.minioData ? getAssetThumb(asset.minioData) : null
  403. }
  404. asset.name = asset.minioData?.originalName || '素材已删除'
  405. asset.duration = parseDuration(asset.adDuration)
  406. return asset
  407. },
  408. onChoosenDataset ({ value, done }) {
  409. if (this.$datasetId !== value.id) {
  410. this.$datasetId = value.id
  411. this.$datasetAssets = null
  412. }
  413. this.getAssets().then(({ data }) => {
  414. data.forEach(item => {
  415. const { tag, type, keyName, adDuration, name, file, minioData } = item
  416. if (minioData) {
  417. this.currentTarget.assets.push({
  418. key: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
  419. tag,
  420. type,
  421. keyName,
  422. size: minioData.size,
  423. md5: minioData.md5,
  424. info: `${AssetTagInfo[tag]} ${AssetTypeInfo[type]}`,
  425. name,
  426. duration: adDuration,
  427. disabled: type === AssetType.VIDEO,
  428. file
  429. })
  430. }
  431. })
  432. done()
  433. })
  434. },
  435. onDelAsset (index) {
  436. this.currentTarget.assets.splice(index, 1)
  437. },
  438. createEventTarget () {
  439. if (this.isAssets ? !this.currentTarget.assets.length : !this.selectedId) {
  440. this.$message({
  441. type: 'warning',
  442. message: '请选择上播内容'
  443. })
  444. return null
  445. }
  446. const { type, detail, assets } = this.currentTarget
  447. return type === EventTarget.PROGRAM
  448. ? {
  449. type,
  450. id: detail.id,
  451. name: detail.name,
  452. programUrl: `${detail.buckets}/${detail.itemConfigName}`
  453. }
  454. : type === EventTarget.RECUR
  455. ? {
  456. type,
  457. id: detail.id,
  458. name: detail.name
  459. }
  460. : {
  461. type,
  462. sources: assets.map(({ tag, type, name, keyName, duration, size, md5 }) => {
  463. return { tag, type, name, keyName, duration, size, md5 }
  464. })
  465. }
  466. },
  467. getSnapshot () {
  468. return this.currentTarget
  469. },
  470. getValue () {
  471. const eventTarget = this.createEventTarget()
  472. if (eventTarget && !this.isAssets && (!this.ratio || this.ratio !== this.currentTarget.detail.resolutionRatio)) {
  473. eventTarget.adaptive = 1
  474. }
  475. return eventTarget
  476. }
  477. }
  478. }
  479. </script>