index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <template>
  2. <wrapper
  3. v-loading="loading"
  4. margin
  5. padding
  6. background
  7. >
  8. <div class="l-flex__none l-flex--row has-bottom-padding">
  9. <div class="l-flex__auto l-flex--row c-sibling-item">
  10. <button
  11. v-if="hasEditPermission"
  12. class="o-button"
  13. @click="toAdd"
  14. >
  15. <i class="o-button__icon el-icon-circle-plus-outline" />
  16. 新增
  17. </button>
  18. </div>
  19. <el-select
  20. v-if="hasEditPermission"
  21. v-model="params.status"
  22. class="l-flex__none c-sibling-item o-select"
  23. placeholder="请选择状态"
  24. @change="search"
  25. >
  26. <el-option
  27. v-for="item in statusOptions"
  28. :key="item.label"
  29. :label="item.label"
  30. :value="item.value"
  31. />
  32. </el-select>
  33. <search-input
  34. v-model.trim="params.name"
  35. class="l-flex__none c-sibling-item"
  36. placeholder="节目名称"
  37. @search="search"
  38. />
  39. <button
  40. class="l-flex__none c-sibling-item o-button"
  41. @click="search"
  42. >
  43. 搜索
  44. </button>
  45. </div>
  46. <status-wrapper
  47. v-if="isAbnormal"
  48. :error="error"
  49. @retry="getList"
  50. />
  51. <div class="c-grid">
  52. <div
  53. v-for="item in list"
  54. :key="item.id"
  55. class="l-flex--col"
  56. >
  57. <div
  58. class="c-program u-pointer"
  59. :style="item.style"
  60. @click="onClickProgram(item)"
  61. >
  62. <i
  63. v-if="item.status === 1"
  64. class="c-program__status el-icon-lock u-color--warning"
  65. />
  66. <i
  67. v-if="item.status === 2"
  68. class="c-program__status el-icon-circle-check u-color--success"
  69. />
  70. <span class="c-program__rate">
  71. {{ item.resolutionRatio }}
  72. </span>
  73. <div class="c-program__footer">
  74. <span class="c-program__time u-ellipsis">{{ item.createTime }}</span>
  75. <template v-if="item.status === 0">
  76. <el-tooltip
  77. content="提交"
  78. :hide-after="2000"
  79. >
  80. <i
  81. class="o-icon--active el-icon-upload2 u-pointer"
  82. @click.stop="onSubmit(item)"
  83. />
  84. </el-tooltip>
  85. </template>
  86. <permission :skip="item.status === 0">
  87. <el-tooltip
  88. content="删除"
  89. :hide-after="2000"
  90. >
  91. <i
  92. class="o-icon--active el-icon-delete u-pointer"
  93. @click.stop="onDel(item)"
  94. />
  95. </el-tooltip>
  96. </permission>
  97. </div>
  98. </div>
  99. <edit-input
  100. v-model.trim="item.name"
  101. class="c-program__name"
  102. :disabled="item.status !== 0"
  103. align="left"
  104. @edit="onEdit(item)"
  105. />
  106. </div>
  107. </div>
  108. <pagination
  109. :total="totalCount"
  110. :page-sizes="[8, 16, 32]"
  111. :page.sync="params.pageNum"
  112. :limit.sync="params.pageSize"
  113. @pagination="getList"
  114. />
  115. <el-dialog
  116. title="新增节目"
  117. :visible.sync="adding"
  118. custom-class="c-dialog"
  119. :close-on-click-modal="false"
  120. :before-close="handleCloseAddDialog"
  121. >
  122. <div class="c-form">
  123. <div class="c-form__section">
  124. <span class="c-form__label">名称:</span>
  125. <el-input
  126. v-model.trim="program.name"
  127. class="c-form__item"
  128. placeholder="请输入名称"
  129. maxlength="50"
  130. show-word-limit
  131. />
  132. </div>
  133. <div class="c-form__section">
  134. <span class="c-form__label">分辨率:</span>
  135. <el-select
  136. v-model="program.resolutionRatio"
  137. class="c-form__item"
  138. placeholder="请选择"
  139. :loading="fetching"
  140. popper-class="o-select-option"
  141. >
  142. <el-option
  143. v-for="ratio in ratios"
  144. :key="ratio.value"
  145. :label="ratio.label"
  146. :value="ratio.value"
  147. />
  148. </el-select>
  149. </div>
  150. </div>
  151. <template #footer>
  152. <button
  153. class="o-button"
  154. @click="add"
  155. >
  156. 确定
  157. </button>
  158. <button
  159. class="o-button cancel"
  160. @click="handleCloseAddDialog"
  161. >
  162. 取消
  163. </button>
  164. </template>
  165. </el-dialog>
  166. </wrapper>
  167. </template>
  168. <script>
  169. import { mapGetters } from 'vuex'
  170. import { getAssetUrl } from '@/api/asset'
  171. import { getRatios } from '@/api/device'
  172. import {
  173. getPrograms,
  174. addProgram,
  175. updateProgramName,
  176. deleteProgram,
  177. getProgram,
  178. submitProgram
  179. } from '@/api/program'
  180. import { State } from '@/constant'
  181. import { createListOptions } from '@/utils'
  182. import { validate } from './core/utils'
  183. export default {
  184. name: 'BigScreen',
  185. data () {
  186. return {
  187. statusOptions: [
  188. { value: void 0, label: '全部状态' },
  189. { value: State.READY, label: '待提交' },
  190. { value: State.SUBMITTED, label: '未审核' },
  191. { value: State.RESOLVED, label: '已审核' }
  192. ],
  193. adding: false,
  194. program: {},
  195. fetching: false,
  196. ratios: [],
  197. ...createListOptions({
  198. pageSize: 8,
  199. status: this.$store.getters.hasEditPermission ? void 0 : State.RESOLVED,
  200. name: ''
  201. }),
  202. disabled: false
  203. }
  204. },
  205. computed: {
  206. ...mapGetters(['hasEditPermission']),
  207. isAbnormal () {
  208. return this.error || !this.loading && this.totalCount === 0
  209. }
  210. },
  211. created () {
  212. this.getList()
  213. window.addEventListener('message', this.onMessage)
  214. },
  215. beforeDestroy () {
  216. window.removeEventListener('message', this.onMessage)
  217. },
  218. methods: {
  219. onMessage (event) {
  220. if (!this.loading) {
  221. const id = event.data?.id
  222. if (id) {
  223. const program = this.list.find(program => program.id === id)
  224. if (program) {
  225. program.style = this.getStyle(event.data.base64, true)
  226. }
  227. }
  228. }
  229. },
  230. search () {
  231. if (this.disabled) {
  232. return
  233. }
  234. this.list = []
  235. this.totalCount = 0
  236. this.params.pageNum = 1
  237. this.getList()
  238. },
  239. getStyle (img, base64) {
  240. return img ? {
  241. backgroundSize: 'contain',
  242. backgroundImage: `url("${base64 || /^data/.test(img) ? img : getAssetUrl(img)}")`
  243. } : {}
  244. },
  245. transform ({ id, name, status, resolutionRatio, createTime, img }) {
  246. return {
  247. id, name, status, resolutionRatio,
  248. style: this.getStyle(img),
  249. createTime: createTime?.split(' ')[0],
  250. originalName: name
  251. }
  252. },
  253. onEdit (item) {
  254. if (!item.name) {
  255. item.name = item.originalName
  256. return
  257. }
  258. if (item.name === item.originalName) {
  259. return
  260. }
  261. updateProgramName({
  262. id: item.id,
  263. name: item.name
  264. }).then(() => {
  265. item.originalName = item.name
  266. }, () => {
  267. item.name = item.originalName
  268. })
  269. },
  270. getList () {
  271. this.error = false
  272. this.loading = true
  273. getPrograms(this.params).then(({ data, totalCount }) => {
  274. this.list = data.map(this.transform)
  275. this.totalCount = totalCount
  276. }, () => {
  277. this.error = true
  278. this.list = []
  279. }).finally(() => {
  280. this.loading = false
  281. })
  282. },
  283. getRatios () {
  284. if (!this.fetching && this.ratios.length === 0) {
  285. this.fetching = true
  286. getRatios().then(ratios => {
  287. this.ratios = ratios
  288. }).finally(() => {
  289. this.fetching = false
  290. })
  291. }
  292. },
  293. toAdd () {
  294. if (this.disabled) {
  295. return
  296. }
  297. this.program = {
  298. name: '',
  299. resolutionRatio: ''
  300. }
  301. this.adding = true
  302. this.getRatios()
  303. },
  304. handleCloseAddDialog () {
  305. this.adding = false
  306. },
  307. add () {
  308. if (!this.program.name) {
  309. this.$message({
  310. type: 'warning',
  311. message: '名称不能为空'
  312. })
  313. return
  314. }
  315. if (!this.program.resolutionRatio) {
  316. this.$message({
  317. type: 'warning',
  318. message: '请选择分辨率'
  319. })
  320. return
  321. }
  322. addProgram(this.program).then(({ data: id }) => {
  323. this.handleCloseAddDialog()
  324. const params = this.params
  325. if (params.status !== void 0 && params.status !== State.READY) {
  326. params.status = void 0
  327. }
  328. if (params.name && !(new RegExp(params.name).test(this.program.name))) {
  329. params.name = ''
  330. }
  331. this.search()
  332. this.onDesign(id)
  333. })
  334. },
  335. onDesign (id) {
  336. const route = this.$router.resolve({
  337. name: 'design',
  338. params: { id }
  339. })
  340. window.open(route.href, '_blank')
  341. },
  342. onView (id) {
  343. const route = this.$router.resolve({
  344. name: 'view',
  345. params: { id }
  346. })
  347. window.open(route.href, '_blank')
  348. },
  349. onClickProgram (item) {
  350. const { id, status } = item
  351. if (status === 0) {
  352. this.onDesign(id)
  353. } else {
  354. this.onView(id)
  355. }
  356. },
  357. onSubmit (item) {
  358. const loading = this.$showLoading()
  359. getProgram(item.id).then(({ data }) => {
  360. const { itemJsonStr } = data
  361. if (!itemJsonStr) {
  362. this.$message({
  363. type: 'warning',
  364. message: `请先编辑节目`
  365. })
  366. return
  367. }
  368. try {
  369. const error = validate(JSON.parse(itemJsonStr))
  370. if (error) {
  371. this.$message({
  372. type: 'warning',
  373. message: error
  374. })
  375. return
  376. }
  377. } catch (e) {
  378. this.$message({
  379. type: 'warning',
  380. message: `节目数据异常`
  381. })
  382. return
  383. }
  384. submitProgram(item).then(() => {
  385. this.getList()
  386. })
  387. }).finally(() => {
  388. this.$closeLoading(loading)
  389. })
  390. },
  391. onDel (item) {
  392. deleteProgram(item).then(() => {
  393. if (this.list.length === 1 && this.params.pageNum > 1) {
  394. this.params.pageNum -= 1
  395. }
  396. this.getList()
  397. })
  398. }
  399. }
  400. }
  401. </script>
  402. <style lang="scss" scoped>
  403. .c-program {
  404. position: relative;
  405. padding-top: 60%;
  406. color: #fff;
  407. font-size: 16px;
  408. border-radius: $radius;
  409. background: rgba(0, 0, 0, 0.8) url("~@/assets/program_bg.png") center center /
  410. 100% 100% no-repeat;
  411. overflow: hidden;
  412. &__status {
  413. position: absolute;
  414. top: 6px;
  415. left: 6px;
  416. font-size: 24px;
  417. }
  418. &__rate {
  419. position: absolute;
  420. top: 0;
  421. right: 0;
  422. padding: 2px 4px;
  423. font-size: 12px;
  424. background-color: rgba(0, 0, 0, 0.5);
  425. border-radius: 0 0 0 4px;
  426. }
  427. &__name {
  428. color: $black;
  429. font-weight: bold;
  430. }
  431. &__footer {
  432. display: inline-flex;
  433. align-items: center;
  434. position: absolute;
  435. width: 100%;
  436. bottom: 0;
  437. padding: 24px 16px 16px;
  438. background-image: linear-gradient(
  439. to bottom,
  440. rgba(#000, 0) 0%,
  441. rgba(#000, 0.6) 100%
  442. );
  443. i {
  444. margin-left: $spacing;
  445. font-size: 18px;
  446. }
  447. }
  448. &__time {
  449. flex: 1 1 auto;
  450. min-width: 0;
  451. font-size: 14px;
  452. }
  453. }
  454. </style>