AssetTask.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. <template>
  2. <wrapper
  3. fill
  4. margin
  5. padding
  6. background
  7. horizontal
  8. >
  9. <schema-table
  10. ref="table"
  11. class="c-sibling-item far"
  12. row-key="id"
  13. :schema="schema"
  14. />
  15. <table-dialog
  16. ref="contentDialog"
  17. size="medium"
  18. title="上播内容"
  19. :schema="contentSchema"
  20. />
  21. <asset-task-dialog
  22. ref="taskDialog"
  23. @view="onViewAsset"
  24. @confirm="onSubmitAssetTask"
  25. />
  26. <radio-table-dialog
  27. ref="contractTableDialog"
  28. title="新增合同任务"
  29. message="请选择合同"
  30. :schema="contractTableSchema"
  31. @confirm="onSubmitContractTask"
  32. />
  33. <preview-dialog ref="previewDialog" />
  34. <confirm-dialog
  35. ref="timeDialog"
  36. title="上播时间设置"
  37. @confirm="onConfirmTime"
  38. >
  39. <div class="c-grid-form auto u-align-self--center">
  40. <div class="c-grid-form__label">上播方式</div>
  41. <schema-select
  42. v-model="taskTime.type"
  43. class="u-width"
  44. :schema="taskTimeTypeSelectSchema"
  45. />
  46. <template v-if="taskTime.type === 2">
  47. <div class="c-grid-form__label">时段</div>
  48. <el-time-picker
  49. key="task-range-picker"
  50. v-model="taskTime.range"
  51. class="u-width"
  52. is-range
  53. value-format="HH:mm:ss"
  54. :clearable="false"
  55. />
  56. </template>
  57. <template v-if="taskTime.type === 3">
  58. <div class="c-grid-form__label">时间点</div>
  59. <el-time-picker
  60. key="task-time-picker"
  61. v-model="taskTime.point"
  62. class="u-width"
  63. value-format="HH:mm:ss"
  64. :clearable="false"
  65. />
  66. </template>
  67. </div>
  68. </confirm-dialog>
  69. <multi-device-dialog
  70. ref="multiDeviceDialog"
  71. @confirm="onChangeDevices"
  72. />
  73. </wrapper>
  74. </template>
  75. <script>
  76. import {
  77. TimeType,
  78. SCREEN_TIME_KEY,
  79. TaskFromType,
  80. TaskFromTypeInfo,
  81. AssetType,
  82. AssetTypeInfo
  83. } from '@/constant'
  84. import {
  85. parseDuration,
  86. parseTaskTime,
  87. getTaskTimeInfo,
  88. transformToTaskTime,
  89. offsetDate,
  90. calculateDay,
  91. transformOrderAssetToAsset,
  92. transformContractAssetToAsset
  93. } from '@/utils'
  94. import {
  95. getOrderDetail,
  96. getContracts,
  97. getContract
  98. } from '../api'
  99. import {
  100. getDepartmentTasks,
  101. addDepartmentTask,
  102. deleteTask,
  103. updateTask
  104. } from './api'
  105. import AssetTaskDialog from './components/AssetTaskDialog.vue'
  106. export default {
  107. name: 'AdAssetTask',
  108. components: {
  109. AssetTaskDialog
  110. },
  111. data () {
  112. const canEdit = this.$store.getters.isOperator
  113. const canAudit = this.$store.getters.isGroupAdmin
  114. return {
  115. schema: {
  116. props: {
  117. size: 'small'
  118. },
  119. list: getDepartmentTasks,
  120. transform: this.transform,
  121. buttons: canEdit
  122. ? [
  123. { type: 'add', label: '文件资源任务', on: this.onAddAssetTask },
  124. { type: 'add', label: '流媒体任务', on: this.onAddStreamTask },
  125. { type: 'add', label: '合同任务', on: this.onAddContractTask }
  126. ]
  127. : null,
  128. cols: [
  129. { prop: 'type', type: 'refresh', width: 72 },
  130. { render: (data, h) => data.allowed
  131. ? h(
  132. 'div',
  133. {
  134. staticClass: 'u-ellipsis has-active',
  135. on: {
  136. click: () => this.onViewTask(data)
  137. }
  138. },
  139. data.name
  140. )
  141. : data.name },
  142. { label: '上刊设备', render: (data, h) => h(
  143. 'div',
  144. {
  145. staticClass: canEdit ? 'u-ellipsis has-active' : 'u-ellipsis',
  146. on: canEdit
  147. ? {
  148. click: () => this.onEditDevices(data)
  149. }
  150. : {}
  151. },
  152. data.devices.length > 1
  153. ? `${data.devices[0].name}等`
  154. : data.devices.length === 1
  155. ? data.devices[0].name
  156. : '暂未绑定'
  157. ), 'min-width': 100 },
  158. { label: '上刊日期', render: (data, h) => canEdit && data.allowed && data.from === TaskFromType.ASSET
  159. ? h('el-date-picker', {
  160. staticClass: 'o-date-picker',
  161. props: {
  162. value: [data.startDate, data.endDate],
  163. type: 'daterange',
  164. rangeSeparator: '至',
  165. valueFormat: 'yyyy-MM-dd',
  166. editable: false,
  167. clearable: false
  168. },
  169. on: {
  170. input: val => this.onDateEdit(data, val)
  171. }
  172. })
  173. : `${data.startDate} 至 ${data.endDate}`, width: 200, align: 'center' },
  174. { label: '上播时间', render: (data, h) => canEdit && data.allowed && data.from === TaskFromType.ASSET
  175. ? h('div', {
  176. staticClass: 'o-date-picker jc',
  177. on: {
  178. click: () => this.onEditTime(data)
  179. }
  180. }, data.range)
  181. : data.range, width: 160, align: 'center' },
  182. { label: '单次时长(s)', render: (data, h) => canEdit && this.canEditDuration(data)
  183. ? h('edit-input', {
  184. staticClass: 'sm border',
  185. props: {
  186. value: `${data.duration}`,
  187. align: 'center'
  188. },
  189. on: {
  190. edit: val => this.onSimpleEdit(data, 'duration', val)
  191. }
  192. })
  193. : data.duration, 'min-width': 96, align: 'center' },
  194. { label: '上播次数', render: (data, h) => canEdit && data.allowed && data.from === TaskFromType.ASSET
  195. ? h('edit-input', {
  196. staticClass: 'sm border',
  197. props: {
  198. value: `${data.count}`,
  199. align: 'center'
  200. },
  201. on: {
  202. edit: val => this.onSimpleEdit(data, 'count', val)
  203. }
  204. })
  205. : data.count, align: 'center' },
  206. { label: '审核次数', render: (data, h) => canAudit && data.allowed
  207. ? h('edit-input', {
  208. staticClass: 'sm border',
  209. props: {
  210. value: `${data.auditCount}`,
  211. align: 'center'
  212. },
  213. on: {
  214. edit: val => this.onAuditEdit(data, val)
  215. }
  216. })
  217. : '-', align: 'center' },
  218. { prop: 'tag', type: 'tag', size: 'sm', on: canAudit && this.onAudit, width: 80 },
  219. { type: 'invoke', render: [
  220. { label: '查看', allow: ({ allowed }) => allowed, on: this.onViewTask },
  221. { label: '删除', allow: ({ from }) => from !== TaskFromType.ORDER, on: this.onDel }
  222. ], width: 92 }
  223. ]
  224. },
  225. contentSchema: {
  226. singlePage: true,
  227. list: this.getContentAssets,
  228. cols: [
  229. { prop: 'tagInfo', label: '类型', width: 100, align: 'center' },
  230. { prop: 'typeInfo', label: '资源', width: 72, align: 'center' },
  231. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  232. { prop: 'name', label: '' },
  233. { prop: 'adDuration', label: '上播时长', align: 'center' },
  234. { type: 'invoke', render: [
  235. { label: '查看', allow: ({ file }) => !!file, on: this.onViewAsset }
  236. ] }
  237. ]
  238. },
  239. contractTableSchema: {
  240. list: getContracts,
  241. transform: this.transformContract,
  242. filters: [
  243. { key: 'name', type: 'search', placeholder: '合同名称' }
  244. ],
  245. cols: [
  246. { prop: 'name', label: '名称' },
  247. { prop: 'dateRange', label: '上刊日期', 'width': 200 },
  248. { prop: 'timeRange', label: '上播时间' },
  249. { prop: 'count', label: '上播次数' },
  250. { prop: 'durationInfo', label: '上播时长' }
  251. ]
  252. },
  253. taskTimeTypeSelectSchema: {
  254. options: [
  255. { value: TimeType.SCREEN, label: '开机期间' },
  256. { value: TimeType.RANGE, label: '时段' },
  257. { value: TimeType.POINT, label: '时间点' }
  258. ]
  259. },
  260. taskTime: {}
  261. }
  262. },
  263. methods: {
  264. canEditDuration ({ allowed, from, fromInfo }) {
  265. return allowed && from === TaskFromType.ASSET && fromInfo?.type === AssetType.IMAGE
  266. },
  267. transform (task) {
  268. const { id, devices, from, fromId, fromInfo, startDate, startTime, endTime, day, duration, count, auditCount, enable } = task
  269. const allowed = this.isValidTask(from, fromInfo)
  270. return {
  271. id,
  272. devices,
  273. from,
  274. fromId,
  275. fromInfo,
  276. type: this.getDataType(task),
  277. name: this.getDataName(task),
  278. startDate,
  279. endDate: offsetDate(startDate, day),
  280. startTime,
  281. endTime,
  282. range: getTaskTimeInfo({ startTime, endTime }),
  283. count,
  284. duration,
  285. auditCount,
  286. enable,
  287. tag: allowed
  288. ? this.getEnableTag(enable)
  289. : {
  290. type: 'danger',
  291. label: '异常',
  292. msg: '源数据丢失'
  293. },
  294. allowed
  295. }
  296. },
  297. isValidTask (from, fromInfo) {
  298. switch (from) {
  299. case TaskFromType.ORDER:
  300. return true
  301. default:
  302. return !!fromInfo
  303. }
  304. },
  305. getDataType ({ from, fromInfo }) {
  306. if (from === TaskFromType.ASSET && fromInfo) {
  307. return AssetTypeInfo[fromInfo.type]
  308. }
  309. return TaskFromTypeInfo[from]
  310. },
  311. getDataName ({ from, fromInfo }) {
  312. switch (from) {
  313. case TaskFromType.ORDER:
  314. return '自助广告'
  315. default:
  316. return fromInfo?.name || '-'
  317. }
  318. },
  319. getEnableTag (enable) {
  320. return {
  321. type: enable ? 'success' : 'primary',
  322. label: enable ? '已审核' : '待审核'
  323. }
  324. },
  325. transformContract (contract) {
  326. contract.durationInfo = parseDuration(contract.duration)
  327. contract.dateRange = `${contract.startDate} 至 ${offsetDate(contract.startDate, contract.day)}`
  328. contract.timeRange = getTaskTimeInfo(contract)
  329. return contract
  330. },
  331. onViewTask (task) {
  332. switch (task.from) {
  333. case TaskFromType.ORDER:
  334. getOrderDetail(task.fromId).then(({ data }) => {
  335. this.showContentDialog(data.assets.map(transformOrderAssetToAsset))
  336. })
  337. break
  338. case TaskFromType.ASSET:
  339. this.onViewAsset({
  340. file: {
  341. type: task.fromInfo.type,
  342. url: task.fromId
  343. }
  344. })
  345. break
  346. case TaskFromType.CONTRACT:
  347. getContract(task.fromId).then(({ data }) => {
  348. this.showContentDialog(data.assets.map(transformContractAssetToAsset))
  349. })
  350. break
  351. default:
  352. break
  353. }
  354. },
  355. showContentDialog (assets) {
  356. if (assets.length === 1) {
  357. assets[0].adDuration = '独占'
  358. }
  359. this.$assets = assets
  360. this.$refs.contentDialog.show()
  361. },
  362. getContentAssets () {
  363. return Promise.resolve({ data: this.$assets })
  364. },
  365. onViewAsset ({ file }) {
  366. this.$refs.previewDialog.show(file)
  367. },
  368. onDel ({ id, type, name }) {
  369. deleteTask(id, `${type} ${name}`).then(() => {
  370. this.$refs.table.decrease(1)
  371. })
  372. },
  373. onAddAssetTask () {
  374. this.$refs.taskDialog.show()
  375. },
  376. onAddStreamTask () {
  377. this.$refs.taskDialog.show(AssetType.STREAMING_MEDIA)
  378. },
  379. onSubmitAssetTask ({ value, done }) {
  380. this.addTask(value).then(done)
  381. },
  382. onAddContractTask () {
  383. this.$refs.contractTableDialog.show()
  384. },
  385. onSubmitContractTask ({ value, done }) {
  386. this.addTask({
  387. from: TaskFromType.CONTRACT,
  388. fromId: value.id,
  389. startDate: value.startDate,
  390. day: value.day,
  391. startTime: value.startTime,
  392. endTime: value.endTime,
  393. count: value.count,
  394. duration: value.duration
  395. }).then(done)
  396. },
  397. addTask (task) {
  398. return addDepartmentTask({
  399. ...task,
  400. auditCount: task.count,
  401. enable: false
  402. }).then(() => {
  403. this.$refs.table.pageTo(1)
  404. })
  405. },
  406. onAudit (task) {
  407. const { allowed, devices, enable, startTime, endTime, duration, auditCount } = task
  408. if (!allowed) {
  409. return
  410. }
  411. if (!devices.length) {
  412. this.$message({
  413. type: 'warning',
  414. message: '请先添加上刊设备'
  415. })
  416. return
  417. }
  418. if (!enable) {
  419. const totalDuration = duration * auditCount * 1000
  420. if (startTime && endTime && endTime !== SCREEN_TIME_KEY) {
  421. const remind = new Date(`2000-01-01 ${endTime}`) - new Date(`2000-01-01 ${startTime}`)
  422. if (remind < totalDuration) {
  423. this.$message({
  424. type: 'warning',
  425. message: '所选时段无法满足上播次数'
  426. })
  427. return
  428. }
  429. }
  430. }
  431. this.$confirm(
  432. enable ? '停止使用后将不可参与自动编单' : '启用后将可参与自动编单',
  433. enable ? '停用任务' : '启用任务',
  434. { type: 'warning' }
  435. ).then(() => {
  436. this.onUpdateEnable(task)
  437. })
  438. },
  439. onUpdateEnable (task) {
  440. const { id, enable } = task
  441. updateTask({
  442. id,
  443. enable: !enable
  444. }).then(() => {
  445. task.enable = !enable
  446. task.tag = this.getEnableTag(!enable)
  447. })
  448. },
  449. onSimpleEdit (task, key, { newVal, oldVal }) {
  450. if (!newVal || !/^\d+$/.test(newVal) || Number(newVal) < 1) {
  451. return
  452. }
  453. if (newVal !== oldVal) {
  454. task[key] = Number(newVal)
  455. updateTask({
  456. id: task.id,
  457. enable: false,
  458. [key]: newVal
  459. }).then(
  460. () => {
  461. if (task.enable) {
  462. task.enable = false
  463. task.tag = this.getEnableTag(false)
  464. }
  465. },
  466. () => {
  467. task[key] = oldVal
  468. }
  469. )
  470. }
  471. },
  472. onDateEdit (task, val) {
  473. const { id, enable, startDate, endDate } = task
  474. if (startDate !== val[0] || endDate !== val[1]) {
  475. const day = calculateDay(val[0], val[1]) + 1
  476. updateTask({
  477. id,
  478. enable: false,
  479. startDate: val[0],
  480. day
  481. }).then(
  482. () => {
  483. if (enable) {
  484. task.enable = false
  485. task.tag = this.getEnableTag(false)
  486. }
  487. task.startDate = val[0]
  488. task.endDate = val[1]
  489. task.day = day
  490. }
  491. )
  492. }
  493. },
  494. onAuditEdit (task, { newVal, oldVal }) {
  495. if (!newVal || !/^\d+$/.test(newVal) || Number(newVal) < 1) {
  496. return
  497. }
  498. if (newVal !== oldVal) {
  499. const auditCount = Number(newVal)
  500. if (task.from === TaskFromType.ORDER && auditCount < task.count) {
  501. this.$message({
  502. type: 'warning',
  503. message: '订单的审核次数不能小于上播次数'
  504. })
  505. return
  506. }
  507. if (task.from === TaskFromType.CONTRACT && auditCount < task.count) {
  508. this.$message({
  509. type: 'warning',
  510. message: '合同的审核次数不能小于上播次数'
  511. })
  512. return
  513. }
  514. task.auditCount = auditCount
  515. updateTask({
  516. id: task.id,
  517. enable: false,
  518. auditCount
  519. }).then(
  520. () => {
  521. if (task.enable) {
  522. task.enable = false
  523. task.tag = this.getEnableTag(false)
  524. }
  525. },
  526. () => {
  527. task.auditCount = oldVal
  528. }
  529. )
  530. }
  531. },
  532. onEditTime (task) {
  533. this.$task = task
  534. this.taskTime = parseTaskTime(task)
  535. this.$refs.timeDialog.show()
  536. },
  537. onConfirmTime (done) {
  538. const { id, enable, startTime, endTime } = this.$task
  539. const { startTime: targetStartTime, endTime: targetEndTime } = transformToTaskTime(this.taskTime)
  540. if (!targetStartTime) {
  541. this.$message({
  542. type: 'warning',
  543. message: '请选择上播时间'
  544. })
  545. return
  546. }
  547. if (startTime !== targetStartTime || endTime !== targetEndTime) {
  548. updateTask({
  549. id,
  550. enable: false,
  551. startTime: targetStartTime,
  552. endTime: targetEndTime
  553. }).then(
  554. () => {
  555. if (enable) {
  556. this.$task.enable = false
  557. this.$task.tag = this.getEnableTag(false)
  558. }
  559. this.$task.startTime = targetStartTime
  560. this.$task.endTime = targetEndTime
  561. this.$task.range = getTaskTimeInfo({ startTime: targetStartTime, endTime: targetEndTime })
  562. done()
  563. }
  564. )
  565. } else {
  566. done()
  567. }
  568. },
  569. onEditDevices (task) {
  570. if (!task.allowed) {
  571. return
  572. }
  573. this.$task = task
  574. this.$currDeviceMap = {}
  575. task.devices.forEach(({ id }) => {
  576. this.$currDeviceMap[id] = 1
  577. })
  578. this.$refs.multiDeviceDialog.show([...task.devices])
  579. },
  580. onChangeDevices ({ value, done }) {
  581. let change = false
  582. if (this.$task.enable) {
  583. if (value.length) {
  584. change = value.some(({ id }) => !this.$currDeviceMap[id])
  585. } else {
  586. change = true
  587. }
  588. }
  589. if (change && value.length) {
  590. this.$confirm(
  591. '新增上刊设备需重新审核,将影响其他设备的节目单生成',
  592. '操作确认',
  593. { type: 'warning' }
  594. ).then(() => this.onSubmitDevices(change, value)).then(done)
  595. return
  596. }
  597. this.onSubmitDevices(change, value).then(done)
  598. },
  599. onSubmitDevices (change, devices) {
  600. const data = {
  601. id: this.$task.id,
  602. deviceIdList: devices.map(({ id }) => id)
  603. }
  604. if (change) {
  605. data.enable = false
  606. }
  607. return updateTask(data).then(() => {
  608. this.$task.devices = devices
  609. if (change) {
  610. this.$task.enable = false
  611. this.$task.tag = this.getEnableTag(false)
  612. }
  613. })
  614. }
  615. }
  616. }
  617. </script>
  618. <style lang="scss" scoped>
  619. .c-task {
  620. line-height: 1;
  621. border-radius: $radius--sm;
  622. border: 1px solid #dcdfe6;
  623. &:hover {
  624. border-color: #c0c4cc;
  625. }
  626. &__name {
  627. position: absolute;
  628. width: 100%;
  629. height: 100%;
  630. }
  631. }
  632. ::v-deep .o-date-picker {
  633. display: flex;
  634. justify-content: space-between;
  635. align-items: center;
  636. width: auto;
  637. height: $height--sm;
  638. padding: 2px 0;
  639. line-height: $height--sm - 6px;
  640. border: 1px solid $primary;
  641. border-radius: $radius--sm;
  642. background-color: $blue--light;
  643. &.jc {
  644. justify-content: center;
  645. }
  646. &:hover {
  647. border-color: $gray--dark;
  648. }
  649. .el-range-input {
  650. flex: 1 1 auto;
  651. width: auto;
  652. min-width: 0;
  653. font-size: $font-size--xs;
  654. background-color: $blue--light;
  655. }
  656. .el-range-separator {
  657. flex: 0 0 auto;
  658. min-width: 0;
  659. width: auto;
  660. padding: 0;
  661. font-size: $font-size--xs;
  662. line-height: inherit;
  663. }
  664. .el-input__icon {
  665. display: none;
  666. }
  667. }
  668. </style>