index.vue 11 KB

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