ReviewPublish.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <template>
  2. <div class="l-flex__fill l-flex--col">
  3. <schema-table
  4. ref="table"
  5. class="l-flex__none c-sibling-item--v"
  6. :schema="schema"
  7. />
  8. <div class="l-flex__fill c-sibling-item--v u-overflow-y--auto">
  9. <div class="c-sibling-item--v u-font-size--sm u-color--black u-bold">目标设备</div>
  10. <div class="c-sibling-item--v near c-devices u-color--info">
  11. <div
  12. v-for="device in devices"
  13. :key="device.deviceId"
  14. class="l-flex__self u-ellipsis"
  15. >
  16. {{ device.deviceName }}
  17. </div>
  18. </div>
  19. <template v-if="targetContent">
  20. <div class="c-sibling-item--v u-font-size--sm u-color--black u-bold">上播内容</div>
  21. <template v-if="isProgram || isAssets">
  22. <schema-table
  23. class="c-sibling-item--v near"
  24. :schema="contentSchema"
  25. />
  26. <preview-dialog ref="previewDialog" />
  27. </template>
  28. <schedule
  29. v-else
  30. class="c-sibling-item--v"
  31. :schedule="targetContent"
  32. hide-header
  33. />
  34. </template>
  35. </div>
  36. <material-dialog ref="materialDialog" />
  37. <confirm-dialog
  38. ref="rejectDialog"
  39. title="驳回"
  40. @confirm="onConfirmReject"
  41. >
  42. <div class="c-grid-form u-align-self--center">
  43. <span class="c-grid-form__label">审核意见</span>
  44. <el-select
  45. v-model="review.type"
  46. placeholder="请选择"
  47. >
  48. <el-option
  49. v-for="option in reviewOptions"
  50. :key="option.label"
  51. :label="option.label"
  52. :value="option.value"
  53. />
  54. </el-select>
  55. <template v-if="review.type === 'reject'">
  56. <span class="c-grid-form__label u-required">原因</span>
  57. <el-input
  58. v-model.trim="review.reason"
  59. type="textarea"
  60. placeholder="请填写驳回原因"
  61. maxlength="50"
  62. :rows="3"
  63. resize="none"
  64. show-word-limit
  65. />
  66. </template>
  67. </div>
  68. </confirm-dialog>
  69. </div>
  70. </template>
  71. <script>
  72. import { mapGetters } from 'vuex'
  73. import {
  74. PublishTargetType,
  75. EventTarget,
  76. AssetTagInfo,
  77. AssetTypeInfo,
  78. Access,
  79. State,
  80. JUMP_REVIEW
  81. } from '@/constant'
  82. import {
  83. parseByte,
  84. parseDuration,
  85. getAssetThumb,
  86. getAssetDiff,
  87. getAIState
  88. } from '@/utils'
  89. import {
  90. resolveFirstLevel,
  91. rejectFirstLevel,
  92. resolveSecondLevel,
  93. rejectSecondLevel,
  94. resolveFinal,
  95. rejectFinal
  96. } from '../../api'
  97. import { transformCalendarRelease } from '../../utils'
  98. import mixin from './mixin'
  99. export default {
  100. name: 'WorkflowReviewPublish',
  101. mixins: [mixin],
  102. data () {
  103. return {
  104. solo: false,
  105. assetSchema: {
  106. nonPagination: true,
  107. list: this.getContentAssets,
  108. cols: [
  109. { prop: 'tagInfo', label: '类型', width: 80, align: 'center' },
  110. { prop: 'typeInfo', label: '资源', width: 72, align: 'center' },
  111. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  112. { prop: 'name', label: '' },
  113. { prop: 'adDuration', label: '上播时长', 'min-width': 60, align: 'center' },
  114. { type: 'invoke', render: [
  115. { label: '查看', allow: ({ file }) => !!file, on: this.onViewAsset }
  116. ] }
  117. ]
  118. },
  119. programAssetSchema: {
  120. nonPaination: true,
  121. list: this.getContentAssets,
  122. cols: [
  123. { prop: 'tagInfo', label: '类型', width: 80, align: 'center' },
  124. { prop: 'typeInfo', label: '资源', width: 72, align: 'center' },
  125. { prop: 'file', label: '', type: 'asset', on: this.onViewAsset },
  126. { prop: 'originalName', label: '' },
  127. { prop: 'ai', label: 'AI审核', type: 'tag' },
  128. { prop: 'size', label: '文件大小', align: 'right' },
  129. { prop: 'diff', label: '其他', align: 'center' },
  130. { type: 'invoke', render: [
  131. { label: '查看', on: this.onViewAsset }
  132. ] }
  133. ]
  134. },
  135. reviewOptions: [
  136. { value: 'reject', label: '驳回' },
  137. { value: '图文不符' },
  138. { value: '内容不合规' }
  139. ],
  140. review: {
  141. type: '',
  142. reason: ''
  143. }
  144. }
  145. },
  146. computed: {
  147. ...mapGetters(['access']),
  148. schema () {
  149. return {
  150. list: this.getList,
  151. cols: [
  152. { prop: 'priority', label: '优先级', width: 80, align: 'center' },
  153. { prop: 'priorityInfo', label: '', width: 80, align: 'center' },
  154. { prop: 'targetInfo', label: '上播内容', width: 80, align: 'center' },
  155. this.programScreenshot && { label: '缩略图', type: 'asset', render: () => {
  156. return {
  157. thumb: this.programScreenshot
  158. }
  159. }, on: this.onView },
  160. !this.isAssets && { prop: 'targetName', label: '' },
  161. { prop: 'desc', label: '上播时间', 'min-width': 160 },
  162. { type: 'invoke', render: [
  163. this.isProgram && { label: '查看', on: this.onView },
  164. { label: '通过', on: this.onResolve },
  165. { label: '驳回', on: this.onReject }
  166. ].filter(Boolean), width: this.isProgram ? 160 : 120 }
  167. ]
  168. }
  169. },
  170. contentSchema () {
  171. return this.isProgram ? this.programAssetSchema : this.assetSchema
  172. },
  173. devices () {
  174. return this.list?.[0].devices
  175. },
  176. status () {
  177. return this.workflow.status
  178. },
  179. isSecondLevel () {
  180. return this.status === State.FIRST_LEVEL
  181. },
  182. isFinalLevel () {
  183. return this.status === State.SECOND_LEVEL
  184. },
  185. resolveInvoke () {
  186. if (JUMP_REVIEW && this.access.has(Access.REVIEW_RELEASE_FINAL)) {
  187. return resolveFinal
  188. }
  189. switch (this.workflow.status) {
  190. case State.SUBMITTED:
  191. return resolveFirstLevel
  192. case State.FIRST_LEVEL:
  193. return resolveSecondLevel
  194. default:
  195. return resolveFinal
  196. }
  197. },
  198. rejectInvoke () {
  199. if (JUMP_REVIEW && this.access.has(Access.REVIEW_RELEASE_FINAL)) {
  200. return rejectFinal
  201. }
  202. switch (this.workflow.status) {
  203. case State.SUBMITTED:
  204. return rejectFirstLevel
  205. case State.FIRST_LEVEL:
  206. return rejectSecondLevel
  207. default:
  208. return rejectFinal
  209. }
  210. },
  211. publishTarget () {
  212. return this.list?.[0].target
  213. },
  214. isEvent () {
  215. return this.publishTarget?.type === PublishTargetType.EVENT
  216. },
  217. isProgram () {
  218. return this.isEvent && this.publishTarget.detail.target.type === EventTarget.PROGRAM
  219. },
  220. programScreenshot () {
  221. if (this.isProgram && this.workflow.items?.length) {
  222. return this.workflow.items[0].img
  223. }
  224. return null
  225. },
  226. isAssets () {
  227. return this.isEvent && this.publishTarget.detail.target.type === EventTarget.ASSETS
  228. },
  229. targetContent () {
  230. if (this.isEvent) {
  231. const target = this.publishTarget.detail.target
  232. let content = null
  233. switch (target.type) {
  234. case EventTarget.PROGRAM:
  235. content = this.workflow.minios?.map(this.transformProgramAsset)
  236. break
  237. case EventTarget.RECUR:
  238. content = target.id
  239. break
  240. case EventTarget.ASSETS:
  241. content = target.sources.map(this.transformAsset)
  242. if (content.length === 1) {
  243. content[0].adDuration = '独占'
  244. }
  245. break
  246. default:
  247. break
  248. }
  249. return content
  250. }
  251. return this.publishTarget?.detail
  252. }
  253. },
  254. created () {
  255. this.list = [this.transformItem(this.workflow.calendarReleaseScheduling)]
  256. },
  257. methods: {
  258. transformItem (item) {
  259. return transformCalendarRelease(item, {
  260. ignoreDeviceTransform: true
  261. })
  262. },
  263. transformProgramAsset (asset) {
  264. return {
  265. tagInfo: AssetTagInfo[asset.tag],
  266. typeInfo: AssetTypeInfo[asset.type],
  267. file: {
  268. type: asset.type,
  269. url: asset.keyName,
  270. thumb: getAssetThumb(asset),
  271. files: (asset.childrenData || []).map(({ type, keyName }) => {
  272. return { type, url: keyName }
  273. })
  274. },
  275. originalName: asset.originalName,
  276. ai: getAIState(asset),
  277. size: parseByte(asset.size),
  278. diff: getAssetDiff(asset)
  279. }
  280. },
  281. onView ({ target }) {
  282. this.$refs.materialDialog.showPublishTarget(target)
  283. },
  284. onViewAsset ({ file }) {
  285. this.$refs.previewDialog.show(file)
  286. },
  287. transformAsset ({ tag, type, keyName, name, duration }) {
  288. return {
  289. tagInfo: AssetTagInfo[tag],
  290. typeInfo: AssetTypeInfo[type],
  291. file: {
  292. type,
  293. url: keyName
  294. },
  295. name,
  296. adDuration: parseDuration(duration)
  297. }
  298. },
  299. getContentAssets () {
  300. return Promise.resolve({ data: this.targetContent })
  301. },
  302. onResolve () {
  303. this.$confirm(
  304. '审核通过?',
  305. '操作确认',
  306. { type: 'warning' }
  307. ).then(() => {
  308. this.resolveInvoke(this.workflow.workflowId).then(() => {
  309. this.$emit('finish')
  310. })
  311. })
  312. },
  313. onReject () {
  314. this.review = {
  315. type: 'reject',
  316. reason: ''
  317. }
  318. this.$refs.rejectDialog.show()
  319. },
  320. onConfirmReject (done) {
  321. const reason = this.review.type === 'reject' ? this.review.reason : this.review.type
  322. if (!reason) {
  323. this.$message({
  324. type: 'warning',
  325. message: '请选择或填写驳回原因'
  326. })
  327. return
  328. }
  329. this.rejectInvoke(this.workflow.workflowId, reason).then(() => {
  330. done()
  331. this.$message({
  332. type: 'success',
  333. message: '流程驳回成功'
  334. })
  335. this.$router.replace({ name: 'workflow-list' })
  336. })
  337. }
  338. }
  339. }
  340. </script>
  341. <style lang="scss" scoped>
  342. .c-devices {
  343. display: flex;
  344. flex-wrap: wrap;
  345. gap: $spacing--xs;
  346. }
  347. </style>