index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. AssetType,
  65. AssetTypeInfo
  66. } from '@/constant'
  67. import {
  68. parseTime,
  69. parseDuration
  70. } from '@/utils'
  71. import { getOrderDetail } from '../api'
  72. import {
  73. createScheduling,
  74. getDeviceSchedulings,
  75. getDeviceScheduling,
  76. setSchedulingEnable
  77. } from './api'
  78. import DatasetConfigDialog from '../dataset/components/DatasetConfigDialog.vue'
  79. const SchedulingStatus = {
  80. ERROR: 0,
  81. PROCESSING: 1,
  82. CREATED: 2,
  83. RESOLVED: 3
  84. }
  85. export default {
  86. name: 'AdScheduling',
  87. components: {
  88. DatasetConfigDialog
  89. },
  90. data () {
  91. const canEdit = this.$store.getters.isOperator
  92. const canAudit = this.$store.getters.isGroupAdmin
  93. return {
  94. deviceId: '',
  95. schema: {
  96. buttons: canEdit
  97. ? [
  98. { label: '填充素材包', on: this.onDataset },
  99. { label: '一键排单', on: this.onScheduling }
  100. ]
  101. : null,
  102. condition: { order: 'startDate' },
  103. list: this.getDeviceSchedulings,
  104. transform: this.transform,
  105. filters: [
  106. { key: 'order', type: 'select', options: [
  107. { value: 'startDate', label: '日期降序' },
  108. { value: 'createTime', label: '生成时间降序' }
  109. ] }
  110. ],
  111. cols: [
  112. { type: 'refresh' },
  113. { prop: 'startDate', label: '日期' },
  114. { prop: 'createTime', label: '生成时间' },
  115. { prop: 'statusTag', type: 'tag', on: (canEdit || canAudit) && this.onToggle },
  116. { prop: 'remark', label: '备注', 'align': 'right' },
  117. { type: 'invoke', render: [
  118. canAudit ? { label: '上架', render: ({ status, expired }) => !expired && status === SchedulingStatus.CREATED, on: this.onToggle } : null,
  119. canAudit ? { label: '下架', render: ({ status, expired }) => !expired && status === SchedulingStatus.RESOLVED, on: this.onToggle } : null,
  120. canEdit ? { label: '重编', render: ({ status, expired }) => !expired && status === SchedulingStatus.ERROR, on: this.onToggle } : null,
  121. { label: '详情', allow: ({ status }) => status !== SchedulingStatus.ERROR, on: this.onView }
  122. ].filter(Boolean) }
  123. ]
  124. },
  125. schedulingId: '',
  126. title: '',
  127. minDate: '',
  128. dateRange: [],
  129. contentSchema: {
  130. list: this.getContentAssets,
  131. cols: [
  132. { prop: 'tagType', label: '文件', width: 100, align: 'center' },
  133. { prop: 'fileType', label: '', width: 80 },
  134. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  135. { prop: 'name', label: '' },
  136. { prop: 'adDuration', label: '上播时长', width: 100, align: 'center' },
  137. { type: 'invoke', render: [
  138. { label: '查看', allow: ({ file }) => !!file, on: this.onViewAsset }
  139. ] }
  140. ]
  141. }
  142. }
  143. },
  144. computed: {
  145. schedulingSchema () {
  146. return {
  147. list: getDeviceScheduling,
  148. transform: this.transformScheduling,
  149. condition: { id: this.schedulingId },
  150. cols: [
  151. { prop: 'fromInfo', type: 'refresh', width: 80, align: 'center' },
  152. { prop: 'dataTag', label: '类型', width: 80, align: 'center' },
  153. { prop: 'dataType', label: '', width: 80, align: 'center' },
  154. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  155. { prop: 'originalName', label: '' },
  156. { prop: 'time', label: '时间', 'align': 'center' },
  157. { prop: 'tag', type: 'tag' },
  158. { type: 'invoke', render: [
  159. { label: '查看', allow: ({ relationBOS }) => relationBOS?.length, on: this.onViewDetail }
  160. ] }
  161. ]
  162. }
  163. },
  164. pickerOptions () {
  165. return {
  166. disabledDate: this.isDisableDate
  167. }
  168. }
  169. },
  170. watch: {
  171. deviceId () {
  172. this.$refs.table?.pageTo(1)
  173. }
  174. },
  175. methods: {
  176. onChange ({ id }) {
  177. this.deviceId = id
  178. },
  179. getDeviceSchedulings (params) {
  180. return getDeviceSchedulings({
  181. deviceId: this.deviceId,
  182. ...params
  183. })
  184. },
  185. isDisableDate (date) {
  186. return date < this.minDate
  187. },
  188. onDataset () {
  189. this.$refs.datasetConfigDialog.show({ id: this.deviceId })
  190. },
  191. onScheduling () {
  192. const date = parseTime(new Date(), '{y}-{m}-{d}')
  193. this.minDate = new Date(`${date} 00:00:00`)
  194. this.dateRange = [date, date]
  195. this.$refs.schedulingOptionsDialog.show()
  196. },
  197. onConfirm (done) {
  198. this.$confirm(
  199. '新生成的节目单将覆盖原有节目单但不影响已下发的数据,确认继续?',
  200. '一键排单',
  201. { type: 'warning' }
  202. ).then(() => this.createScheduling(this.dateRange[0], this.dateRange[1])).then(done)
  203. },
  204. createScheduling (startDate, endDate) {
  205. createScheduling([this.deviceId], {
  206. startDate,
  207. endDate: endDate || startDate
  208. }).then(() => {
  209. this.$refs.table.pageTo()
  210. })
  211. },
  212. transform (scheduling) {
  213. const expired = scheduling.startDate < parseTime(new Date(), '{y}-{m}-{d}')
  214. scheduling.expired = expired
  215. scheduling.statusTag = this.getStatusTag(scheduling.status, expired)
  216. return scheduling
  217. },
  218. getStatusTag (status, hasExpired) {
  219. return {
  220. label: ['生成失败', '生成中', hasExpired ? '未发布' : '待发布', hasExpired ? '已生效' : '生效中'][status],
  221. type: hasExpired ? null : ['danger', 'primary', 'warning', 'success'][status]
  222. }
  223. },
  224. onView ({ id, startDate, status }) {
  225. if (status === 0) {
  226. return
  227. }
  228. this.title = startDate
  229. this.schedulingId = id
  230. this.$refs.tableDialog.show()
  231. },
  232. transformScheduling (data) {
  233. const { from, startTime, endTime } = data
  234. data.fromInfo = TaskFromTypeInfo[from] || '未知'
  235. data.dataTag = this.getDataTag(data)
  236. data.dataType = this.getDataType(data)
  237. data.file = this.getDataFile(data)
  238. data.time = `${startTime}-${endTime}`
  239. data.tag = this.getTag(data)
  240. return data
  241. },
  242. getDataTag ({ from, relationBOS }) {
  243. switch (from) {
  244. case TaskFromType.ORDER:
  245. return AssetTagInfo[AssetTag.AD]
  246. case TaskFromType.FILL:
  247. case TaskFromType.ASSET:
  248. return relationBOS?.[0] ? AssetTagInfo[relationBOS[0].tag] : '-'
  249. default:
  250. return '-'
  251. }
  252. },
  253. getDataType ({ from, relationBOS }) {
  254. switch (from) {
  255. case TaskFromType.FILL:
  256. case TaskFromType.ASSET:
  257. return relationBOS?.[0] ? AssetTypeInfo[relationBOS[0].type] : '-'
  258. default:
  259. return '素材包'
  260. }
  261. },
  262. getDataFile ({ from, relationBOS }) {
  263. switch (from) {
  264. case TaskFromType.FILL:
  265. case TaskFromType.ASSET:
  266. return relationBOS?.[0] && {
  267. type: relationBOS[0].type,
  268. url: relationBOS[0].keyName
  269. }
  270. default:
  271. return null
  272. }
  273. },
  274. getTag ({ relationBOS }) {
  275. return relationBOS?.length
  276. ? {
  277. type: 'success',
  278. label: '成功'
  279. }
  280. : {
  281. type: 'primary',
  282. label: '生成中'
  283. }
  284. },
  285. onViewDetail (detail) {
  286. switch (detail.from) {
  287. case TaskFromType.ORDER:
  288. case TaskFromType.CONTRACT:
  289. this.getContent(detail)
  290. break
  291. case TaskFromType.FILL:
  292. case TaskFromType.ASSET:
  293. this.onViewAsset(detail)
  294. break
  295. default:
  296. break
  297. }
  298. },
  299. getContent ({ from, fromId, relationBOS }) {
  300. if (fromId === this.$fromId) {
  301. this.$refs.contentDialog.show()
  302. return
  303. }
  304. switch (from) {
  305. case TaskFromType.ORDER:
  306. getOrderDetail(fromId).then(({ data }) => {
  307. this.showContentDialog(fromId, data.assets.map(this.transformOrderAsset))
  308. })
  309. break
  310. case TaskFromType.CONTRACT:
  311. this.showContentDialog(fromId, relationBOS.map(this.transformContractAsset))
  312. break
  313. default:
  314. break
  315. }
  316. },
  317. showContentDialog (fromId, assets) {
  318. this.$fromId = fromId
  319. if (assets.length === 1) {
  320. assets[0].adDuration = '独占'
  321. }
  322. this.$assets = assets
  323. this.$refs.contentDialog.show()
  324. },
  325. getContentAssets () {
  326. const assets = this.$assets
  327. return Promise.resolve({
  328. data: assets,
  329. totalCount: assets.length
  330. })
  331. },
  332. transformOrderAsset ({ type, keyName, adDuration, thumb }) {
  333. const isImage = type === AssetType.IMAGE
  334. return type
  335. ? {
  336. tagType: AssetTagInfo[AssetTag.AD],
  337. fileType: AssetTypeInfo[type],
  338. file: {
  339. type,
  340. url: keyName,
  341. thumb: isImage ? keyName : thumb,
  342. origin: !isImage
  343. },
  344. adDuration: parseDuration(adDuration)
  345. }
  346. : {
  347. tagType: '素材已删除',
  348. adDuration: parseDuration(adDuration)
  349. }
  350. },
  351. transformContractAsset ({ tag, type, keyName, originalName, duration }) {
  352. return {
  353. tagType: AssetTagInfo[tag],
  354. fileType: AssetTypeInfo[type],
  355. file: {
  356. type,
  357. url: keyName
  358. },
  359. name: originalName,
  360. adDuration: parseDuration(duration)
  361. }
  362. },
  363. onViewAsset ({ file }) {
  364. this.$refs.previewDialog.show(file)
  365. },
  366. onToggle (scheduling) {
  367. if (scheduling.status === SchedulingStatus.ERROR) {
  368. this.$confirm(
  369. '重新生成节目单?',
  370. { type: 'warning' }
  371. ).then(() => {
  372. this.createScheduling(scheduling.startDate)
  373. })
  374. return
  375. }
  376. if (scheduling.status === SchedulingStatus.PROCESSING) {
  377. return
  378. }
  379. const enable = scheduling.status === SchedulingStatus.CREATED
  380. setSchedulingEnable(scheduling, enable).then(() => {
  381. scheduling.status = enable ? SchedulingStatus.RESOLVED : SchedulingStatus.CREATED
  382. scheduling.statusTag = this.getStatusTag(scheduling.status)
  383. })
  384. }
  385. }
  386. }
  387. </script>