index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <template>
  2. <div
  3. class="c-grid-form medium c-event"
  4. :class="{ row: !vertical, col: vertical }"
  5. >
  6. <div class="c-grid-form__label required">播放内容</div>
  7. <div class="c-event__option c-event__program u-pointer">
  8. <div
  9. class="c-event__name has-padding--h"
  10. @click="onChoose"
  11. >
  12. <div class="u-ellipsis">{{ msg }}</div>
  13. </div>
  14. </div>
  15. <div class="c-grid-form__label">播放方式</div>
  16. <div>
  17. <el-select
  18. v-model="eventOptions.freq"
  19. class="c-event__option"
  20. @change="onEventTypeChanged"
  21. >
  22. <el-option
  23. v-for="option in freqOptions"
  24. :key="option.value"
  25. :label="option.label"
  26. :value="option.value"
  27. />
  28. </el-select>
  29. </div>
  30. <template v-if="isOnce">
  31. <div class="c-grid-form__label required">生效时间</div>
  32. <el-date-picker
  33. v-model="eventOptions.start"
  34. class="c-event__option"
  35. type="datetime"
  36. placeholder="请选择生效时间"
  37. value-format="yyyy-MM-dd HH:mm:ss"
  38. :picker-options="pickerOptions"
  39. @change="onDateTimeChange('start')"
  40. />
  41. <div class="c-grid-form__label required">失效时间</div>
  42. <el-date-picker
  43. v-model="eventOptions.until"
  44. class="c-event__option"
  45. type="datetime"
  46. :disabled="!eventOptions.start"
  47. placeholder="请选择失效时间"
  48. value-format="yyyy-MM-dd HH:mm:ss"
  49. :picker-options="endPickerOptions"
  50. @change="onDateTimeChange('until')"
  51. />
  52. </template>
  53. <template v-if="isWeekly">
  54. <div class="c-grid-form__label c-grid-form__auto required">每周</div>
  55. <el-checkbox-group
  56. v-model="eventOptions.byDay"
  57. class="c-grid-form__auto l-flex--row c-event__option"
  58. size="mini"
  59. fill="#1c5cb0"
  60. >
  61. <el-checkbox-button
  62. v-for="week in weeks"
  63. :key="week.value"
  64. :label="week.value"
  65. >
  66. {{ week.label }}
  67. </el-checkbox-button>
  68. </el-checkbox-group>
  69. <div class="c-grid-form__label required">开始时间</div>
  70. <el-time-picker
  71. v-model="eventOptions.startTime"
  72. class="c-event__option"
  73. placeholder="请选择开始时间"
  74. value-format="HH:mm:ss"
  75. :clearable="false"
  76. />
  77. <div class="c-grid-form__label required">结束时间</div>
  78. <el-time-picker
  79. v-model="eventOptions.endTime"
  80. class="c-grid-form__info c-event__option"
  81. data-info="结束时间小于等于开始时间为跨天播放"
  82. placeholder="请选择结束时间"
  83. value-format="HH:mm:ss"
  84. :clearable="false"
  85. />
  86. <div class="c-grid-form__label required">生效日期</div>
  87. <el-date-picker
  88. v-model="eventOptions.start"
  89. class="c-event__option"
  90. type="date"
  91. placeholder="请选择生效日期"
  92. value-format="yyyy-MM-dd HH:mm:ss"
  93. :picker-options="pickerOptions"
  94. @change="onDateTimeChange('start')"
  95. />
  96. <div class="c-grid-form__label required">失效日期</div>
  97. <el-date-picker
  98. v-model="eventOptions.until"
  99. class="c-grid-form__info c-event__option"
  100. data-info="失效日期当天不触发"
  101. type="date"
  102. :disabled="!eventOptions.start"
  103. placeholder="请选择失效日期"
  104. value-format="yyyy-MM-dd HH:mm:ss"
  105. :picker-options="endPickerOptions"
  106. @change="onDateTimeChange('until')"
  107. />
  108. </template>
  109. <template v-if="isCount">
  110. <div class="c-grid-form__label">次数</div>
  111. <el-input-number
  112. v-model="eventOptions.count"
  113. :min="1"
  114. :max="99999999"
  115. :step="1"
  116. step-strictly
  117. />
  118. <div class="c-grid-form__label required">开始时间</div>
  119. <el-date-picker
  120. v-model="eventOptions.start"
  121. class="c-event__option"
  122. type="datetime"
  123. placeholder="请选择生效时间"
  124. value-format="yyyy-MM-dd HH:mm:ss"
  125. :picker-options="pickerOptions"
  126. />
  127. <template v-if="untilDate">
  128. <div class="c-grid-form__label">结束时间</div>
  129. <el-date-picker
  130. :value="untilDate"
  131. class="c-event__option"
  132. type="datetime"
  133. disabled
  134. />
  135. </template>
  136. </template>
  137. <event-target-dialog
  138. ref="eventTargetDialog"
  139. v-bind="$attrs"
  140. @choosen="onChoosen"
  141. />
  142. </div>
  143. </template>
  144. <script>
  145. import {
  146. EventPriority,
  147. EventFreq,
  148. EventTarget
  149. } from '@/constant'
  150. import { getSchedule } from '@/api/calendar'
  151. import { getProgram } from '@/api/program'
  152. import {
  153. isMoreThanOneWeek,
  154. getNearestHitDate,
  155. isOverDay
  156. } from '@/utils/event'
  157. import { parseTime } from '@/utils'
  158. const EventFreqCount = 'COUNT'
  159. export default {
  160. name: 'EventPicker',
  161. props: {
  162. event: {
  163. type: Object,
  164. default: null
  165. },
  166. priority: {
  167. type: Number,
  168. default: EventPriority.NORMAL
  169. },
  170. vertical: {
  171. type: [Boolean, String],
  172. default: false
  173. }
  174. },
  175. data () {
  176. return {
  177. freqOptions: [
  178. { value: EventFreq.ONCE, label: '时段播放' },
  179. { value: EventFreq.WEEKLY, label: '重复播放' },
  180. { value: EventFreqCount, label: '按次播放' }
  181. ],
  182. weeks: [
  183. { value: '0', label: '日' },
  184. { value: '1', label: '一' },
  185. { value: '2', label: '二' },
  186. { value: '3', label: '三' },
  187. { value: '4', label: '四' },
  188. { value: '5', label: '五' },
  189. { value: '6', label: '六' }
  190. ],
  191. eventOptions: null,
  192. eventTarget: null
  193. }
  194. },
  195. computed: {
  196. isOnce () {
  197. return this.eventOptions.freq === EventFreq.ONCE
  198. },
  199. isWeekly () {
  200. return this.eventOptions.freq === EventFreq.WEEKLY
  201. },
  202. isCount () {
  203. return this.eventOptions.freq === EventFreqCount
  204. },
  205. pickerOptions () {
  206. return {
  207. disabledDate: this.isDisableDate
  208. }
  209. },
  210. endPickerOptions () {
  211. return {
  212. disabledDate: this.isDisableEndDate
  213. }
  214. },
  215. minDate () {
  216. const now = new Date()
  217. return new Date(now.getFullYear(), now.getMonth(), now.getDate())
  218. },
  219. msg () {
  220. return this.eventTarget ? this.eventTarget.name : '点击选择播放内容'
  221. },
  222. untilDate () {
  223. if (this.isCount && this.eventTarget) {
  224. const { start, count } = this.eventOptions
  225. const { duration } = this.eventTarget
  226. if (start && duration) {
  227. return new Date(start).getTime() + duration * count * 1000
  228. }
  229. }
  230. return null
  231. }
  232. },
  233. watch: {
  234. event: {
  235. handler () {
  236. this.init()
  237. },
  238. immediate: true
  239. }
  240. },
  241. methods: {
  242. isDisableDate (date) {
  243. return date < this.minDate
  244. },
  245. isDisableEndDate (date) {
  246. if (date < this.minDate) {
  247. return true
  248. }
  249. if (this.eventOptions.start) {
  250. if (this.isWeekly) {
  251. return date <= new Date(this.eventOptions.start)
  252. }
  253. return date < new Date(this.eventOptions.start)
  254. }
  255. return false
  256. },
  257. init () {
  258. const { freq, byDay, target, ...options } = {
  259. freq: EventFreq.ONCE,
  260. start: null,
  261. until: null,
  262. startTime: '00:00:00',
  263. endTime: '00:00:00',
  264. ...this.event
  265. }
  266. if (freq === EventFreq.ONCE && target?.duration) {
  267. const { start, until } = options
  268. this.eventOptions = {
  269. ...options,
  270. freq: EventFreqCount,
  271. count: (new Date(until) - new Date(start)) / target.duration / 1000,
  272. byDay: byDay ? byDay.split(',') : []
  273. }
  274. } else {
  275. this.eventOptions = {
  276. ...options,
  277. freq,
  278. count: 1,
  279. byDay: byDay ? byDay.split(',') : []
  280. }
  281. }
  282. this.eventTarget = target
  283. },
  284. onDateTimeChange (type) {
  285. const { start, until } = this.eventOptions
  286. if (start && until && start > until) {
  287. if (type === 'start') {
  288. this.eventOptions.until = start
  289. } else {
  290. this.eventOptions.start = until
  291. }
  292. }
  293. },
  294. onChoose () {
  295. this.$refs.eventTargetDialog.show()
  296. },
  297. onChoosen ({ value, done }) {
  298. done()
  299. this.eventTarget = value
  300. this.onCheckCount()
  301. this.$emit('choosen', value)
  302. },
  303. onEventTypeChanged () {
  304. this.onCheckCount()
  305. if (this.isCount) {
  306. this.eventOptions.until = null
  307. }
  308. },
  309. onCheckCount () {
  310. if (this.eventTarget && this.isCount) {
  311. this.getDuration(this.eventTarget).then(
  312. duration => {
  313. this.eventTarget = {
  314. ...this.eventTarget,
  315. duration
  316. }
  317. },
  318. ({ isCancel }) => {
  319. if (!isCancel) {
  320. this.$message({
  321. type: 'wanring',
  322. message: '暂无节目时长,将使用默认值(1分钟)'
  323. })
  324. this.eventTarget = {
  325. ...this.eventTarget,
  326. duration: 60
  327. }
  328. }
  329. }
  330. )
  331. }
  332. },
  333. getDuration ({ duration, type, id }) {
  334. if (duration === 0) {
  335. return Promise.reject()
  336. }
  337. if (!duration) {
  338. const loading = this.$showLoading()
  339. switch (type) {
  340. case EventTarget.RECUR:
  341. return getSchedule(id)
  342. .then(({ events }) => events.reduce((total, { spend }) => total + spend, 0))
  343. .finally(() => {
  344. this.$closeLoading(loading)
  345. })
  346. default:
  347. return getProgram(id)
  348. .then(({ data }) => Number(data.duration))
  349. .finally(() => {
  350. this.$closeLoading(loading)
  351. })
  352. }
  353. }
  354. return Promise.resolve(duration)
  355. },
  356. isEventTargetMatchEventFreq () {
  357. if (this.eventTarget && this.isCount) {
  358. return this.eventTarget.type === ''
  359. }
  360. return true
  361. },
  362. onError (message) {
  363. this.$message({
  364. type: 'warning',
  365. message
  366. })
  367. return null
  368. },
  369. isOnceSame () {
  370. const { freq, byDay, startTime, endTime } = this.eventOptions
  371. return freq === EventFreq.WEEKLY && byDay.length === 7 && startTime === '00:00:00' && endTime === '00:00:00'
  372. },
  373. createOnceEvent () {
  374. const { start, until } = this.eventOptions
  375. return {
  376. priority: this.priority,
  377. freq: EventFreq.ONCE,
  378. start, until
  379. }
  380. },
  381. createWeeklyEvent () {
  382. const { start, until, byDay, startTime, endTime } = this.eventOptions
  383. const byDayValue = byDay.join(',')
  384. const isOver = isOverDay({ startTime, endTime })
  385. const startWeek = new Date(start).getDay()
  386. const startDate = start.replace(/\d{2}:\d{2}:\d{2}$/, byDayValue.includes(startWeek) || isOver && byDayValue.includes((startWeek + 6) % 7) ? startTime : '00:00:00')
  387. const untilDate = until && until.replace(/\d{2}:\d{2}:\d{2}$/, isOver && byDayValue.includes((new Date(until).getDay() + 6) % 7) ? endTime : '00:00:00')
  388. const event = {
  389. priority: this.priority,
  390. freq: EventFreq.WEEKLY,
  391. start: startDate,
  392. until: untilDate,
  393. byDay: byDayValue,
  394. startTime,
  395. endTime
  396. }
  397. if (untilDate && !isMoreThanOneWeek(new Date(untilDate) - new Date(startDate)) && !getNearestHitDate(event, startDate, untilDate)) {
  398. return this.onError('有效日期内无法触发')
  399. }
  400. return event
  401. },
  402. createCountEvent () {
  403. const { start, count } = this.eventOptions
  404. const { duration } = this.eventTarget
  405. return {
  406. priority: this.priority,
  407. freq: EventFreq.ONCE,
  408. start,
  409. until: parseTime(new Date(start).getTime() + duration * count * 1000, '{y}-{m}-{d} {h}:{i}:{s}')
  410. }
  411. },
  412. createEventTarget () {
  413. const { duration, ...eventTarget } = this.eventTarget
  414. if (this.isCount) {
  415. return { ...eventTarget, duration }
  416. }
  417. return { ...eventTarget }
  418. },
  419. createEvent () {
  420. if (this.isWeekly && !this.isOnceSame()) {
  421. return this.createWeeklyEvent()
  422. }
  423. if (this.isCount) {
  424. return this.createCountEvent()
  425. }
  426. return this.createOnceEvent()
  427. },
  428. getValue () {
  429. if (!this.eventTarget) {
  430. return this.onError('请选择播放内容')
  431. }
  432. const { start, until } = this.eventOptions
  433. if (this.isOnce) {
  434. if (!start) {
  435. return this.onError('请选择生效时间')
  436. }
  437. if (!until) {
  438. return this.onError('请选择失效时间')
  439. }
  440. if (start === until) {
  441. return this.onError('生效时间与失效时间不能一样')
  442. }
  443. }
  444. if (this.isWeekly) {
  445. const { byDay, startTime, endTime } = this.eventOptions
  446. if (!byDay.length) {
  447. return this.onError('请选择生效星期')
  448. }
  449. if (!startTime) {
  450. return this.onError('请选择开始时间')
  451. }
  452. if (!endTime) {
  453. return this.onError('请选择结束时间')
  454. }
  455. if (!start) {
  456. return this.onError('请选择生效日期')
  457. }
  458. if (!until) {
  459. return this.onError('请选择失效日期')
  460. }
  461. if (start.split(' ')[0] === until.split(' ')[0]) {
  462. return this.onError('生效日期与失效日期不能一样')
  463. }
  464. }
  465. if (until && new Date(until).getTime() <= Date.now()) {
  466. return this.onError('结束时间小于当前时间,请配置有效的生效时间')
  467. }
  468. if (this.isCount) {
  469. if (!start) {
  470. return this.onError('请选择生效时间')
  471. }
  472. if (new Date(this.untilDate).getTime() <= Date.now()) {
  473. return this.onError('结束时间小于当前时间,请配置有效的生效时间或次数')
  474. }
  475. }
  476. return {
  477. ...this.createEvent(),
  478. target: this.createEventTarget()
  479. }
  480. }
  481. }
  482. }
  483. </script>
  484. <style lang="scss" scoped>
  485. .c-event {
  486. &.row &__option {
  487. width: 100%;
  488. }
  489. &__program {
  490. position: relative;
  491. height: 40px;
  492. color: $blue;
  493. font-size: 14px;
  494. line-height: 1;
  495. border-radius: $radius--mini;
  496. border: 1px solid #dcdfe6;
  497. &:hover {
  498. border-color: #c0c4cc;
  499. }
  500. }
  501. &__name {
  502. display: inline-flex;
  503. justify-content: center;
  504. align-items: center;
  505. position: absolute;
  506. width: 100%;
  507. height: 100%;
  508. }
  509. }
  510. </style>