index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <wrapper
  3. fill
  4. margin
  5. padding
  6. background
  7. horizontal
  8. >
  9. <device-tree
  10. class="c-sibling-item c-sidebar u-width--xl"
  11. highlight
  12. checkbox
  13. @change="onChange"
  14. @node-click="onDeviceChange"
  15. />
  16. <div class="l-flex__fill l-flex--col c-sibling-item far">
  17. <div class="l-flex__row l-flex--row c-sibling-item--v has-bottom-padding--sm has-bottom-border">
  18. <div
  19. class="o-button"
  20. @click="onBatchScheduling"
  21. >
  22. <i class="o-button__icon el-icon-edit" />
  23. 批量编单
  24. </div>
  25. </div>
  26. <schema-table
  27. v-if="deviceId"
  28. ref="table"
  29. class="c-sibling-item--v far"
  30. row-key="id"
  31. :schema="schema"
  32. />
  33. <el-empty
  34. v-else
  35. class="l-flex__auto l-flex--row center c-sibling-item--v"
  36. description="请选择设备"
  37. />
  38. </div>
  39. <dataset-config-dialog ref="datasetConfigDialog" />
  40. <table-dialog
  41. ref="tableDialog"
  42. :title="title"
  43. size="lg"
  44. :schema="schedulingSchema"
  45. />
  46. <table-dialog
  47. ref="contentDialog"
  48. title="上播内容"
  49. :schema="contentSchema"
  50. />
  51. <preview-dialog ref="previewDialog" />
  52. <confirm-dialog
  53. ref="schedulingOptionsDialog"
  54. title="一键编单"
  55. @confirm="onConfirm"
  56. >
  57. <div class="c-grid-form auto u-align-self--center">
  58. <div class="c-grid-form__label">编单日期范围</div>
  59. <el-date-picker
  60. v-model="dateRange"
  61. type="daterange"
  62. range-separator="至"
  63. value-format="yyyy-MM-dd"
  64. :picker-options="pickerOptions"
  65. :editable="false"
  66. :clearable="false"
  67. />
  68. </div>
  69. </confirm-dialog>
  70. </wrapper>
  71. </template>
  72. <script>
  73. import {
  74. ONE_DAY,
  75. TaskFromType,
  76. TaskFromTypeInfo,
  77. AssetTag,
  78. AssetTagInfo,
  79. AssetTypeInfo
  80. } from '@/constant'
  81. import {
  82. parseTime,
  83. parseDuration,
  84. transformOrderAssetToAsset
  85. } from '@/utils'
  86. import { getOrderDetail } from '../api'
  87. import {
  88. createScheduling,
  89. getDeviceSchedulings,
  90. getDeviceScheduling,
  91. setSchedulingEnable
  92. } from './api'
  93. const SchedulingStatus = {
  94. ERROR: 0,
  95. PROCESSING: 1,
  96. CREATED: 2,
  97. RESOLVED: 3
  98. }
  99. export default {
  100. name: 'AdScheduling',
  101. data () {
  102. const canEdit = this.$store.getters.isOperator
  103. const canAudit = this.$store.getters.isGroupAdmin
  104. return {
  105. devices: [],
  106. deviceId: '',
  107. schema: {
  108. autoRefresh: true,
  109. list: this.getDeviceSchedulings,
  110. condition: { order: 'startDate' },
  111. transform: this.transform,
  112. buttons: canEdit
  113. ? [
  114. { label: '填充素材包', icon: 'el-icon-edit', on: this.onDataset },
  115. { label: '一键排单', icon: 'el-icon-edit', on: this.onScheduling }
  116. ]
  117. : null,
  118. filters: [
  119. { key: 'order', type: 'select', options: [
  120. { value: 'startDate', label: '日期降序' },
  121. { value: 'createTime', label: '生成时间降序' }
  122. ] }
  123. ],
  124. cols: [
  125. { type: 'refresh' },
  126. { prop: 'startDate', label: '日期' },
  127. { prop: 'createTime', label: '生成时间' },
  128. { prop: 'statusTag', type: 'tag', on: (canEdit || canAudit) && this.onToggle },
  129. { prop: 'remark', label: '备注', 'align': 'right' },
  130. { type: 'invoke', render: [
  131. canAudit ? { label: '上架', render: ({ status, expired }) => !expired && status === SchedulingStatus.CREATED, on: this.onToggle } : null,
  132. canAudit ? { label: '下架', render: ({ status, expired }) => !expired && status === SchedulingStatus.RESOLVED, on: this.onToggle } : null,
  133. canEdit ? { label: '重编', render: ({ status, expired }) => !expired && status === SchedulingStatus.ERROR, on: this.onToggle } : null,
  134. { label: '详情', allow: ({ status }) => status !== SchedulingStatus.ERROR, on: this.onView }
  135. ].filter(Boolean) }
  136. ]
  137. },
  138. schedulingId: '',
  139. title: '',
  140. dateRange: [],
  141. contentSchema: {
  142. singlePage: true,
  143. list: this.getContentAssets,
  144. cols: [
  145. { prop: 'tagInfo', label: '类型', width: 100, align: 'center' },
  146. { prop: 'typeInfo', label: '资源', width: 72, align: 'center' },
  147. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  148. { prop: 'name', label: '' },
  149. { prop: 'adDuration', label: '上播时长', width: 100, align: 'center' },
  150. { type: 'invoke', render: [
  151. { label: '查看', allow: ({ file }) => !!file, on: this.onViewAsset }
  152. ] }
  153. ]
  154. }
  155. }
  156. },
  157. computed: {
  158. schedulingSchema () {
  159. return {
  160. list: getDeviceScheduling,
  161. transform: this.transformScheduling,
  162. condition: { id: this.schedulingId },
  163. cols: [
  164. { prop: 'fromInfo', type: 'refresh', width: 80, align: 'center' },
  165. { prop: 'dataTag', label: '类型', width: 100, align: 'center' },
  166. { prop: 'dataType', label: '', width: 80, align: 'center' },
  167. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  168. { prop: 'originalName', label: '' },
  169. { prop: 'time', label: '时间', 'align': 'center' },
  170. { prop: 'tag', type: 'tag' },
  171. { type: 'invoke', render: [
  172. { label: '查看', allow: ({ relationBOS }) => relationBOS?.length, on: this.onViewDetail }
  173. ] }
  174. ]
  175. }
  176. },
  177. pickerOptions () {
  178. return {
  179. disabledDate: this.isDisableDate
  180. }
  181. }
  182. },
  183. watch: {
  184. deviceId () {
  185. this.$refs.table?.pageTo(1)
  186. }
  187. },
  188. methods: {
  189. onChange (devices) {
  190. this.devices = devices
  191. },
  192. onDeviceChange (device) {
  193. this.deviceId = device?.id
  194. },
  195. getDeviceSchedulings (params) {
  196. return getDeviceSchedulings({
  197. deviceId: this.deviceId,
  198. ...params
  199. })
  200. },
  201. isDisableDate (date) {
  202. return date <= Date.now() - ONE_DAY
  203. },
  204. onDataset () {
  205. this.$refs.datasetConfigDialog.show({ id: this.deviceId })
  206. },
  207. onBatchScheduling () {
  208. if (this.devices.length === 0) {
  209. this.$message({
  210. type: 'warning',
  211. message: '请勾选需要编单的设备'
  212. })
  213. return
  214. }
  215. this.$deviceIds = this.devices.map(({ id }) => id)
  216. this.onConfigScheduling()
  217. },
  218. onScheduling () {
  219. this.$deviceIds = [this.deviceId]
  220. this.onConfigScheduling()
  221. },
  222. onConfigScheduling () {
  223. const date = parseTime(new Date(), '{y}-{m}-{d}')
  224. this.dateRange = [date, date]
  225. this.$refs.schedulingOptionsDialog.show()
  226. },
  227. onConfirm (done) {
  228. this.$confirm(
  229. '新生成的节目单将覆盖原有节目单但不影响已下发的数据,确认继续?',
  230. '一键排单',
  231. { type: 'warning' }
  232. ).then(() => this.createScheduling(this.dateRange[0], this.dateRange[1])).then(done)
  233. },
  234. createScheduling (startDate, endDate) {
  235. createScheduling(this.$deviceIds, {
  236. startDate,
  237. endDate: endDate || startDate
  238. }).then(() => {
  239. this.$refs.table.pageTo()
  240. })
  241. },
  242. transform (scheduling) {
  243. const expired = scheduling.startDate < parseTime(new Date(), '{y}-{m}-{d}')
  244. scheduling.expired = expired
  245. scheduling.statusTag = this.getStatusTag(scheduling.status, expired)
  246. return scheduling
  247. },
  248. getStatusTag (status, hasExpired) {
  249. return {
  250. label: ['生成失败', '生成中', hasExpired ? '未发布' : '待发布', hasExpired ? '已生效' : '生效中'][status],
  251. type: hasExpired ? null : ['danger', 'primary', 'warning', 'success'][status]
  252. }
  253. },
  254. onView ({ id, startDate, status }) {
  255. if (status === 0) {
  256. return
  257. }
  258. this.title = startDate
  259. this.schedulingId = id
  260. this.$refs.tableDialog.show()
  261. },
  262. transformScheduling (data) {
  263. const { from, startTime, endTime } = data
  264. data.fromInfo = TaskFromTypeInfo[from] || '未知'
  265. data.dataTag = this.getDataTag(data)
  266. data.dataType = this.getDataType(data)
  267. data.file = this.getDataFile(data)
  268. data.time = `${startTime}-${endTime}`
  269. data.tag = this.getTag(data)
  270. return data
  271. },
  272. getDataTag ({ from, relationBOS }) {
  273. switch (from) {
  274. case TaskFromType.ORDER:
  275. return AssetTagInfo[AssetTag.AD]
  276. case TaskFromType.FILL:
  277. case TaskFromType.ASSET:
  278. return relationBOS?.[0]?.detailId ? AssetTagInfo[relationBOS[0].tag] : '-'
  279. default:
  280. return '-'
  281. }
  282. },
  283. getDataType ({ from, relationBOS }) {
  284. switch (from) {
  285. case TaskFromType.FILL:
  286. case TaskFromType.ASSET:
  287. return relationBOS?.[0]?.detailId ? AssetTypeInfo[relationBOS[0].type] : '-'
  288. default:
  289. return '素材包'
  290. }
  291. },
  292. getDataFile ({ from, relationBOS }) {
  293. switch (from) {
  294. case TaskFromType.FILL:
  295. case TaskFromType.ASSET:
  296. return relationBOS?.[0]?.detailId && {
  297. type: relationBOS[0].type,
  298. url: relationBOS[0].keyName
  299. }
  300. default:
  301. return null
  302. }
  303. },
  304. getTag ({ relationBOS }) {
  305. return relationBOS?.length
  306. ? {
  307. type: 'success',
  308. label: '成功'
  309. }
  310. : {
  311. type: 'primary',
  312. label: '生成中'
  313. }
  314. },
  315. onViewDetail (detail) {
  316. switch (detail.from) {
  317. case TaskFromType.ORDER:
  318. case TaskFromType.CONTRACT:
  319. this.getContent(detail)
  320. break
  321. case TaskFromType.FILL:
  322. case TaskFromType.ASSET:
  323. this.onViewAsset(detail)
  324. break
  325. default:
  326. break
  327. }
  328. },
  329. getContent ({ from, fromId, relationBOS }) {
  330. if (fromId === this.$fromId) {
  331. this.$refs.contentDialog.show()
  332. return
  333. }
  334. switch (from) {
  335. case TaskFromType.ORDER:
  336. getOrderDetail(fromId).then(({ data }) => {
  337. this.showContentDialog(fromId, data.assets.map(transformOrderAssetToAsset))
  338. })
  339. break
  340. case TaskFromType.CONTRACT:
  341. this.showContentDialog(fromId, relationBOS.map(this.transformContractAsset))
  342. break
  343. default:
  344. break
  345. }
  346. },
  347. showContentDialog (fromId, assets) {
  348. this.$fromId = fromId
  349. if (assets.length === 1) {
  350. assets[0].adDuration = '独占'
  351. }
  352. this.$assets = assets
  353. this.$refs.contentDialog.show()
  354. },
  355. getContentAssets () {
  356. return Promise.resolve({ data: this.$assets })
  357. },
  358. transformContractAsset ({ detailId, tag, type, keyName, originalName, duration }) {
  359. if (!detailId) {
  360. return {
  361. tagInfo: '资源已删除'
  362. }
  363. }
  364. return {
  365. tagInfo: AssetTagInfo[tag],
  366. typeInfo: AssetTypeInfo[type],
  367. file: {
  368. type,
  369. url: keyName
  370. },
  371. name: originalName,
  372. adDuration: parseDuration(duration)
  373. }
  374. },
  375. onViewAsset ({ file }) {
  376. this.$refs.previewDialog.show(file)
  377. },
  378. onToggle (scheduling) {
  379. if (scheduling.status === SchedulingStatus.ERROR) {
  380. this.$confirm(
  381. '重新生成节目单?',
  382. { type: 'warning' }
  383. ).then(() => {
  384. this.createScheduling(scheduling.startDate)
  385. })
  386. return
  387. }
  388. if (scheduling.status === SchedulingStatus.PROCESSING) {
  389. return
  390. }
  391. const enable = scheduling.status === SchedulingStatus.CREATED
  392. setSchedulingEnable(scheduling, enable).then(() => {
  393. scheduling.status = enable ? SchedulingStatus.RESOLVED : SchedulingStatus.CREATED
  394. scheduling.statusTag = this.getStatusTag(scheduling.status)
  395. })
  396. }
  397. }
  398. }
  399. </script>