index.vue 11 KB

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