index.vue 11 KB

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